In cmd_keypress_cb(), sanity-check c for NULL consistently.
[xxxterm.git] / xxxterm.c
blob3473c616da0d795b8918f4c38675097b8f96683b
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>
61 #include <sys/time.h>
62 #include <sys/resource.h>
64 #include <gtk/gtk.h>
65 #include <gdk/gdkkeysyms.h>
67 #if GTK_CHECK_VERSION(3,0,0)
68 /* we still use GDK_* instead of GDK_KEY_* */
69 #include <gdk/gdkkeysyms-compat.h>
70 #endif
72 #include <webkit/webkit.h>
73 #include <libsoup/soup.h>
74 #include <gnutls/gnutls.h>
75 #include <JavaScriptCore/JavaScript.h>
76 #include <gnutls/x509.h>
78 #include "javascript.h"
81 javascript.h borrowed from vimprobable2 under the following license:
83 Copyright (c) 2009 Leon Winter
84 Copyright (c) 2009 Hannes Schueller
85 Copyright (c) 2009 Matto Fransen
87 Permission is hereby granted, free of charge, to any person obtaining a copy
88 of this software and associated documentation files (the "Software"), to deal
89 in the Software without restriction, including without limitation the rights
90 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
91 copies of the Software, and to permit persons to whom the Software is
92 furnished to do so, subject to the following conditions:
94 The above copyright notice and this permission notice shall be included in
95 all copies or substantial portions of the Software.
97 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
98 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
99 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
100 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
101 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
103 THE SOFTWARE.
106 static char *version = "$xxxterm$";
108 /* hooked functions */
109 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
110 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
111 SoupCookie *);
113 /*#define XT_DEBUG*/
114 #ifdef XT_DEBUG
115 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
116 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
117 #define XT_D_MOVE 0x0001
118 #define XT_D_KEY 0x0002
119 #define XT_D_TAB 0x0004
120 #define XT_D_URL 0x0008
121 #define XT_D_CMD 0x0010
122 #define XT_D_NAV 0x0020
123 #define XT_D_DOWNLOAD 0x0040
124 #define XT_D_CONFIG 0x0080
125 #define XT_D_JS 0x0100
126 #define XT_D_FAVORITE 0x0200
127 #define XT_D_PRINTING 0x0400
128 #define XT_D_COOKIE 0x0800
129 #define XT_D_KEYBINDING 0x1000
130 #define XT_D_CLIP 0x2000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
147 #else
148 #define DPRINTF(x...)
149 #define DNPRINTF(n,x...)
150 #endif
152 #define LENGTH(x) (sizeof x / sizeof x[0])
153 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
154 ~(GDK_BUTTON1_MASK) & \
155 ~(GDK_BUTTON2_MASK) & \
156 ~(GDK_BUTTON3_MASK) & \
157 ~(GDK_BUTTON4_MASK) & \
158 ~(GDK_BUTTON5_MASK))
160 char *icons[] = {
161 "xxxtermicon16.png",
162 "xxxtermicon32.png",
163 "xxxtermicon48.png",
164 "xxxtermicon64.png",
165 "xxxtermicon128.png"
168 struct tab {
169 TAILQ_ENTRY(tab) entry;
170 GtkWidget *vbox;
171 GtkWidget *tab_content;
172 GtkWidget *label;
173 GtkWidget *spinner;
174 GtkWidget *uri_entry;
175 GtkWidget *search_entry;
176 GtkWidget *toolbar;
177 GtkWidget *browser_win;
178 GtkWidget *statusbar;
179 GtkWidget *cmd;
180 GtkWidget *buffers;
181 GtkWidget *oops;
182 GtkWidget *backward;
183 GtkWidget *forward;
184 GtkWidget *stop;
185 GtkWidget *gohome;
186 GtkWidget *js_toggle;
187 GtkEntryCompletion *completion;
188 guint tab_id;
189 WebKitWebView *wv;
191 WebKitWebHistoryItem *item;
192 WebKitWebBackForwardList *bfl;
194 /* favicon */
195 WebKitNetworkRequest *icon_request;
196 WebKitDownload *icon_download;
197 gchar *icon_dest_uri;
199 /* adjustments for browser */
200 GtkScrollbar *sb_h;
201 GtkScrollbar *sb_v;
202 GtkAdjustment *adjust_h;
203 GtkAdjustment *adjust_v;
205 /* flags */
206 int focus_wv;
207 int ctrl_click;
208 gchar *status;
209 int xtp_meaning; /* identifies dls/favorites */
211 /* hints */
212 int hints_on;
213 int hint_mode;
214 #define XT_HINT_NONE (0)
215 #define XT_HINT_NUMERICAL (1)
216 #define XT_HINT_ALPHANUM (2)
217 char hint_buf[128];
218 char hint_num[128];
220 /* custom stylesheet */
221 int styled;
222 char *stylesheet;
224 /* search */
225 char *search_text;
226 int search_forward;
228 /* settings */
229 WebKitWebSettings *settings;
230 int font_size;
231 gchar *user_agent;
233 TAILQ_HEAD(tab_list, tab);
235 struct history {
236 RB_ENTRY(history) entry;
237 const gchar *uri;
238 const gchar *title;
240 RB_HEAD(history_list, history);
242 struct download {
243 RB_ENTRY(download) entry;
244 int id;
245 WebKitDownload *download;
246 struct tab *tab;
248 RB_HEAD(download_list, download);
250 struct domain {
251 RB_ENTRY(domain) entry;
252 gchar *d;
253 int handy; /* app use */
255 RB_HEAD(domain_list, domain);
257 struct undo {
258 TAILQ_ENTRY(undo) entry;
259 gchar *uri;
260 GList *history;
261 int back; /* Keeps track of how many back
262 * history items there are. */
264 TAILQ_HEAD(undo_tailq, undo);
266 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
267 int next_download_id = 1;
269 struct karg {
270 int i;
271 char *s;
272 int p;
275 /* defines */
276 #define XT_NAME ("XXXTerm")
277 #define XT_DIR (".xxxterm")
278 #define XT_CACHE_DIR ("cache")
279 #define XT_CERT_DIR ("certs/")
280 #define XT_SESSIONS_DIR ("sessions/")
281 #define XT_CONF_FILE ("xxxterm.conf")
282 #define XT_FAVS_FILE ("favorites")
283 #define XT_SAVED_TABS_FILE ("main_session")
284 #define XT_RESTART_TABS_FILE ("restart_tabs")
285 #define XT_SOCKET_FILE ("socket")
286 #define XT_HISTORY_FILE ("history")
287 #define XT_REJECT_FILE ("rejected.txt")
288 #define XT_COOKIE_FILE ("cookies.txt")
289 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
290 #define XT_CB_HANDLED (TRUE)
291 #define XT_CB_PASSTHROUGH (FALSE)
292 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
293 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
294 #define XT_DLMAN_REFRESH "10"
295 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
296 "td{overflow: hidden;" \
297 " padding: 2px 2px 2px 2px;" \
298 " border: 1px solid black;" \
299 " vertical-align:top;" \
300 " word-wrap: break-word}\n" \
301 "tr:hover{background: #ffff99}\n" \
302 "th{background-color: #cccccc;" \
303 " border: 1px solid black}\n" \
304 "table{width: 100%%;" \
305 " border: 1px black solid;" \
306 " border-collapse:collapse}\n" \
307 ".progress-outer{" \
308 "border: 1px solid black;" \
309 " height: 8px;" \
310 " width: 90%%}\n" \
311 ".progress-inner{float: left;" \
312 " height: 8px;" \
313 " background: green}\n" \
314 ".dlstatus{font-size: small;" \
315 " text-align: center}\n" \
316 "</style>\n"
317 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
318 #define XT_MAX_UNDO_CLOSE_TAB (32)
319 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
320 #define XT_PRINT_EXTRA_MARGIN 10
322 /* colors */
323 #define XT_COLOR_RED "#cc0000"
324 #define XT_COLOR_YELLOW "#ffff66"
325 #define XT_COLOR_BLUE "lightblue"
326 #define XT_COLOR_GREEN "#99ff66"
327 #define XT_COLOR_WHITE "white"
328 #define XT_COLOR_BLACK "black"
331 * xxxterm "protocol" (xtp)
332 * We use this for managing stuff like downloads and favorites. They
333 * make magical HTML pages in memory which have xxxt:// links in order
334 * to communicate with xxxterm's internals. These links take the format:
335 * xxxt://class/session_key/action/arg
337 * Don't begin xtp class/actions as 0. atoi returns that on error.
339 * Typically we have not put addition of items in this framework, as
340 * adding items is either done via an ex-command or via a keybinding instead.
343 #define XT_XTP_STR "xxxt://"
345 /* XTP classes (xxxt://<class>) */
346 #define XT_XTP_INVALID 0 /* invalid */
347 #define XT_XTP_DL 1 /* downloads */
348 #define XT_XTP_HL 2 /* history */
349 #define XT_XTP_CL 3 /* cookies */
350 #define XT_XTP_FL 4 /* favorites */
352 /* XTP download actions */
353 #define XT_XTP_DL_LIST 1
354 #define XT_XTP_DL_CANCEL 2
355 #define XT_XTP_DL_REMOVE 3
357 /* XTP history actions */
358 #define XT_XTP_HL_LIST 1
359 #define XT_XTP_HL_REMOVE 2
361 /* XTP cookie actions */
362 #define XT_XTP_CL_LIST 1
363 #define XT_XTP_CL_REMOVE 2
365 /* XTP cookie actions */
366 #define XT_XTP_FL_LIST 1
367 #define XT_XTP_FL_REMOVE 2
369 /* actions */
370 #define XT_MOVE_INVALID (0)
371 #define XT_MOVE_DOWN (1)
372 #define XT_MOVE_UP (2)
373 #define XT_MOVE_BOTTOM (3)
374 #define XT_MOVE_TOP (4)
375 #define XT_MOVE_PAGEDOWN (5)
376 #define XT_MOVE_PAGEUP (6)
377 #define XT_MOVE_HALFDOWN (7)
378 #define XT_MOVE_HALFUP (8)
379 #define XT_MOVE_LEFT (9)
380 #define XT_MOVE_FARLEFT (10)
381 #define XT_MOVE_RIGHT (11)
382 #define XT_MOVE_FARRIGHT (12)
384 #define XT_TAB_LAST (-4)
385 #define XT_TAB_FIRST (-3)
386 #define XT_TAB_PREV (-2)
387 #define XT_TAB_NEXT (-1)
388 #define XT_TAB_INVALID (0)
389 #define XT_TAB_NEW (1)
390 #define XT_TAB_DELETE (2)
391 #define XT_TAB_DELQUIT (3)
392 #define XT_TAB_OPEN (4)
393 #define XT_TAB_UNDO_CLOSE (5)
394 #define XT_TAB_SHOW (6)
395 #define XT_TAB_HIDE (7)
397 #define XT_NAV_INVALID (0)
398 #define XT_NAV_BACK (1)
399 #define XT_NAV_FORWARD (2)
400 #define XT_NAV_RELOAD (3)
401 #define XT_NAV_RELOAD_CACHE (4)
403 #define XT_FOCUS_INVALID (0)
404 #define XT_FOCUS_URI (1)
405 #define XT_FOCUS_SEARCH (2)
407 #define XT_SEARCH_INVALID (0)
408 #define XT_SEARCH_NEXT (1)
409 #define XT_SEARCH_PREV (2)
411 #define XT_PASTE_CURRENT_TAB (0)
412 #define XT_PASTE_NEW_TAB (1)
414 #define XT_FONT_SET (0)
416 #define XT_URL_SHOW (1)
417 #define XT_URL_HIDE (2)
419 #define XT_STATUSBAR_SHOW (1)
420 #define XT_STATUSBAR_HIDE (2)
422 #define XT_WL_TOGGLE (1<<0)
423 #define XT_WL_ENABLE (1<<1)
424 #define XT_WL_DISABLE (1<<2)
425 #define XT_WL_FQDN (1<<3) /* default */
426 #define XT_WL_TOPLEVEL (1<<4)
427 #define XT_WL_PERSISTENT (1<<5)
428 #define XT_WL_SESSION (1<<6)
429 #define XT_WL_RELOAD (1<<7)
431 #define XT_SHOW (1<<7)
432 #define XT_DELETE (1<<8)
433 #define XT_SAVE (1<<9)
434 #define XT_OPEN (1<<10)
436 #define XT_CMD_OPEN (0)
437 #define XT_CMD_OPEN_CURRENT (1)
438 #define XT_CMD_TABNEW (2)
439 #define XT_CMD_TABNEW_CURRENT (3)
441 #define XT_STATUS_NOTHING (0)
442 #define XT_STATUS_LINK (1)
443 #define XT_STATUS_URI (2)
444 #define XT_STATUS_LOADING (3)
446 #define XT_SES_DONOTHING (0)
447 #define XT_SES_CLOSETABS (1)
449 #define XT_BM_NORMAL (0)
450 #define XT_BM_WHITELIST (1)
451 #define XT_BM_KIOSK (2)
453 #define XT_PREFIX (1<<0)
454 #define XT_USERARG (1<<1)
455 #define XT_URLARG (1<<2)
456 #define XT_INTARG (1<<3)
458 /* mime types */
459 struct mime_type {
460 char *mt_type;
461 char *mt_action;
462 int mt_default;
463 int mt_download;
464 TAILQ_ENTRY(mime_type) entry;
466 TAILQ_HEAD(mime_type_list, mime_type);
468 /* uri aliases */
469 struct alias {
470 char *a_name;
471 char *a_uri;
472 TAILQ_ENTRY(alias) entry;
474 TAILQ_HEAD(alias_list, alias);
476 /* settings that require restart */
477 int tabless = 0; /* allow only 1 tab */
478 int enable_socket = 0;
479 int single_instance = 0; /* only allow one xxxterm to run */
480 int fancy_bar = 1; /* fancy toolbar */
481 int browser_mode = XT_BM_NORMAL;
482 int enable_localstorage = 0;
484 /* runtime settings */
485 int show_tabs = 1; /* show tabs on notebook */
486 int show_url = 1; /* show url toolbar on notebook */
487 int show_statusbar = 0; /* vimperator style status bar */
488 int ctrl_click_focus = 0; /* ctrl click gets focus */
489 int cookies_enabled = 1; /* enable cookies */
490 int read_only_cookies = 0; /* enable to not write cookies */
491 int enable_scripts = 1;
492 int enable_plugins = 0;
493 int default_font_size = 12;
494 gfloat default_zoom_level = 1.0;
495 int window_height = 768;
496 int window_width = 1024;
497 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
498 int refresh_interval = 10; /* download refresh interval */
499 int enable_cookie_whitelist = 0;
500 int enable_js_whitelist = 0;
501 int session_timeout = 3600; /* cookie session timeout */
502 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
503 char *ssl_ca_file = NULL;
504 char *resource_dir = NULL;
505 gboolean ssl_strict_certs = FALSE;
506 int append_next = 1; /* append tab after current tab */
507 char *home = NULL;
508 char *search_string = NULL;
509 char *http_proxy = NULL;
510 char download_dir[PATH_MAX];
511 char runtime_settings[PATH_MAX]; /* override of settings */
512 int allow_volatile_cookies = 0;
513 int save_global_history = 0; /* save global history to disk */
514 char *user_agent = NULL;
515 int save_rejected_cookies = 0;
516 int session_autosave = 0;
517 int guess_search = 0;
518 int dns_prefetch = FALSE;
519 gint max_connections = 25;
520 gint max_host_connections = 5;
521 gint enable_spell_checking = 0;
522 char *spell_check_languages = NULL;
524 char *cmd_font_name = NULL;
525 char *statusbar_font_name = NULL;
526 PangoFontDescription *cmd_font;
527 PangoFontDescription *statusbar_font;
529 struct settings;
530 struct key_binding;
531 int set_download_dir(struct settings *, char *);
532 int set_work_dir(struct settings *, char *);
533 int set_runtime_dir(struct settings *, char *);
534 int set_browser_mode(struct settings *, char *);
535 int set_cookie_policy(struct settings *, char *);
536 int add_alias(struct settings *, char *);
537 int add_mime_type(struct settings *, char *);
538 int add_cookie_wl(struct settings *, char *);
539 int add_js_wl(struct settings *, char *);
540 int add_kb(struct settings *, char *);
541 void button_set_stockid(GtkWidget *, char *);
542 GtkWidget * create_button(char *, char *, int);
544 char *get_browser_mode(struct settings *);
545 char *get_cookie_policy(struct settings *);
547 char *get_download_dir(struct settings *);
548 char *get_work_dir(struct settings *);
549 char *get_runtime_dir(struct settings *);
551 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
552 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
553 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
554 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
555 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
557 void recalc_tabs(void);
559 struct special {
560 int (*set)(struct settings *, char *);
561 char *(*get)(struct settings *);
562 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
565 struct special s_browser_mode = {
566 set_browser_mode,
567 get_browser_mode,
568 NULL
571 struct special s_cookie = {
572 set_cookie_policy,
573 get_cookie_policy,
574 NULL
577 struct special s_alias = {
578 add_alias,
579 NULL,
580 walk_alias
583 struct special s_mime = {
584 add_mime_type,
585 NULL,
586 walk_mime_type
589 struct special s_js = {
590 add_js_wl,
591 NULL,
592 walk_js_wl
595 struct special s_kb = {
596 add_kb,
597 NULL,
598 walk_kb
601 struct special s_cookie_wl = {
602 add_cookie_wl,
603 NULL,
604 walk_cookie_wl
607 struct special s_download_dir = {
608 set_download_dir,
609 get_download_dir,
610 NULL
613 struct special s_work_dir = {
614 set_work_dir,
615 get_work_dir,
616 NULL
619 struct settings {
620 char *name;
621 int type;
622 #define XT_S_INVALID (0)
623 #define XT_S_INT (1)
624 #define XT_S_STR (2)
625 #define XT_S_FLOAT (3)
626 uint32_t flags;
627 #define XT_SF_RESTART (1<<0)
628 #define XT_SF_RUNTIME (1<<1)
629 int *ival;
630 char **sval;
631 struct special *s;
632 gfloat *fval;
633 } rs[] = {
634 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
635 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
636 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
637 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
638 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
639 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
640 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
641 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
642 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
643 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
644 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
645 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
646 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
647 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
648 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
649 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
650 { "home", XT_S_STR, 0, NULL, &home, NULL },
651 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
652 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
653 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
654 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
655 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
656 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
657 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
658 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
659 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
660 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
661 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
662 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
663 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
664 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
665 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
666 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
667 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
668 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
669 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
670 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
671 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
672 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
673 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
674 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
675 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
677 /* font settings */
678 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
679 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
681 /* runtime settings */
682 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
683 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
684 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
685 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
686 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
689 int about(struct tab *, struct karg *);
690 int blank(struct tab *, struct karg *);
691 int ca_cmd(struct tab *, struct karg *);
692 int cookie_show_wl(struct tab *, struct karg *);
693 int js_show_wl(struct tab *, struct karg *);
694 int help(struct tab *, struct karg *);
695 int set(struct tab *, struct karg *);
696 int stats(struct tab *, struct karg *);
697 int marco(struct tab *, struct karg *);
698 const char * marco_message(int *);
699 int xtp_page_cl(struct tab *, struct karg *);
700 int xtp_page_dl(struct tab *, struct karg *);
701 int xtp_page_fl(struct tab *, struct karg *);
702 int xtp_page_hl(struct tab *, struct karg *);
703 void xt_icon_from_file(struct tab *, char *);
704 const gchar *get_uri(struct tab *);
705 const gchar *get_title(struct tab *);
707 #define XT_URI_ABOUT ("about:")
708 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
709 #define XT_URI_ABOUT_ABOUT ("about")
710 #define XT_URI_ABOUT_BLANK ("blank")
711 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
712 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
713 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
714 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
715 #define XT_URI_ABOUT_FAVORITES ("favorites")
716 #define XT_URI_ABOUT_HELP ("help")
717 #define XT_URI_ABOUT_HISTORY ("history")
718 #define XT_URI_ABOUT_JSWL ("jswl")
719 #define XT_URI_ABOUT_SET ("set")
720 #define XT_URI_ABOUT_STATS ("stats")
721 #define XT_URI_ABOUT_MARCO ("marco")
723 struct about_type {
724 char *name;
725 int (*func)(struct tab *, struct karg *);
726 } about_list[] = {
727 { XT_URI_ABOUT_ABOUT, about },
728 { XT_URI_ABOUT_BLANK, blank },
729 { XT_URI_ABOUT_CERTS, ca_cmd },
730 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
731 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
732 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
733 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
734 { XT_URI_ABOUT_HELP, help },
735 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
736 { XT_URI_ABOUT_JSWL, js_show_wl },
737 { XT_URI_ABOUT_SET, set },
738 { XT_URI_ABOUT_STATS, stats },
739 { XT_URI_ABOUT_MARCO, marco },
742 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
743 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
744 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
745 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
746 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
747 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
748 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
750 /* globals */
751 extern char *__progname;
752 char **start_argv;
753 struct passwd *pwd;
754 GtkWidget *main_window;
755 GtkNotebook *notebook;
756 GtkWidget *arrow, *abtn;
757 struct tab_list tabs;
758 struct history_list hl;
759 struct download_list downloads;
760 struct domain_list c_wl;
761 struct domain_list js_wl;
762 struct undo_tailq undos;
763 struct keybinding_list kbl;
764 int undo_count;
765 int updating_dl_tabs = 0;
766 int updating_hl_tabs = 0;
767 int updating_cl_tabs = 0;
768 int updating_fl_tabs = 0;
769 char *global_search;
770 uint64_t blocked_cookies = 0;
771 char named_session[PATH_MAX];
772 int icon_size_map(int);
774 GtkListStore *completion_model;
775 void completion_add(struct tab *);
776 void completion_add_uri(const gchar *);
777 GtkListStore *buffers_store;
778 void xxx_dir(char *);
780 void
781 sigchild(int sig)
783 int saved_errno, status;
784 pid_t pid;
786 saved_errno = errno;
788 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
789 if (pid == -1) {
790 if (errno == EINTR)
791 continue;
792 if (errno != ECHILD) {
794 clog_warn("sigchild: waitpid:");
797 break;
800 if (WIFEXITED(status)) {
801 if (WEXITSTATUS(status) != 0) {
803 clog_warnx("sigchild: child exit status: %d",
804 WEXITSTATUS(status));
807 } else {
809 clog_warnx("sigchild: child is terminated abnormally");
814 errno = saved_errno;
818 is_g_object_setting(GObject *o, char *str)
820 guint n_props = 0, i;
821 GParamSpec **proplist;
823 if (! G_IS_OBJECT(o))
824 return (0);
826 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
827 &n_props);
829 for (i=0; i < n_props; i++) {
830 if (! strcmp(proplist[i]->name, str))
831 return (1);
833 return (0);
836 gchar *
837 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
839 gchar *r;
841 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
842 "<head>\n"
843 "<title>%s</title>\n"
844 "%s"
845 "%s"
846 "</head>\n"
847 "<body>\n"
848 "<h1>%s</h1>\n"
849 "%s\n</body>\n"
850 "</html>",
851 title,
852 addstyles ? XT_PAGE_STYLE : "",
853 head,
854 title,
855 body);
857 return r;
861 * Display a web page from a HTML string in memory, rather than from a URL
863 void
864 load_webkit_string(struct tab *t, const char *str, gchar *title)
866 char file[PATH_MAX];
867 int i;
869 /* we set this to indicate we want to manually do navaction */
870 if (t->bfl)
871 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
873 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
874 if (title) {
875 /* set t->xtp_meaning */
876 for (i = 0; i < LENGTH(about_list); i++)
877 if (!strcmp(title, about_list[i].name)) {
878 t->xtp_meaning = i;
879 break;
882 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
883 #if GTK_CHECK_VERSION(2, 20, 0)
884 gtk_spinner_stop(GTK_SPINNER(t->spinner));
885 gtk_widget_hide(t->spinner);
886 #endif
887 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
888 xt_icon_from_file(t, file);
892 struct tab *
893 get_current_tab(void)
895 struct tab *t;
897 TAILQ_FOREACH(t, &tabs, entry) {
898 if (t->tab_id == gtk_notebook_get_current_page(notebook))
899 return (t);
902 warnx("%s: no current tab", __func__);
904 return (NULL);
907 void
908 set_status(struct tab *t, gchar *s, int status)
910 gchar *type = NULL;
912 if (s == NULL)
913 return;
915 switch (status) {
916 case XT_STATUS_LOADING:
917 type = g_strdup_printf("Loading: %s", s);
918 s = type;
919 break;
920 case XT_STATUS_LINK:
921 type = g_strdup_printf("Link: %s", s);
922 if (!t->status)
923 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
924 s = type;
925 break;
926 case XT_STATUS_URI:
927 type = g_strdup_printf("%s", s);
928 if (!t->status) {
929 t->status = g_strdup(type);
931 s = type;
932 if (!t->status)
933 t->status = g_strdup(s);
934 break;
935 case XT_STATUS_NOTHING:
936 /* FALL THROUGH */
937 default:
938 break;
940 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
941 if (type)
942 g_free(type);
945 void
946 hide_cmd(struct tab *t)
948 gtk_widget_hide(t->cmd);
951 void
952 show_cmd(struct tab *t)
954 gtk_widget_hide(t->oops);
955 gtk_widget_show(t->cmd);
958 void
959 hide_buffers(struct tab *t)
961 gtk_widget_hide(t->buffers);
962 gtk_list_store_clear(buffers_store);
965 enum {
966 COL_ID = 0,
967 COL_TITLE,
968 NUM_COLS
972 sort_tabs_by_page_num(struct tab ***stabs)
974 int num_tabs = 0;
975 struct tab *t;
977 num_tabs = gtk_notebook_get_n_pages(notebook);
979 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
981 TAILQ_FOREACH(t, &tabs, entry)
982 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
984 return (num_tabs);
987 void
988 buffers_make_list(void)
990 int i, num_tabs;
991 const gchar *title = NULL;
992 GtkTreeIter iter;
993 struct tab **stabs = NULL;
995 num_tabs = sort_tabs_by_page_num(&stabs);
997 for (i = 0; i < num_tabs; i++)
998 if (stabs[i]) {
999 gtk_list_store_append(buffers_store, &iter);
1000 title = get_title(stabs[i]);
1001 gtk_list_store_set(buffers_store, &iter,
1002 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1003 * rather than 0. */
1004 COL_TITLE, title,
1005 -1);
1008 g_free(stabs);
1011 void
1012 show_buffers(struct tab *t)
1014 buffers_make_list();
1015 gtk_widget_show(t->buffers);
1016 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1019 void
1020 toggle_buffers(struct tab *t)
1022 if (gtk_widget_get_visible(t->buffers))
1023 hide_buffers(t);
1024 else
1025 show_buffers(t);
1029 buffers(struct tab *t, struct karg *args)
1031 show_buffers(t);
1033 return (0);
1036 void
1037 hide_oops(struct tab *t)
1039 gtk_widget_hide(t->oops);
1042 void
1043 show_oops(struct tab *at, const char *fmt, ...)
1045 va_list ap;
1046 char *msg;
1047 struct tab *t = NULL;
1049 if (fmt == NULL)
1050 return;
1052 if (at == NULL) {
1053 if ((t = get_current_tab()) == NULL)
1054 return;
1055 } else
1056 t = at;
1058 va_start(ap, fmt);
1059 if (vasprintf(&msg, fmt, ap) == -1)
1060 errx(1, "show_oops failed");
1061 va_end(ap);
1063 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1064 gtk_widget_hide(t->cmd);
1065 gtk_widget_show(t->oops);
1068 char *
1069 get_as_string(struct settings *s)
1071 char *r = NULL;
1073 if (s == NULL)
1074 return (NULL);
1076 if (s->s) {
1077 if (s->s->get)
1078 r = s->s->get(s);
1079 else
1080 warnx("get_as_string skip %s\n", s->name);
1081 } else if (s->type == XT_S_INT)
1082 r = g_strdup_printf("%d", *s->ival);
1083 else if (s->type == XT_S_STR)
1084 r = g_strdup(*s->sval);
1085 else if (s->type == XT_S_FLOAT)
1086 r = g_strdup_printf("%f", *s->fval);
1087 else
1088 r = g_strdup_printf("INVALID TYPE");
1090 return (r);
1093 void
1094 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1096 int i;
1097 char *s;
1099 for (i = 0; i < LENGTH(rs); i++) {
1100 if (rs[i].s && rs[i].s->walk)
1101 rs[i].s->walk(&rs[i], cb, cb_args);
1102 else {
1103 s = get_as_string(&rs[i]);
1104 cb(&rs[i], s, cb_args);
1105 g_free(s);
1111 set_browser_mode(struct settings *s, char *val)
1113 if (!strcmp(val, "whitelist")) {
1114 browser_mode = XT_BM_WHITELIST;
1115 allow_volatile_cookies = 0;
1116 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1117 cookies_enabled = 1;
1118 enable_cookie_whitelist = 1;
1119 read_only_cookies = 0;
1120 save_rejected_cookies = 0;
1121 session_timeout = 3600;
1122 enable_scripts = 0;
1123 enable_js_whitelist = 1;
1124 enable_localstorage = 0;
1125 } else if (!strcmp(val, "normal")) {
1126 browser_mode = XT_BM_NORMAL;
1127 allow_volatile_cookies = 0;
1128 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1129 cookies_enabled = 1;
1130 enable_cookie_whitelist = 0;
1131 read_only_cookies = 0;
1132 save_rejected_cookies = 0;
1133 session_timeout = 3600;
1134 enable_scripts = 1;
1135 enable_js_whitelist = 0;
1136 enable_localstorage = 1;
1137 } else if (!strcmp(val, "kiosk")) {
1138 browser_mode = XT_BM_KIOSK;
1139 allow_volatile_cookies = 0;
1140 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1141 cookies_enabled = 1;
1142 enable_cookie_whitelist = 0;
1143 read_only_cookies = 0;
1144 save_rejected_cookies = 0;
1145 session_timeout = 3600;
1146 enable_scripts = 1;
1147 enable_js_whitelist = 0;
1148 enable_localstorage = 1;
1149 show_tabs = 0;
1150 tabless = 1;
1151 } else
1152 return (1);
1154 return (0);
1157 char *
1158 get_browser_mode(struct settings *s)
1160 char *r = NULL;
1162 if (browser_mode == XT_BM_WHITELIST)
1163 r = g_strdup("whitelist");
1164 else if (browser_mode == XT_BM_NORMAL)
1165 r = g_strdup("normal");
1166 else if (browser_mode == XT_BM_KIOSK)
1167 r = g_strdup("kiosk");
1168 else
1169 return (NULL);
1171 return (r);
1175 set_cookie_policy(struct settings *s, char *val)
1177 if (!strcmp(val, "no3rdparty"))
1178 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1179 else if (!strcmp(val, "accept"))
1180 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1181 else if (!strcmp(val, "reject"))
1182 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1183 else
1184 return (1);
1186 return (0);
1189 char *
1190 get_cookie_policy(struct settings *s)
1192 char *r = NULL;
1194 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1195 r = g_strdup("no3rdparty");
1196 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1197 r = g_strdup("accept");
1198 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1199 r = g_strdup("reject");
1200 else
1201 return (NULL);
1203 return (r);
1206 char *
1207 get_download_dir(struct settings *s)
1209 if (download_dir[0] == '\0')
1210 return (0);
1211 return (g_strdup(download_dir));
1215 set_download_dir(struct settings *s, char *val)
1217 if (val[0] == '~')
1218 snprintf(download_dir, sizeof download_dir, "%s/%s",
1219 pwd->pw_dir, &val[1]);
1220 else
1221 strlcpy(download_dir, val, sizeof download_dir);
1223 return (0);
1227 * Session IDs.
1228 * We use these to prevent people putting xxxt:// URLs on
1229 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1231 #define XT_XTP_SES_KEY_SZ 8
1232 #define XT_XTP_SES_KEY_HEX_FMT \
1233 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1234 char *dl_session_key; /* downloads */
1235 char *hl_session_key; /* history list */
1236 char *cl_session_key; /* cookie list */
1237 char *fl_session_key; /* favorites list */
1239 char work_dir[PATH_MAX];
1240 char certs_dir[PATH_MAX];
1241 char cache_dir[PATH_MAX];
1242 char sessions_dir[PATH_MAX];
1243 char cookie_file[PATH_MAX];
1244 SoupURI *proxy_uri = NULL;
1245 SoupSession *session;
1246 SoupCookieJar *s_cookiejar;
1247 SoupCookieJar *p_cookiejar;
1248 char rc_fname[PATH_MAX];
1250 struct mime_type_list mtl;
1251 struct alias_list aliases;
1253 /* protos */
1254 struct tab *create_new_tab(char *, struct undo *, int, int);
1255 void delete_tab(struct tab *);
1256 void adjustfont_webkit(struct tab *, int);
1257 int run_script(struct tab *, char *);
1258 int download_rb_cmp(struct download *, struct download *);
1259 gboolean cmd_execute(struct tab *t, char *str);
1262 history_rb_cmp(struct history *h1, struct history *h2)
1264 return (strcmp(h1->uri, h2->uri));
1266 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1269 domain_rb_cmp(struct domain *d1, struct domain *d2)
1271 return (strcmp(d1->d, d2->d));
1273 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1275 char *
1276 get_work_dir(struct settings *s)
1278 if (work_dir[0] == '\0')
1279 return (0);
1280 return (g_strdup(work_dir));
1284 set_work_dir(struct settings *s, char *val)
1286 if (val[0] == '~')
1287 snprintf(work_dir, sizeof work_dir, "%s/%s",
1288 pwd->pw_dir, &val[1]);
1289 else
1290 strlcpy(work_dir, val, sizeof work_dir);
1292 return (0);
1296 * generate a session key to secure xtp commands.
1297 * pass in a ptr to the key in question and it will
1298 * be modified in place.
1300 void
1301 generate_xtp_session_key(char **key)
1303 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1305 /* free old key */
1306 if (*key)
1307 g_free(*key);
1309 /* make a new one */
1310 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1311 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1312 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1313 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1315 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1319 * validate a xtp session key.
1320 * return 1 if OK
1323 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1325 if (strcmp(trusted, untrusted) != 0) {
1326 show_oops(t, "%s: xtp session key mismatch possible spoof",
1327 __func__);
1328 return (0);
1331 return (1);
1335 download_rb_cmp(struct download *e1, struct download *e2)
1337 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1339 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1341 struct valid_url_types {
1342 char *type;
1343 } vut[] = {
1344 { "http://" },
1345 { "https://" },
1346 { "ftp://" },
1347 { "file://" },
1348 { XT_XTP_STR },
1352 valid_url_type(char *url)
1354 int i;
1356 for (i = 0; i < LENGTH(vut); i++)
1357 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1358 return (0);
1360 return (1);
1363 void
1364 print_cookie(char *msg, SoupCookie *c)
1366 if (c == NULL)
1367 return;
1369 if (msg)
1370 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1371 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1372 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1373 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1374 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1375 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1376 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1377 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1378 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1379 DNPRINTF(XT_D_COOKIE, "====================================\n");
1382 void
1383 walk_alias(struct settings *s,
1384 void (*cb)(struct settings *, char *, void *), void *cb_args)
1386 struct alias *a;
1387 char *str;
1389 if (s == NULL || cb == NULL) {
1390 show_oops(NULL, "walk_alias invalid parameters");
1391 return;
1394 TAILQ_FOREACH(a, &aliases, entry) {
1395 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1396 cb(s, str, cb_args);
1397 g_free(str);
1401 char *
1402 match_alias(char *url_in)
1404 struct alias *a;
1405 char *arg;
1406 char *url_out = NULL, *search, *enc_arg;
1408 search = g_strdup(url_in);
1409 arg = search;
1410 if (strsep(&arg, " \t") == NULL) {
1411 show_oops(NULL, "match_alias: NULL URL");
1412 goto done;
1415 TAILQ_FOREACH(a, &aliases, entry) {
1416 if (!strcmp(search, a->a_name))
1417 break;
1420 if (a != NULL) {
1421 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1422 a->a_name);
1423 if (arg != NULL) {
1424 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1425 url_out = g_strdup_printf(a->a_uri, enc_arg);
1426 g_free(enc_arg);
1427 } else
1428 url_out = g_strdup(a->a_uri);
1430 done:
1431 g_free(search);
1432 return (url_out);
1435 char *
1436 guess_url_type(char *url_in)
1438 struct stat sb;
1439 char *url_out = NULL, *enc_search = NULL;
1441 url_out = match_alias(url_in);
1442 if (url_out != NULL)
1443 return (url_out);
1445 if (guess_search) {
1447 * If there is no dot nor slash in the string and it isn't a
1448 * path to a local file and doesn't resolves to an IP, assume
1449 * that the user wants to search for the string.
1452 if (strchr(url_in, '.') == NULL &&
1453 strchr(url_in, '/') == NULL &&
1454 stat(url_in, &sb) != 0 &&
1455 gethostbyname(url_in) == NULL) {
1457 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1458 url_out = g_strdup_printf(search_string, enc_search);
1459 g_free(enc_search);
1460 return (url_out);
1464 /* XXX not sure about this heuristic */
1465 if (stat(url_in, &sb) == 0)
1466 url_out = g_strdup_printf("file://%s", url_in);
1467 else
1468 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1470 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1472 return (url_out);
1475 void
1476 load_uri(struct tab *t, gchar *uri)
1478 struct karg args;
1479 gchar *newuri = NULL;
1480 int i;
1482 if (uri == NULL)
1483 return;
1485 /* Strip leading spaces. */
1486 while (*uri && isspace(*uri))
1487 uri++;
1489 if (strlen(uri) == 0) {
1490 blank(t, NULL);
1491 return;
1494 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1496 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1497 for (i = 0; i < LENGTH(about_list); i++)
1498 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1499 bzero(&args, sizeof args);
1500 about_list[i].func(t, &args);
1501 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1502 FALSE);
1503 return;
1505 show_oops(t, "invalid about page");
1506 return;
1509 if (valid_url_type(uri)) {
1510 newuri = guess_url_type(uri);
1511 uri = newuri;
1514 set_status(t, (char *)uri, XT_STATUS_LOADING);
1515 webkit_web_view_load_uri(t->wv, uri);
1517 if (newuri)
1518 g_free(newuri);
1521 const gchar *
1522 get_uri(struct tab *t)
1524 const gchar *uri = NULL;
1526 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
1527 uri = webkit_web_view_get_uri(t->wv);
1528 else
1529 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, about_list[t->xtp_meaning].name);
1531 return uri;
1534 const gchar *
1535 get_title(struct tab *t)
1537 const gchar *set = NULL, *title = NULL;
1539 title = webkit_web_view_get_title(t->wv);
1540 set = title ? title : get_uri(t);
1541 if (!set || t->xtp_meaning == XT_XTP_TAB_MEANING_BL) {
1542 set = "(untitled)";
1544 return set;
1548 add_alias(struct settings *s, char *line)
1550 char *l, *alias;
1551 struct alias *a = NULL;
1553 if (s == NULL || line == NULL) {
1554 show_oops(NULL, "add_alias invalid parameters");
1555 return (1);
1558 l = line;
1559 a = g_malloc(sizeof(*a));
1561 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1562 show_oops(NULL, "add_alias: incomplete alias definition");
1563 goto bad;
1565 if (strlen(alias) == 0 || strlen(l) == 0) {
1566 show_oops(NULL, "add_alias: invalid alias definition");
1567 goto bad;
1570 a->a_name = g_strdup(alias);
1571 a->a_uri = g_strdup(l);
1573 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1575 TAILQ_INSERT_TAIL(&aliases, a, entry);
1577 return (0);
1578 bad:
1579 if (a)
1580 g_free(a);
1581 return (1);
1585 add_mime_type(struct settings *s, char *line)
1587 char *mime_type;
1588 char *l;
1589 struct mime_type *m = NULL;
1590 int downloadfirst = 0;
1592 /* XXX this could be smarter */
1594 if (line == NULL || strlen(line) == 0) {
1595 show_oops(NULL, "add_mime_type invalid parameters");
1596 return (1);
1599 l = line;
1600 if (*l == '@') {
1601 downloadfirst = 1;
1602 l++;
1604 m = g_malloc(sizeof(*m));
1606 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1607 show_oops(NULL, "add_mime_type: invalid mime_type");
1608 goto bad;
1610 if (mime_type[strlen(mime_type) - 1] == '*') {
1611 mime_type[strlen(mime_type) - 1] = '\0';
1612 m->mt_default = 1;
1613 } else
1614 m->mt_default = 0;
1616 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1617 show_oops(NULL, "add_mime_type: invalid mime_type");
1618 goto bad;
1621 m->mt_type = g_strdup(mime_type);
1622 m->mt_action = g_strdup(l);
1623 m->mt_download = downloadfirst;
1625 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1626 m->mt_type, m->mt_action, m->mt_default);
1628 TAILQ_INSERT_TAIL(&mtl, m, entry);
1630 return (0);
1631 bad:
1632 if (m)
1633 g_free(m);
1634 return (1);
1637 struct mime_type *
1638 find_mime_type(char *mime_type)
1640 struct mime_type *m, *def = NULL, *rv = NULL;
1642 TAILQ_FOREACH(m, &mtl, entry) {
1643 if (m->mt_default &&
1644 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1645 def = m;
1647 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1648 rv = m;
1649 break;
1653 if (rv == NULL)
1654 rv = def;
1656 return (rv);
1659 void
1660 walk_mime_type(struct settings *s,
1661 void (*cb)(struct settings *, char *, void *), void *cb_args)
1663 struct mime_type *m;
1664 char *str;
1666 if (s == NULL || cb == NULL) {
1667 show_oops(NULL, "walk_mime_type invalid parameters");
1668 return;
1671 TAILQ_FOREACH(m, &mtl, entry) {
1672 str = g_strdup_printf("%s%s --> %s",
1673 m->mt_type,
1674 m->mt_default ? "*" : "",
1675 m->mt_action);
1676 cb(s, str, cb_args);
1677 g_free(str);
1681 void
1682 wl_add(char *str, struct domain_list *wl, int handy)
1684 struct domain *d;
1685 int add_dot = 0;
1687 if (str == NULL || wl == NULL || strlen(str) < 2)
1688 return;
1690 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1692 /* treat *.moo.com the same as .moo.com */
1693 if (str[0] == '*' && str[1] == '.')
1694 str = &str[1];
1695 else if (str[0] == '.')
1696 str = &str[0];
1697 else
1698 add_dot = 1;
1700 d = g_malloc(sizeof *d);
1701 if (add_dot)
1702 d->d = g_strdup_printf(".%s", str);
1703 else
1704 d->d = g_strdup(str);
1705 d->handy = handy;
1707 if (RB_INSERT(domain_list, wl, d))
1708 goto unwind;
1710 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1711 return;
1712 unwind:
1713 if (d) {
1714 if (d->d)
1715 g_free(d->d);
1716 g_free(d);
1721 add_cookie_wl(struct settings *s, char *entry)
1723 wl_add(entry, &c_wl, 1);
1724 return (0);
1727 void
1728 walk_cookie_wl(struct settings *s,
1729 void (*cb)(struct settings *, char *, void *), void *cb_args)
1731 struct domain *d;
1733 if (s == NULL || cb == NULL) {
1734 show_oops(NULL, "walk_cookie_wl invalid parameters");
1735 return;
1738 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1739 cb(s, d->d, cb_args);
1742 void
1743 walk_js_wl(struct settings *s,
1744 void (*cb)(struct settings *, char *, void *), void *cb_args)
1746 struct domain *d;
1748 if (s == NULL || cb == NULL) {
1749 show_oops(NULL, "walk_js_wl invalid parameters");
1750 return;
1753 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1754 cb(s, d->d, cb_args);
1758 add_js_wl(struct settings *s, char *entry)
1760 wl_add(entry, &js_wl, 1 /* persistent */);
1761 return (0);
1764 struct domain *
1765 wl_find(const gchar *search, struct domain_list *wl)
1767 int i;
1768 struct domain *d = NULL, dfind;
1769 gchar *s = NULL;
1771 if (search == NULL || wl == NULL)
1772 return (NULL);
1773 if (strlen(search) < 2)
1774 return (NULL);
1776 if (search[0] != '.')
1777 s = g_strdup_printf(".%s", search);
1778 else
1779 s = g_strdup(search);
1781 for (i = strlen(s) - 1; i >= 0; i--) {
1782 if (s[i] == '.') {
1783 dfind.d = &s[i];
1784 d = RB_FIND(domain_list, wl, &dfind);
1785 if (d)
1786 goto done;
1790 done:
1791 if (s)
1792 g_free(s);
1794 return (d);
1797 struct domain *
1798 wl_find_uri(const gchar *s, struct domain_list *wl)
1800 int i;
1801 char *ss;
1802 struct domain *r;
1804 if (s == NULL || wl == NULL)
1805 return (NULL);
1807 if (!strncmp(s, "http://", strlen("http://")))
1808 s = &s[strlen("http://")];
1809 else if (!strncmp(s, "https://", strlen("https://")))
1810 s = &s[strlen("https://")];
1812 if (strlen(s) < 2)
1813 return (NULL);
1815 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1816 /* chop string at first slash */
1817 if (s[i] == '/' || s[i] == '\0') {
1818 ss = g_strdup(s);
1819 ss[i] = '\0';
1820 r = wl_find(ss, wl);
1821 g_free(ss);
1822 return (r);
1825 return (NULL);
1828 char *
1829 get_toplevel_domain(char *domain)
1831 char *s;
1832 int found = 0;
1834 if (domain == NULL)
1835 return (NULL);
1836 if (strlen(domain) < 2)
1837 return (NULL);
1839 s = &domain[strlen(domain) - 1];
1840 while (s != domain) {
1841 if (*s == '.') {
1842 found++;
1843 if (found == 2)
1844 return (s);
1846 s--;
1849 if (found)
1850 return (domain);
1852 return (NULL);
1856 settings_add(char *var, char *val)
1858 int i, rv, *p;
1859 gfloat *f;
1860 char **s;
1862 /* get settings */
1863 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1864 if (strcmp(var, rs[i].name))
1865 continue;
1867 if (rs[i].s) {
1868 if (rs[i].s->set(&rs[i], val))
1869 errx(1, "invalid value for %s: %s", var, val);
1870 rv = 1;
1871 break;
1872 } else
1873 switch (rs[i].type) {
1874 case XT_S_INT:
1875 p = rs[i].ival;
1876 *p = atoi(val);
1877 rv = 1;
1878 break;
1879 case XT_S_STR:
1880 s = rs[i].sval;
1881 if (s == NULL)
1882 errx(1, "invalid sval for %s",
1883 rs[i].name);
1884 if (*s)
1885 g_free(*s);
1886 *s = g_strdup(val);
1887 rv = 1;
1888 break;
1889 case XT_S_FLOAT:
1890 f = rs[i].fval;
1891 *f = atof(val);
1892 rv = 1;
1893 break;
1894 case XT_S_INVALID:
1895 default:
1896 errx(1, "invalid type for %s", var);
1898 break;
1900 return (rv);
1903 #define WS "\n= \t"
1904 void
1905 config_parse(char *filename, int runtime)
1907 FILE *config, *f;
1908 char *line, *cp, *var, *val;
1909 size_t len, lineno = 0;
1910 int handled;
1911 char file[PATH_MAX];
1912 struct stat sb;
1914 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1916 if (filename == NULL)
1917 return;
1919 if (runtime && runtime_settings[0] != '\0') {
1920 snprintf(file, sizeof file, "%s/%s",
1921 work_dir, runtime_settings);
1922 if (stat(file, &sb)) {
1923 warnx("runtime file doesn't exist, creating it");
1924 if ((f = fopen(file, "w")) == NULL)
1925 err(1, "runtime");
1926 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1927 fclose(f);
1929 } else
1930 strlcpy(file, filename, sizeof file);
1932 if ((config = fopen(file, "r")) == NULL) {
1933 warn("config_parse: cannot open %s", filename);
1934 return;
1937 for (;;) {
1938 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1939 if (feof(config) || ferror(config))
1940 break;
1942 cp = line;
1943 cp += (long)strspn(cp, WS);
1944 if (cp[0] == '\0') {
1945 /* empty line */
1946 free(line);
1947 continue;
1950 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1951 errx(1, "invalid config file entry: %s", line);
1953 cp += (long)strspn(cp, WS);
1955 if ((val = strsep(&cp, "\0")) == NULL)
1956 break;
1958 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
1959 handled = settings_add(var, val);
1960 if (handled == 0)
1961 errx(1, "invalid conf file entry: %s=%s", var, val);
1963 free(line);
1966 fclose(config);
1969 char *
1970 js_ref_to_string(JSContextRef context, JSValueRef ref)
1972 char *s = NULL;
1973 size_t l;
1974 JSStringRef jsref;
1976 jsref = JSValueToStringCopy(context, ref, NULL);
1977 if (jsref == NULL)
1978 return (NULL);
1980 l = JSStringGetMaximumUTF8CStringSize(jsref);
1981 s = g_malloc(l);
1982 if (s)
1983 JSStringGetUTF8CString(jsref, s, l);
1984 JSStringRelease(jsref);
1986 return (s);
1989 void
1990 disable_hints(struct tab *t)
1992 bzero(t->hint_buf, sizeof t->hint_buf);
1993 bzero(t->hint_num, sizeof t->hint_num);
1994 run_script(t, "vimprobable_clear()");
1995 t->hints_on = 0;
1996 t->hint_mode = XT_HINT_NONE;
1999 void
2000 enable_hints(struct tab *t)
2002 bzero(t->hint_buf, sizeof t->hint_buf);
2003 run_script(t, "vimprobable_show_hints()");
2004 t->hints_on = 1;
2005 t->hint_mode = XT_HINT_NONE;
2008 #define XT_JS_OPEN ("open;")
2009 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2010 #define XT_JS_FIRE ("fire;")
2011 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2012 #define XT_JS_FOUND ("found;")
2013 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2016 run_script(struct tab *t, char *s)
2018 JSGlobalContextRef ctx;
2019 WebKitWebFrame *frame;
2020 JSStringRef str;
2021 JSValueRef val, exception;
2022 char *es, buf[128];
2024 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2025 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2027 frame = webkit_web_view_get_main_frame(t->wv);
2028 ctx = webkit_web_frame_get_global_context(frame);
2030 str = JSStringCreateWithUTF8CString(s);
2031 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2032 NULL, 0, &exception);
2033 JSStringRelease(str);
2035 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2036 if (val == NULL) {
2037 es = js_ref_to_string(ctx, exception);
2038 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2039 g_free(es);
2040 return (1);
2041 } else {
2042 es = js_ref_to_string(ctx, val);
2043 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2045 /* handle return value right here */
2046 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2047 disable_hints(t);
2048 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
2051 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2052 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2053 &es[XT_JS_FIRE_LEN]);
2054 run_script(t, buf);
2055 disable_hints(t);
2058 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2059 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2060 disable_hints(t);
2063 g_free(es);
2066 return (0);
2070 hint(struct tab *t, struct karg *args)
2073 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2075 if (t->hints_on == 0)
2076 enable_hints(t);
2077 else
2078 disable_hints(t);
2080 return (0);
2083 void
2084 apply_style(struct tab *t)
2086 g_object_set(G_OBJECT(t->settings),
2087 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2091 userstyle(struct tab *t, struct karg *args)
2093 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2095 if (t->styled) {
2096 t->styled = 0;
2097 g_object_set(G_OBJECT(t->settings),
2098 "user-stylesheet-uri", NULL, (char *)NULL);
2099 } else {
2100 t->styled = 1;
2101 apply_style(t);
2103 return (0);
2107 * Doesn't work fully, due to the following bug:
2108 * https://bugs.webkit.org/show_bug.cgi?id=51747
2111 restore_global_history(void)
2113 char file[PATH_MAX];
2114 FILE *f;
2115 struct history *h;
2116 gchar *uri;
2117 gchar *title;
2118 const char delim[3] = {'\\', '\\', '\0'};
2120 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2122 if ((f = fopen(file, "r")) == NULL) {
2123 warnx("%s: fopen", __func__);
2124 return (1);
2127 for (;;) {
2128 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2129 if (feof(f) || ferror(f))
2130 break;
2132 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2133 if (feof(f) || ferror(f)) {
2134 free(uri);
2135 warnx("%s: broken history file\n", __func__);
2136 return (1);
2139 if (uri && strlen(uri) && title && strlen(title)) {
2140 webkit_web_history_item_new_with_data(uri, title);
2141 h = g_malloc(sizeof(struct history));
2142 h->uri = g_strdup(uri);
2143 h->title = g_strdup(title);
2144 RB_INSERT(history_list, &hl, h);
2145 completion_add_uri(h->uri);
2146 } else {
2147 warnx("%s: failed to restore history\n", __func__);
2148 free(uri);
2149 free(title);
2150 return (1);
2153 free(uri);
2154 free(title);
2155 uri = NULL;
2156 title = NULL;
2159 return (0);
2163 save_global_history_to_disk(struct tab *t)
2165 char file[PATH_MAX];
2166 FILE *f;
2167 struct history *h;
2169 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2171 if ((f = fopen(file, "w")) == NULL) {
2172 show_oops(t, "%s: global history file: %s",
2173 __func__, strerror(errno));
2174 return (1);
2177 RB_FOREACH_REVERSE(h, history_list, &hl) {
2178 if (h->uri && h->title)
2179 fprintf(f, "%s\n%s\n", h->uri, h->title);
2182 fclose(f);
2184 return (0);
2188 quit(struct tab *t, struct karg *args)
2190 if (save_global_history)
2191 save_global_history_to_disk(t);
2193 gtk_main_quit();
2195 return (1);
2199 open_tabs(struct tab *t, struct karg *a)
2201 char file[PATH_MAX];
2202 FILE *f = NULL;
2203 char *uri = NULL;
2204 int rv = 1;
2205 struct tab *ti, *tt;
2207 if (a == NULL)
2208 goto done;
2210 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2211 if ((f = fopen(file, "r")) == NULL)
2212 goto done;
2214 ti = TAILQ_LAST(&tabs, tab_list);
2216 for (;;) {
2217 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2218 if (feof(f) || ferror(f))
2219 break;
2221 /* retrieve session name */
2222 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2223 strlcpy(named_session,
2224 &uri[strlen(XT_SAVE_SESSION_ID)],
2225 sizeof named_session);
2226 continue;
2229 if (uri && strlen(uri))
2230 create_new_tab(uri, NULL, 1, -1);
2232 free(uri);
2233 uri = NULL;
2236 /* close open tabs */
2237 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2238 for (;;) {
2239 tt = TAILQ_FIRST(&tabs);
2240 if (tt != ti) {
2241 delete_tab(tt);
2242 continue;
2244 delete_tab(tt);
2245 break;
2247 recalc_tabs();
2250 rv = 0;
2251 done:
2252 if (f)
2253 fclose(f);
2255 return (rv);
2259 restore_saved_tabs(void)
2261 char file[PATH_MAX];
2262 int unlink_file = 0;
2263 struct stat sb;
2264 struct karg a;
2265 int rv = 0;
2267 snprintf(file, sizeof file, "%s/%s",
2268 sessions_dir, XT_RESTART_TABS_FILE);
2269 if (stat(file, &sb) == -1)
2270 a.s = XT_SAVED_TABS_FILE;
2271 else {
2272 unlink_file = 1;
2273 a.s = XT_RESTART_TABS_FILE;
2276 a.i = XT_SES_DONOTHING;
2277 rv = open_tabs(NULL, &a);
2279 if (unlink_file)
2280 unlink(file);
2282 return (rv);
2286 save_tabs(struct tab *t, struct karg *a)
2288 char file[PATH_MAX];
2289 FILE *f;
2290 int num_tabs = 0, i;
2291 struct tab **stabs = NULL;
2293 if (a == NULL)
2294 return (1);
2295 if (a->s == NULL)
2296 snprintf(file, sizeof file, "%s/%s",
2297 sessions_dir, named_session);
2298 else
2299 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2301 if ((f = fopen(file, "w")) == NULL) {
2302 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2303 return (1);
2306 /* save session name */
2307 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2309 /* Save tabs, in the order they are arranged in the notebook. */
2310 num_tabs = sort_tabs_by_page_num(&stabs);
2312 for (i = 0; i < num_tabs; i++)
2313 if (stabs[i] && get_uri(stabs[i]) != NULL)
2314 fprintf(f, "%s\n", get_uri(stabs[i]));
2316 g_free(stabs);
2318 /* try and make sure this gets to disk NOW. XXX Backup first? */
2319 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2320 show_oops(t, "May not have managed to save session: %s",
2321 strerror(errno));
2324 fclose(f);
2326 return (0);
2330 save_tabs_and_quit(struct tab *t, struct karg *args)
2332 struct karg a;
2334 a.s = NULL;
2335 save_tabs(t, &a);
2336 quit(t, NULL);
2338 return (1);
2342 yank_uri(struct tab *t, struct karg *args)
2344 const gchar *uri;
2345 GtkClipboard *clipboard;
2347 if ((uri = get_uri(t)) == NULL)
2348 return (1);
2350 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2351 gtk_clipboard_set_text(clipboard, uri, -1);
2353 return (0);
2357 paste_uri(struct tab *t, struct karg *args)
2359 GtkClipboard *clipboard;
2360 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2361 gint len;
2362 gchar *p = NULL, *uri;
2364 /* try primary clipboard first */
2365 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2366 p = gtk_clipboard_wait_for_text(clipboard);
2368 /* if it failed get whatever text is in cut_buffer0 */
2369 if (p == NULL)
2370 if (gdk_property_get(gdk_get_default_root_window(),
2371 atom,
2372 gdk_atom_intern("STRING", FALSE),
2374 65536 /* picked out of my butt */,
2375 FALSE,
2376 NULL,
2377 NULL,
2378 &len,
2379 (guchar **)&p)) {
2380 /* yes sir, we need to NUL the string */
2381 p[len] = '\0';
2384 if (p) {
2385 uri = p;
2386 while (*uri && isspace(*uri))
2387 uri++;
2388 if (strlen(uri) == 0) {
2389 show_oops(t, "empty paste buffer");
2390 goto done;
2392 if (guess_search == 0 && valid_url_type(uri)) {
2393 /* we can be clever and paste this in search box */
2394 show_oops(t, "not a valid URL");
2395 goto done;
2398 if (args->i == XT_PASTE_CURRENT_TAB)
2399 load_uri(t, uri);
2400 else if (args->i == XT_PASTE_NEW_TAB)
2401 create_new_tab(uri, NULL, 1, -1);
2404 done:
2405 if (p)
2406 g_free(p);
2408 return (0);
2411 char *
2412 find_domain(const gchar *s, int add_dot)
2414 int i;
2415 char *r = NULL, *ss = NULL;
2417 if (s == NULL)
2418 return (NULL);
2420 if (!strncmp(s, "http://", strlen("http://")))
2421 s = &s[strlen("http://")];
2422 else if (!strncmp(s, "https://", strlen("https://")))
2423 s = &s[strlen("https://")];
2425 if (strlen(s) < 2)
2426 return (NULL);
2428 ss = g_strdup(s);
2429 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2430 /* chop string at first slash */
2431 if (ss[i] == '/' || ss[i] == '\0') {
2432 ss[i] = '\0';
2433 if (add_dot)
2434 r = g_strdup_printf(".%s", ss);
2435 else
2436 r = g_strdup(ss);
2437 break;
2439 g_free(ss);
2441 return (r);
2445 toggle_cwl(struct tab *t, struct karg *args)
2447 struct domain *d;
2448 const gchar *uri;
2449 char *dom = NULL, *dom_toggle = NULL;
2450 int es;
2452 if (args == NULL)
2453 return (1);
2455 uri = get_uri(t);
2456 dom = find_domain(uri, 1);
2457 d = wl_find(dom, &c_wl);
2459 if (d == NULL)
2460 es = 0;
2461 else
2462 es = 1;
2464 if (args->i & XT_WL_TOGGLE)
2465 es = !es;
2466 else if ((args->i & XT_WL_ENABLE) && es != 1)
2467 es = 1;
2468 else if ((args->i & XT_WL_DISABLE) && es != 0)
2469 es = 0;
2471 if (args->i & XT_WL_TOPLEVEL)
2472 dom_toggle = get_toplevel_domain(dom);
2473 else
2474 dom_toggle = dom;
2476 if (es)
2477 /* enable cookies for domain */
2478 wl_add(dom_toggle, &c_wl, 0);
2479 else
2480 /* disable cookies for domain */
2481 RB_REMOVE(domain_list, &c_wl, d);
2483 if (args->i & XT_WL_RELOAD)
2484 webkit_web_view_reload(t->wv);
2486 g_free(dom);
2487 return (0);
2491 toggle_js(struct tab *t, struct karg *args)
2493 int es;
2494 const gchar *uri;
2495 struct domain *d;
2496 char *dom = NULL, *dom_toggle = NULL;
2498 if (args == NULL)
2499 return (1);
2501 g_object_get(G_OBJECT(t->settings),
2502 "enable-scripts", &es, (char *)NULL);
2503 if (args->i & XT_WL_TOGGLE)
2504 es = !es;
2505 else if ((args->i & XT_WL_ENABLE) && es != 1)
2506 es = 1;
2507 else if ((args->i & XT_WL_DISABLE) && es != 0)
2508 es = 0;
2509 else
2510 return (1);
2512 uri = get_uri(t);
2513 dom = find_domain(uri, 1);
2515 if (uri == NULL || dom == NULL) {
2516 show_oops(t, "Can't toggle domain in JavaScript white list");
2517 goto done;
2520 if (args->i & XT_WL_TOPLEVEL)
2521 dom_toggle = get_toplevel_domain(dom);
2522 else
2523 dom_toggle = dom;
2525 if (es) {
2526 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2527 wl_add(dom_toggle, &js_wl, 0 /* session */);
2528 } else {
2529 d = wl_find(dom_toggle, &js_wl);
2530 if (d)
2531 RB_REMOVE(domain_list, &js_wl, d);
2532 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2534 g_object_set(G_OBJECT(t->settings),
2535 "enable-scripts", es, (char *)NULL);
2536 g_object_set(G_OBJECT(t->settings),
2537 "javascript-can-open-windows-automatically", es, (char *)NULL);
2538 webkit_web_view_set_settings(t->wv, t->settings);
2540 if (args->i & XT_WL_RELOAD)
2541 webkit_web_view_reload(t->wv);
2542 done:
2543 if (dom)
2544 g_free(dom);
2545 return (0);
2548 void
2549 js_toggle_cb(GtkWidget *w, struct tab *t)
2551 struct karg a;
2553 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2554 toggle_cwl(t, &a);
2556 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2557 toggle_js(t, &a);
2561 toggle_src(struct tab *t, struct karg *args)
2563 gboolean mode;
2565 if (t == NULL)
2566 return (0);
2568 mode = webkit_web_view_get_view_source_mode(t->wv);
2569 webkit_web_view_set_view_source_mode(t->wv, !mode);
2570 webkit_web_view_reload(t->wv);
2572 return (0);
2575 void
2576 focus_webview(struct tab *t)
2578 if (t == NULL)
2579 return;
2581 /* only grab focus if we are visible */
2582 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2583 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2587 focus(struct tab *t, struct karg *args)
2589 if (t == NULL || args == NULL)
2590 return (1);
2592 if (show_url == 0)
2593 return (0);
2595 if (args->i == XT_FOCUS_URI)
2596 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2597 else if (args->i == XT_FOCUS_SEARCH)
2598 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2600 return (0);
2604 stats(struct tab *t, struct karg *args)
2606 char *page, *body, *s, line[64 * 1024];
2607 uint64_t line_count = 0;
2608 FILE *r_cookie_f;
2610 if (t == NULL)
2611 show_oops(NULL, "stats invalid parameters");
2613 line[0] = '\0';
2614 if (save_rejected_cookies) {
2615 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2616 for (;;) {
2617 s = fgets(line, sizeof line, r_cookie_f);
2618 if (s == NULL || feof(r_cookie_f) ||
2619 ferror(r_cookie_f))
2620 break;
2621 line_count++;
2623 fclose(r_cookie_f);
2624 snprintf(line, sizeof line,
2625 "<br/>Cookies blocked(*) total: %llu", line_count);
2626 } else
2627 show_oops(t, "Can't open blocked cookies file: %s",
2628 strerror(errno));
2631 body = g_strdup_printf(
2632 "Cookies blocked(*) this session: %llu"
2633 "%s"
2634 "<p><small><b>*</b> results vary based on settings</small></p>",
2635 blocked_cookies,
2636 line);
2638 page = get_html_page("Statistics", body, "", 0);
2639 g_free(body);
2641 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2642 g_free(page);
2644 return (0);
2648 marco(struct tab *t, struct karg *args)
2650 char *page, line[64 * 1024];
2651 int len;
2653 if (t == NULL)
2654 show_oops(NULL, "marco invalid parameters");
2656 line[0] = '\0';
2657 snprintf(line, sizeof line, "%s", marco_message(&len));
2659 page = get_html_page("Marco Sez...", line, "", 0);
2661 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2662 g_free(page);
2664 return (0);
2668 blank(struct tab *t, struct karg *args)
2670 if (t == NULL)
2671 show_oops(NULL, "blank invalid parameters");
2673 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2675 return (0);
2678 about(struct tab *t, struct karg *args)
2680 char *page, *body;
2682 if (t == NULL)
2683 show_oops(NULL, "about invalid parameters");
2685 body = g_strdup_printf("<b>Version: %s</b><p>"
2686 "Authors:"
2687 "<ul>"
2688 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2689 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2690 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2691 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2692 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2693 "</ul>"
2694 "Copyrights and licenses can be found on the XXXterm "
2695 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2696 version
2699 page = get_html_page("About", body, "", 0);
2700 g_free(body);
2702 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2703 g_free(page);
2705 return (0);
2709 help(struct tab *t, struct karg *args)
2711 char *page, *head, *body;
2713 if (t == NULL)
2714 show_oops(NULL, "help invalid parameters");
2716 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2717 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2718 "</head>\n";
2719 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2720 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2721 "cgi-bin/man-cgi?xxxterm</a>";
2723 page = get_html_page("XXXterm", body, head, FALSE);
2725 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2726 g_free(page);
2728 return (0);
2732 * update all favorite tabs apart from one. Pass NULL if
2733 * you want to update all.
2735 void
2736 update_favorite_tabs(struct tab *apart_from)
2738 struct tab *t;
2739 if (!updating_fl_tabs) {
2740 updating_fl_tabs = 1; /* stop infinite recursion */
2741 TAILQ_FOREACH(t, &tabs, entry)
2742 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2743 && (t != apart_from))
2744 xtp_page_fl(t, NULL);
2745 updating_fl_tabs = 0;
2749 /* show a list of favorites (bookmarks) */
2751 xtp_page_fl(struct tab *t, struct karg *args)
2753 char file[PATH_MAX];
2754 FILE *f;
2755 char *uri = NULL, *title = NULL;
2756 size_t len, lineno = 0;
2757 int i, failed = 0;
2758 char *body, *tmp, *page = NULL;
2759 const char delim[3] = {'\\', '\\', '\0'};
2761 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2763 if (t == NULL)
2764 warn("%s: bad param", __func__);
2766 /* new session key */
2767 if (!updating_fl_tabs)
2768 generate_xtp_session_key(&fl_session_key);
2770 /* open favorites */
2771 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2772 if ((f = fopen(file, "r")) == NULL) {
2773 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2774 return (1);
2777 /* body */
2778 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2779 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2780 "<th style='width: 40px'>Rm</th></tr>\n");
2782 for (i = 1;;) {
2783 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2784 if (feof(f) || ferror(f))
2785 break;
2786 if (len == 0) {
2787 free(title);
2788 title = NULL;
2789 continue;
2792 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2793 if (feof(f) || ferror(f)) {
2794 show_oops(t, "favorites file corrupt");
2795 failed = 1;
2796 break;
2799 tmp = body;
2800 body = g_strdup_printf("%s<tr>"
2801 "<td>%d</td>"
2802 "<td><a href='%s'>%s</a></td>"
2803 "<td style='text-align: center'>"
2804 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2805 "</tr>\n",
2806 body, i, uri, title,
2807 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2809 g_free(tmp);
2811 free(uri);
2812 uri = NULL;
2813 free(title);
2814 title = NULL;
2815 i++;
2817 fclose(f);
2819 /* if none, say so */
2820 if (i == 1) {
2821 tmp = body;
2822 body = g_strdup_printf("%s<tr>"
2823 "<td colspan='3' style='text-align: center'>"
2824 "No favorites - To add one use the 'favadd' command."
2825 "</td></tr>", body);
2826 g_free(tmp);
2829 tmp = body;
2830 body = g_strdup_printf("%s</table>", body);
2831 g_free(tmp);
2833 if (uri)
2834 free(uri);
2835 if (title)
2836 free(title);
2838 /* render */
2839 if (!failed) {
2840 page = get_html_page("Favorites", body, "", 1);
2841 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2842 g_free(page);
2845 update_favorite_tabs(t);
2847 if (body)
2848 g_free(body);
2850 return (failed);
2853 void
2854 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2855 size_t cert_count, char *title)
2857 gnutls_datum_t cinfo;
2858 char *tmp, *body;
2859 int i;
2861 body = g_strdup("");
2863 for (i = 0; i < cert_count; i++) {
2864 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2865 &cinfo))
2866 return;
2868 tmp = body;
2869 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2870 body, i, cinfo.data);
2871 gnutls_free(cinfo.data);
2872 g_free(tmp);
2875 tmp = get_html_page(title, body, "", 0);
2876 g_free(body);
2878 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2879 g_free(tmp);
2883 ca_cmd(struct tab *t, struct karg *args)
2885 FILE *f = NULL;
2886 int rv = 1, certs = 0, certs_read;
2887 struct stat sb;
2888 gnutls_datum_t dt;
2889 gnutls_x509_crt_t *c = NULL;
2890 char *certs_buf = NULL, *s;
2892 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2893 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2894 return (1);
2897 if (fstat(fileno(f), &sb) == -1) {
2898 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2899 goto done;
2902 certs_buf = g_malloc(sb.st_size + 1);
2903 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2904 show_oops(t, "Can't read CA file: %s", strerror(errno));
2905 goto done;
2907 certs_buf[sb.st_size] = '\0';
2909 s = certs_buf;
2910 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2911 certs++;
2912 s += strlen("BEGIN CERTIFICATE");
2915 bzero(&dt, sizeof dt);
2916 dt.data = (unsigned char *)certs_buf;
2917 dt.size = sb.st_size;
2918 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2919 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
2920 GNUTLS_X509_FMT_PEM, 0);
2921 if (certs_read <= 0) {
2922 show_oops(t, "No cert(s) available");
2923 goto done;
2925 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2926 done:
2927 if (c)
2928 g_free(c);
2929 if (certs_buf)
2930 g_free(certs_buf);
2931 if (f)
2932 fclose(f);
2934 return (rv);
2938 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2940 SoupURI *su = NULL;
2941 struct addrinfo hints, *res = NULL, *ai;
2942 int s = -1, on;
2943 char port[8];
2945 if (uri && !g_str_has_prefix(uri, "https://"))
2946 goto done;
2948 su = soup_uri_new(uri);
2949 if (su == NULL)
2950 goto done;
2951 if (!SOUP_URI_VALID_FOR_HTTP(su))
2952 goto done;
2954 snprintf(port, sizeof port, "%d", su->port);
2955 bzero(&hints, sizeof(struct addrinfo));
2956 hints.ai_flags = AI_CANONNAME;
2957 hints.ai_family = AF_UNSPEC;
2958 hints.ai_socktype = SOCK_STREAM;
2960 if (getaddrinfo(su->host, port, &hints, &res))
2961 goto done;
2963 for (ai = res; ai; ai = ai->ai_next) {
2964 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2965 continue;
2967 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2968 if (s < 0)
2969 goto done;
2970 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2971 sizeof(on)) == -1)
2972 goto done;
2974 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2975 goto done;
2978 if (domain)
2979 strlcpy(domain, su->host, domain_sz);
2980 done:
2981 if (su)
2982 soup_uri_free(su);
2983 if (res)
2984 freeaddrinfo(res);
2986 return (s);
2990 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2992 if (gsession)
2993 gnutls_deinit(gsession);
2994 if (xcred)
2995 gnutls_certificate_free_credentials(xcred);
2997 return (0);
3001 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3002 gnutls_certificate_credentials_t *xc)
3004 gnutls_certificate_credentials_t xcred;
3005 gnutls_session_t gsession;
3006 int rv = 1;
3008 if (gs == NULL || xc == NULL)
3009 goto done;
3011 *gs = NULL;
3012 *xc = NULL;
3014 gnutls_certificate_allocate_credentials(&xcred);
3015 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3016 GNUTLS_X509_FMT_PEM);
3017 gnutls_init(&gsession, GNUTLS_CLIENT);
3018 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3019 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3020 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3021 if ((rv = gnutls_handshake(gsession)) < 0) {
3022 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3024 gnutls_error_is_fatal(rv),
3025 gnutls_strerror_name(rv));
3026 stop_tls(gsession, xcred);
3027 goto done;
3030 gnutls_credentials_type_t cred;
3031 cred = gnutls_auth_get_type(gsession);
3032 if (cred != GNUTLS_CRD_CERTIFICATE) {
3033 stop_tls(gsession, xcred);
3034 goto done;
3037 *gs = gsession;
3038 *xc = xcred;
3039 rv = 0;
3040 done:
3041 return (rv);
3045 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3046 size_t *cert_count)
3048 unsigned int len;
3049 const gnutls_datum_t *cl;
3050 gnutls_x509_crt_t *all_certs;
3051 int i, rv = 1;
3053 if (certs == NULL || cert_count == NULL)
3054 goto done;
3055 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3056 goto done;
3057 cl = gnutls_certificate_get_peers(gsession, &len);
3058 if (len == 0)
3059 goto done;
3061 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3062 for (i = 0; i < len; i++) {
3063 gnutls_x509_crt_init(&all_certs[i]);
3064 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3065 GNUTLS_X509_FMT_PEM < 0)) {
3066 g_free(all_certs);
3067 goto done;
3071 *certs = all_certs;
3072 *cert_count = len;
3073 rv = 0;
3074 done:
3075 return (rv);
3078 void
3079 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3081 int i;
3083 for (i = 0; i < cert_count; i++)
3084 gnutls_x509_crt_deinit(certs[i]);
3085 g_free(certs);
3088 void
3089 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3090 size_t cert_count, char *domain)
3092 size_t cert_buf_sz;
3093 char cert_buf[64 * 1024], file[PATH_MAX];
3094 int i;
3095 FILE *f;
3096 GdkColor color;
3098 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3099 return;
3101 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3102 if ((f = fopen(file, "w")) == NULL) {
3103 show_oops(t, "Can't create cert file %s %s",
3104 file, strerror(errno));
3105 return;
3108 for (i = 0; i < cert_count; i++) {
3109 cert_buf_sz = sizeof cert_buf;
3110 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3111 cert_buf, &cert_buf_sz)) {
3112 show_oops(t, "gnutls_x509_crt_export failed");
3113 goto done;
3115 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3116 show_oops(t, "Can't write certs: %s", strerror(errno));
3117 goto done;
3121 /* not the best spot but oh well */
3122 gdk_color_parse("lightblue", &color);
3123 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3124 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3125 gdk_color_parse(XT_COLOR_BLACK, &color);
3126 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3127 done:
3128 fclose(f);
3132 load_compare_cert(struct tab *t, struct karg *args)
3134 const gchar *uri;
3135 char domain[8182], file[PATH_MAX];
3136 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3137 int s = -1, rv = 1, i;
3138 size_t cert_count;
3139 FILE *f = NULL;
3140 size_t cert_buf_sz;
3141 gnutls_session_t gsession;
3142 gnutls_x509_crt_t *certs;
3143 gnutls_certificate_credentials_t xcred;
3145 if (t == NULL)
3146 return (1);
3148 if ((uri = get_uri(t)) == NULL)
3149 return (1);
3151 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3152 return (1);
3154 /* go ssl/tls */
3155 if (start_tls(t, s, &gsession, &xcred)) {
3156 show_oops(t, "Start TLS failed");
3157 goto done;
3160 /* get certs */
3161 if (get_connection_certs(gsession, &certs, &cert_count)) {
3162 show_oops(t, "Can't get connection certificates");
3163 goto done;
3166 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3167 if ((f = fopen(file, "r")) == NULL)
3168 goto freeit;
3170 for (i = 0; i < cert_count; i++) {
3171 cert_buf_sz = sizeof cert_buf;
3172 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3173 cert_buf, &cert_buf_sz)) {
3174 goto freeit;
3176 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3177 rv = -1; /* critical */
3178 goto freeit;
3180 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3181 rv = -1; /* critical */
3182 goto freeit;
3186 rv = 0;
3187 freeit:
3188 if (f)
3189 fclose(f);
3190 free_connection_certs(certs, cert_count);
3191 done:
3192 /* we close the socket first for speed */
3193 if (s != -1)
3194 close(s);
3195 stop_tls(gsession, xcred);
3197 return (rv);
3201 cert_cmd(struct tab *t, struct karg *args)
3203 const gchar *uri;
3204 char domain[8182];
3205 int s = -1;
3206 size_t cert_count;
3207 gnutls_session_t gsession;
3208 gnutls_x509_crt_t *certs;
3209 gnutls_certificate_credentials_t xcred;
3211 if (t == NULL)
3212 return (1);
3214 if (ssl_ca_file == NULL) {
3215 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3216 return (1);
3219 if ((uri = get_uri(t)) == NULL) {
3220 show_oops(t, "Invalid URI");
3221 return (1);
3224 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3225 show_oops(t, "Invalid certificate URI: %s", uri);
3226 return (1);
3229 /* go ssl/tls */
3230 if (start_tls(t, s, &gsession, &xcred)) {
3231 show_oops(t, "Start TLS failed");
3232 goto done;
3235 /* get certs */
3236 if (get_connection_certs(gsession, &certs, &cert_count)) {
3237 show_oops(t, "get_connection_certs failed");
3238 goto done;
3241 if (args->i & XT_SHOW)
3242 show_certs(t, certs, cert_count, "Certificate Chain");
3243 else if (args->i & XT_SAVE)
3244 save_certs(t, certs, cert_count, domain);
3246 free_connection_certs(certs, cert_count);
3247 done:
3248 /* we close the socket first for speed */
3249 if (s != -1)
3250 close(s);
3251 stop_tls(gsession, xcred);
3253 return (0);
3257 remove_cookie(int index)
3259 int i, rv = 1;
3260 GSList *cf;
3261 SoupCookie *c;
3263 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3265 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3267 for (i = 1; cf; cf = cf->next, i++) {
3268 if (i != index)
3269 continue;
3270 c = cf->data;
3271 print_cookie("remove cookie", c);
3272 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3273 rv = 0;
3274 break;
3277 soup_cookies_free(cf);
3279 return (rv);
3283 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3285 struct domain *d;
3286 char *tmp, *body;
3288 body = g_strdup("");
3290 /* p list */
3291 if (args->i & XT_WL_PERSISTENT) {
3292 tmp = body;
3293 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3294 g_free(tmp);
3295 RB_FOREACH(d, domain_list, wl) {
3296 if (d->handy == 0)
3297 continue;
3298 tmp = body;
3299 body = g_strdup_printf("%s%s<br/>", body, d->d);
3300 g_free(tmp);
3304 /* s list */
3305 if (args->i & XT_WL_SESSION) {
3306 tmp = body;
3307 body = g_strdup_printf("%s<h2>Session</h2>", body);
3308 g_free(tmp);
3309 RB_FOREACH(d, domain_list, wl) {
3310 if (d->handy == 1)
3311 continue;
3312 tmp = body;
3313 body = g_strdup_printf("%s%s<br/>", body, d->d);
3314 g_free(tmp);
3318 tmp = get_html_page(title, body, "", 0);
3319 g_free(body);
3320 if (wl == &js_wl)
3321 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3322 else
3323 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3324 g_free(tmp);
3325 return (0);
3329 wl_save(struct tab *t, struct karg *args, int js)
3331 char file[PATH_MAX];
3332 FILE *f;
3333 char *line = NULL, *lt = NULL;
3334 size_t linelen;
3335 const gchar *uri;
3336 char *dom = NULL, *dom_save = NULL;
3337 struct karg a;
3338 struct domain *d;
3339 GSList *cf;
3340 SoupCookie *ci, *c;
3342 if (t == NULL || args == NULL)
3343 return (1);
3345 if (runtime_settings[0] == '\0')
3346 return (1);
3348 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3349 if ((f = fopen(file, "r+")) == NULL)
3350 return (1);
3352 uri = get_uri(t);
3353 dom = find_domain(uri, 1);
3354 if (uri == NULL || dom == NULL) {
3355 show_oops(t, "Can't add domain to %s white list",
3356 js ? "JavaScript" : "cookie");
3357 goto done;
3360 if (args->i & XT_WL_TOPLEVEL) {
3361 /* save domain */
3362 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3363 show_oops(t, "invalid domain: %s", dom);
3364 goto done;
3366 } else if (args->i & XT_WL_FQDN) {
3367 /* save fqdn */
3368 dom_save = dom;
3369 } else
3370 goto done;
3372 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3374 while (!feof(f)) {
3375 line = fparseln(f, &linelen, NULL, NULL, 0);
3376 if (line == NULL)
3377 continue;
3378 if (!strcmp(line, lt))
3379 goto done;
3380 free(line);
3381 line = NULL;
3384 fprintf(f, "%s\n", lt);
3386 a.i = XT_WL_ENABLE;
3387 a.i |= args->i;
3388 if (js) {
3389 d = wl_find(dom_save, &js_wl);
3390 if (!d) {
3391 settings_add("js_wl", dom_save);
3392 d = wl_find(dom_save, &js_wl);
3394 toggle_js(t, &a);
3395 } else {
3396 d = wl_find(dom_save, &c_wl);
3397 if (!d) {
3398 settings_add("cookie_wl", dom_save);
3399 d = wl_find(dom_save, &c_wl);
3401 toggle_cwl(t, &a);
3403 /* find and add to persistent jar */
3404 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3405 for (;cf; cf = cf->next) {
3406 ci = cf->data;
3407 if (!strcmp(dom_save, ci->domain) ||
3408 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3409 c = soup_cookie_copy(ci);
3410 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3413 soup_cookies_free(cf);
3415 if (d)
3416 d->handy = 1;
3418 done:
3419 if (line)
3420 free(line);
3421 if (dom)
3422 g_free(dom);
3423 if (lt)
3424 g_free(lt);
3425 fclose(f);
3427 return (0);
3431 js_show_wl(struct tab *t, struct karg *args)
3433 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3434 wl_show(t, args, "JavaScript White List", &js_wl);
3436 return (0);
3440 cookie_show_wl(struct tab *t, struct karg *args)
3442 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3443 wl_show(t, args, "Cookie White List", &c_wl);
3445 return (0);
3449 cookie_cmd(struct tab *t, struct karg *args)
3451 if (args->i & XT_SHOW)
3452 wl_show(t, args, "Cookie White List", &c_wl);
3453 else if (args->i & XT_WL_TOGGLE) {
3454 args->i |= XT_WL_RELOAD;
3455 toggle_cwl(t, args);
3456 } else if (args->i & XT_SAVE) {
3457 args->i |= XT_WL_RELOAD;
3458 wl_save(t, args, 0);
3459 } else if (args->i & XT_DELETE)
3460 show_oops(t, "'cookie delete' currently unimplemented");
3462 return (0);
3466 js_cmd(struct tab *t, struct karg *args)
3468 if (args->i & XT_SHOW)
3469 wl_show(t, args, "JavaScript White List", &js_wl);
3470 else if (args->i & XT_SAVE) {
3471 args->i |= XT_WL_RELOAD;
3472 wl_save(t, args, 1);
3473 } else if (args->i & XT_WL_TOGGLE) {
3474 args->i |= XT_WL_RELOAD;
3475 toggle_js(t, args);
3476 } else if (args->i & XT_DELETE)
3477 show_oops(t, "'js delete' currently unimplemented");
3479 return (0);
3483 toplevel_cmd(struct tab *t, struct karg *args)
3485 js_toggle_cb(t->js_toggle, t);
3487 return (0);
3491 add_favorite(struct tab *t, struct karg *args)
3493 char file[PATH_MAX];
3494 FILE *f;
3495 char *line = NULL;
3496 size_t urilen, linelen;
3497 const gchar *uri, *title;
3499 if (t == NULL)
3500 return (1);
3502 /* don't allow adding of xtp pages to favorites */
3503 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3504 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3505 return (1);
3508 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3509 if ((f = fopen(file, "r+")) == NULL) {
3510 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3511 return (1);
3514 title = webkit_web_view_get_title(t->wv);
3515 uri = get_uri(t);
3517 if (title == NULL)
3518 title = uri;
3520 if (title == NULL || uri == NULL) {
3521 show_oops(t, "can't add page to favorites");
3522 goto done;
3525 urilen = strlen(uri);
3527 for (;;) {
3528 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3529 if (feof(f) || ferror(f))
3530 break;
3532 if (linelen == urilen && !strcmp(line, uri))
3533 goto done;
3535 free(line);
3536 line = NULL;
3539 fprintf(f, "\n%s\n%s", title, uri);
3540 done:
3541 if (line)
3542 free(line);
3543 fclose(f);
3545 update_favorite_tabs(NULL);
3547 return (0);
3551 navaction(struct tab *t, struct karg *args)
3553 WebKitWebHistoryItem *item;
3555 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3556 t->tab_id, args->i);
3558 if (t->item) {
3559 if (args->i == XT_NAV_BACK)
3560 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3561 else
3562 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3563 if (item == NULL)
3564 return (XT_CB_PASSTHROUGH);
3565 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3566 t->item = NULL;
3567 return (XT_CB_PASSTHROUGH);
3570 switch (args->i) {
3571 case XT_NAV_BACK:
3572 webkit_web_view_go_back(t->wv);
3573 break;
3574 case XT_NAV_FORWARD:
3575 webkit_web_view_go_forward(t->wv);
3576 break;
3577 case XT_NAV_RELOAD:
3578 webkit_web_view_reload(t->wv);
3579 break;
3580 case XT_NAV_RELOAD_CACHE:
3581 webkit_web_view_reload_bypass_cache(t->wv);
3582 break;
3584 return (XT_CB_PASSTHROUGH);
3588 move(struct tab *t, struct karg *args)
3590 GtkAdjustment *adjust;
3591 double pi, si, pos, ps, upper, lower, max;
3593 switch (args->i) {
3594 case XT_MOVE_DOWN:
3595 case XT_MOVE_UP:
3596 case XT_MOVE_BOTTOM:
3597 case XT_MOVE_TOP:
3598 case XT_MOVE_PAGEDOWN:
3599 case XT_MOVE_PAGEUP:
3600 case XT_MOVE_HALFDOWN:
3601 case XT_MOVE_HALFUP:
3602 adjust = t->adjust_v;
3603 break;
3604 default:
3605 adjust = t->adjust_h;
3606 break;
3609 pos = gtk_adjustment_get_value(adjust);
3610 ps = gtk_adjustment_get_page_size(adjust);
3611 upper = gtk_adjustment_get_upper(adjust);
3612 lower = gtk_adjustment_get_lower(adjust);
3613 si = gtk_adjustment_get_step_increment(adjust);
3614 pi = gtk_adjustment_get_page_increment(adjust);
3615 max = upper - ps;
3617 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3618 "max %f si %f pi %f\n",
3619 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3620 pos, ps, upper, lower, max, si, pi);
3622 switch (args->i) {
3623 case XT_MOVE_DOWN:
3624 case XT_MOVE_RIGHT:
3625 pos += si;
3626 gtk_adjustment_set_value(adjust, MIN(pos, max));
3627 break;
3628 case XT_MOVE_UP:
3629 case XT_MOVE_LEFT:
3630 pos -= si;
3631 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3632 break;
3633 case XT_MOVE_BOTTOM:
3634 case XT_MOVE_FARRIGHT:
3635 gtk_adjustment_set_value(adjust, max);
3636 break;
3637 case XT_MOVE_TOP:
3638 case XT_MOVE_FARLEFT:
3639 gtk_adjustment_set_value(adjust, lower);
3640 break;
3641 case XT_MOVE_PAGEDOWN:
3642 pos += pi;
3643 gtk_adjustment_set_value(adjust, MIN(pos, max));
3644 break;
3645 case XT_MOVE_PAGEUP:
3646 pos -= pi;
3647 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3648 break;
3649 case XT_MOVE_HALFDOWN:
3650 pos += pi / 2;
3651 gtk_adjustment_set_value(adjust, MIN(pos, max));
3652 break;
3653 case XT_MOVE_HALFUP:
3654 pos -= pi / 2;
3655 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3656 break;
3657 default:
3658 return (XT_CB_PASSTHROUGH);
3661 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3663 return (XT_CB_HANDLED);
3666 void
3667 url_set_visibility(void)
3669 struct tab *t;
3671 TAILQ_FOREACH(t, &tabs, entry) {
3672 if (show_url == 0) {
3673 gtk_widget_hide(t->toolbar);
3674 focus_webview(t);
3675 } else
3676 gtk_widget_show(t->toolbar);
3680 void
3681 notebook_tab_set_visibility(GtkNotebook *notebook)
3683 if (show_tabs == 0)
3684 gtk_notebook_set_show_tabs(notebook, FALSE);
3685 else
3686 gtk_notebook_set_show_tabs(notebook, TRUE);
3689 void
3690 statusbar_set_visibility(void)
3692 struct tab *t;
3694 TAILQ_FOREACH(t, &tabs, entry) {
3695 if (show_statusbar == 0) {
3696 gtk_widget_hide(t->statusbar);
3697 focus_webview(t);
3698 } else
3699 gtk_widget_show(t->statusbar);
3703 void
3704 url_set(struct tab *t, int enable_url_entry)
3706 GdkPixbuf *pixbuf;
3707 int progress;
3709 show_url = enable_url_entry;
3711 if (enable_url_entry) {
3712 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3713 GTK_ENTRY_ICON_PRIMARY, NULL);
3714 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3715 } else {
3716 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3717 GTK_ENTRY_ICON_PRIMARY);
3718 progress =
3719 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3720 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3721 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3722 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3723 progress);
3728 fullscreen(struct tab *t, struct karg *args)
3730 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3732 if (t == NULL)
3733 return (XT_CB_PASSTHROUGH);
3735 if (show_url == 0) {
3736 url_set(t, 1);
3737 show_tabs = 1;
3738 } else {
3739 url_set(t, 0);
3740 show_tabs = 0;
3743 url_set_visibility();
3744 notebook_tab_set_visibility(notebook);
3746 return (XT_CB_HANDLED);
3750 statusaction(struct tab *t, struct karg *args)
3752 int rv = XT_CB_HANDLED;
3754 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3756 if (t == NULL)
3757 return (XT_CB_PASSTHROUGH);
3759 switch (args->i) {
3760 case XT_STATUSBAR_SHOW:
3761 if (show_statusbar == 0) {
3762 show_statusbar = 1;
3763 statusbar_set_visibility();
3765 break;
3766 case XT_STATUSBAR_HIDE:
3767 if (show_statusbar == 1) {
3768 show_statusbar = 0;
3769 statusbar_set_visibility();
3771 break;
3773 return (rv);
3777 urlaction(struct tab *t, struct karg *args)
3779 int rv = XT_CB_HANDLED;
3781 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3783 if (t == NULL)
3784 return (XT_CB_PASSTHROUGH);
3786 switch (args->i) {
3787 case XT_URL_SHOW:
3788 if (show_url == 0) {
3789 url_set(t, 1);
3790 url_set_visibility();
3792 break;
3793 case XT_URL_HIDE:
3794 if (show_url == 1) {
3795 url_set(t, 0);
3796 url_set_visibility();
3798 break;
3800 return (rv);
3804 tabaction(struct tab *t, struct karg *args)
3806 int rv = XT_CB_HANDLED;
3807 char *url = args->s;
3808 struct undo *u;
3809 struct tab *tt;
3811 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3813 if (t == NULL)
3814 return (XT_CB_PASSTHROUGH);
3816 switch (args->i) {
3817 case XT_TAB_NEW:
3818 if (strlen(url) > 0)
3819 create_new_tab(url, NULL, 1, args->p);
3820 else
3821 create_new_tab(NULL, NULL, 1, args->p);
3822 break;
3823 case XT_TAB_DELETE:
3824 if (args->p < 0)
3825 delete_tab(t);
3826 else
3827 TAILQ_FOREACH(tt, &tabs, entry)
3828 if (tt->tab_id == args->p - 1) {
3829 delete_tab(tt);
3830 recalc_tabs();
3831 break;
3833 break;
3834 case XT_TAB_DELQUIT:
3835 if (gtk_notebook_get_n_pages(notebook) > 1)
3836 delete_tab(t);
3837 else
3838 quit(t, args);
3839 break;
3840 case XT_TAB_OPEN:
3841 if (strlen(url) > 0)
3843 else {
3844 rv = XT_CB_PASSTHROUGH;
3845 goto done;
3847 load_uri(t, url);
3848 break;
3849 case XT_TAB_SHOW:
3850 if (show_tabs == 0) {
3851 show_tabs = 1;
3852 notebook_tab_set_visibility(notebook);
3854 break;
3855 case XT_TAB_HIDE:
3856 if (show_tabs == 1) {
3857 show_tabs = 0;
3858 notebook_tab_set_visibility(notebook);
3860 break;
3861 case XT_TAB_UNDO_CLOSE:
3862 if (undo_count == 0) {
3863 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3864 goto done;
3865 } else {
3866 undo_count--;
3867 u = TAILQ_FIRST(&undos);
3868 create_new_tab(u->uri, u, 1, -1);
3870 TAILQ_REMOVE(&undos, u, entry);
3871 g_free(u->uri);
3872 /* u->history is freed in create_new_tab() */
3873 g_free(u);
3875 break;
3876 default:
3877 rv = XT_CB_PASSTHROUGH;
3878 goto done;
3881 done:
3882 if (args->s) {
3883 g_free(args->s);
3884 args->s = NULL;
3887 return (rv);
3891 resizetab(struct tab *t, struct karg *args)
3893 if (t == NULL || args == NULL) {
3894 show_oops(NULL, "resizetab invalid parameters");
3895 return (XT_CB_PASSTHROUGH);
3898 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3899 t->tab_id, args->i);
3901 adjustfont_webkit(t, args->i);
3903 return (XT_CB_HANDLED);
3907 movetab(struct tab *t, struct karg *args)
3909 int n, dest;
3911 if (t == NULL || args == NULL) {
3912 show_oops(NULL, "movetab invalid parameters");
3913 return (XT_CB_PASSTHROUGH);
3916 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3917 t->tab_id, args->i);
3919 if (args->i >= XT_TAB_INVALID)
3920 return (XT_CB_PASSTHROUGH);
3922 if (TAILQ_EMPTY(&tabs))
3923 return (XT_CB_PASSTHROUGH);
3925 n = gtk_notebook_get_n_pages(notebook);
3926 dest = gtk_notebook_get_current_page(notebook);
3928 switch (args->i) {
3929 case XT_TAB_NEXT:
3930 if (args->p < 0)
3931 dest = dest == n - 1 ? 0 : dest + 1;
3932 else
3933 dest = args->p - 1;
3935 break;
3936 case XT_TAB_PREV:
3937 if (args->p < 0)
3938 dest -= 1;
3939 else
3940 dest -= args->p % n;
3942 if (dest < 0)
3943 dest += n;
3945 break;
3946 case XT_TAB_FIRST:
3947 dest = 0;
3948 break;
3949 case XT_TAB_LAST:
3950 dest = n - 1;
3951 break;
3952 default:
3953 return (XT_CB_PASSTHROUGH);
3956 if (dest < 0 || dest >= n)
3957 return (XT_CB_PASSTHROUGH);
3958 if (t->tab_id == dest) {
3959 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3960 return (XT_CB_HANDLED);
3963 gtk_notebook_set_current_page(notebook, dest);
3965 return (XT_CB_HANDLED);
3968 int cmd_prefix = 0;
3971 command(struct tab *t, struct karg *args)
3973 char *s = NULL, *ss = NULL;
3974 GdkColor color;
3975 const gchar *uri;
3977 if (t == NULL || args == NULL) {
3978 show_oops(NULL, "command invalid parameters");
3979 return (XT_CB_PASSTHROUGH);
3982 switch (args->i) {
3983 case '/':
3984 s = "/";
3985 break;
3986 case '?':
3987 s = "?";
3988 break;
3989 case ':':
3990 if (cmd_prefix == 0)
3991 s = ":";
3992 else {
3993 ss = g_strdup_printf(":%d", cmd_prefix);
3994 s = ss;
3995 cmd_prefix = 0;
3997 break;
3998 case XT_CMD_OPEN:
3999 s = ":open ";
4000 break;
4001 case XT_CMD_TABNEW:
4002 s = ":tabnew ";
4003 break;
4004 case XT_CMD_OPEN_CURRENT:
4005 s = ":open ";
4006 /* FALL THROUGH */
4007 case XT_CMD_TABNEW_CURRENT:
4008 if (!s) /* FALL THROUGH? */
4009 s = ":tabnew ";
4010 if ((uri = get_uri(t)) != NULL) {
4011 ss = g_strdup_printf("%s%s", s, uri);
4012 s = ss;
4014 break;
4015 default:
4016 show_oops(t, "command: invalid opcode %d", args->i);
4017 return (XT_CB_PASSTHROUGH);
4020 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4022 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4023 gdk_color_parse(XT_COLOR_WHITE, &color);
4024 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4025 show_cmd(t);
4026 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4027 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4029 if (ss)
4030 g_free(ss);
4032 return (XT_CB_HANDLED);
4036 * Return a new string with a download row (in html)
4037 * appended. Old string is freed.
4039 char *
4040 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4043 WebKitDownloadStatus stat;
4044 char *status_html = NULL, *cmd_html = NULL, *new_html;
4045 gdouble progress;
4046 char cur_sz[FMT_SCALED_STRSIZE];
4047 char tot_sz[FMT_SCALED_STRSIZE];
4048 char *xtp_prefix;
4050 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4052 /* All actions wil take this form:
4053 * xxxt://class/seskey
4055 xtp_prefix = g_strdup_printf("%s%d/%s/",
4056 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4058 stat = webkit_download_get_status(dl->download);
4060 switch (stat) {
4061 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4062 status_html = g_strdup_printf("Finished");
4063 cmd_html = g_strdup_printf(
4064 "<a href='%s%d/%d'>Remove</a>",
4065 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4066 break;
4067 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4068 /* gather size info */
4069 progress = 100 * webkit_download_get_progress(dl->download);
4071 fmt_scaled(
4072 webkit_download_get_current_size(dl->download), cur_sz);
4073 fmt_scaled(
4074 webkit_download_get_total_size(dl->download), tot_sz);
4076 status_html = g_strdup_printf(
4077 "<div style='width: 100%%' align='center'>"
4078 "<div class='progress-outer'>"
4079 "<div class='progress-inner' style='width: %.2f%%'>"
4080 "</div></div></div>"
4081 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4082 progress, cur_sz, tot_sz, progress);
4084 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4085 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4087 break;
4088 /* LLL */
4089 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4090 status_html = g_strdup_printf("Cancelled");
4091 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4092 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4093 break;
4094 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4095 status_html = g_strdup_printf("Error!");
4096 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4097 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4098 break;
4099 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4100 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4101 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4102 status_html = g_strdup_printf("Starting");
4103 break;
4104 default:
4105 show_oops(t, "%s: unknown download status", __func__);
4108 new_html = g_strdup_printf(
4109 "%s\n<tr><td>%s</td><td>%s</td>"
4110 "<td style='text-align:center'>%s</td></tr>\n",
4111 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4112 status_html, cmd_html);
4113 g_free(html);
4115 if (status_html)
4116 g_free(status_html);
4118 if (cmd_html)
4119 g_free(cmd_html);
4121 g_free(xtp_prefix);
4123 return new_html;
4127 * update all download tabs apart from one. Pass NULL if
4128 * you want to update all.
4130 void
4131 update_download_tabs(struct tab *apart_from)
4133 struct tab *t;
4134 if (!updating_dl_tabs) {
4135 updating_dl_tabs = 1; /* stop infinite recursion */
4136 TAILQ_FOREACH(t, &tabs, entry)
4137 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4138 && (t != apart_from))
4139 xtp_page_dl(t, NULL);
4140 updating_dl_tabs = 0;
4145 * update all cookie tabs apart from one. Pass NULL if
4146 * you want to update all.
4148 void
4149 update_cookie_tabs(struct tab *apart_from)
4151 struct tab *t;
4152 if (!updating_cl_tabs) {
4153 updating_cl_tabs = 1; /* stop infinite recursion */
4154 TAILQ_FOREACH(t, &tabs, entry)
4155 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4156 && (t != apart_from))
4157 xtp_page_cl(t, NULL);
4158 updating_cl_tabs = 0;
4163 * update all history tabs apart from one. Pass NULL if
4164 * you want to update all.
4166 void
4167 update_history_tabs(struct tab *apart_from)
4169 struct tab *t;
4171 if (!updating_hl_tabs) {
4172 updating_hl_tabs = 1; /* stop infinite recursion */
4173 TAILQ_FOREACH(t, &tabs, entry)
4174 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4175 && (t != apart_from))
4176 xtp_page_hl(t, NULL);
4177 updating_hl_tabs = 0;
4181 /* cookie management XTP page */
4183 xtp_page_cl(struct tab *t, struct karg *args)
4185 char *body, *page, *tmp;
4186 int i = 1; /* all ids start 1 */
4187 GSList *sc, *pc, *pc_start;
4188 SoupCookie *c;
4189 char *type, *table_headers, *last_domain;
4191 DNPRINTF(XT_D_CMD, "%s", __func__);
4193 if (t == NULL) {
4194 show_oops(NULL, "%s invalid parameters", __func__);
4195 return (1);
4198 /* Generate a new session key */
4199 if (!updating_cl_tabs)
4200 generate_xtp_session_key(&cl_session_key);
4202 /* table headers */
4203 table_headers = g_strdup_printf("<table><tr>"
4204 "<th>Type</th>"
4205 "<th>Name</th>"
4206 "<th style='width:200px'>Value</th>"
4207 "<th>Path</th>"
4208 "<th>Expires</th>"
4209 "<th>Secure</th>"
4210 "<th>HTTP<br />only</th>"
4211 "<th style='width:40px'>Rm</th></tr>\n");
4213 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4214 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4215 pc_start = pc;
4217 body = NULL;
4218 last_domain = strdup("");
4219 for (; sc; sc = sc->next) {
4220 c = sc->data;
4222 if (strcmp(last_domain, c->domain) != 0) {
4223 /* new domain */
4224 free(last_domain);
4225 last_domain = strdup(c->domain);
4227 if (body != NULL) {
4228 tmp = body;
4229 body = g_strdup_printf("%s</table>"
4230 "<h2>%s</h2>%s\n",
4231 body, c->domain, table_headers);
4232 g_free(tmp);
4233 } else {
4234 /* first domain */
4235 body = g_strdup_printf("<h2>%s</h2>%s\n",
4236 c->domain, table_headers);
4240 type = "Session";
4241 for (pc = pc_start; pc; pc = pc->next)
4242 if (soup_cookie_equal(pc->data, c)) {
4243 type = "Session + Persistent";
4244 break;
4247 tmp = body;
4248 body = g_strdup_printf(
4249 "%s\n<tr>"
4250 "<td>%s</td>"
4251 "<td style='word-wrap:normal'>%s</td>"
4252 "<td>"
4253 " <textarea rows='4'>%s</textarea>"
4254 "</td>"
4255 "<td>%s</td>"
4256 "<td>%s</td>"
4257 "<td>%d</td>"
4258 "<td>%d</td>"
4259 "<td style='text-align:center'>"
4260 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4261 body,
4262 type,
4263 c->name,
4264 c->value,
4265 c->path,
4266 c->expires ?
4267 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4268 c->secure,
4269 c->http_only,
4271 XT_XTP_STR,
4272 XT_XTP_CL,
4273 cl_session_key,
4274 XT_XTP_CL_REMOVE,
4278 g_free(tmp);
4279 i++;
4282 soup_cookies_free(sc);
4283 soup_cookies_free(pc);
4285 /* small message if there are none */
4286 if (i == 1) {
4287 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4288 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4290 tmp = body;
4291 body = g_strdup_printf("%s</table>", body);
4292 g_free(tmp);
4294 page = get_html_page("Cookie Jar", body, "", TRUE);
4295 g_free(body);
4296 g_free(table_headers);
4297 g_free(last_domain);
4299 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4300 update_cookie_tabs(t);
4302 g_free(page);
4304 return (0);
4308 xtp_page_hl(struct tab *t, struct karg *args)
4310 char *body, *page, *tmp;
4311 struct history *h;
4312 int i = 1; /* all ids start 1 */
4314 DNPRINTF(XT_D_CMD, "%s", __func__);
4316 if (t == NULL) {
4317 show_oops(NULL, "%s invalid parameters", __func__);
4318 return (1);
4321 /* Generate a new session key */
4322 if (!updating_hl_tabs)
4323 generate_xtp_session_key(&hl_session_key);
4325 /* body */
4326 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4327 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4329 RB_FOREACH_REVERSE(h, history_list, &hl) {
4330 tmp = body;
4331 body = g_strdup_printf(
4332 "%s\n<tr>"
4333 "<td><a href='%s'>%s</a></td>"
4334 "<td>%s</td>"
4335 "<td style='text-align: center'>"
4336 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4337 body, h->uri, h->uri, h->title,
4338 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4339 XT_XTP_HL_REMOVE, i);
4341 g_free(tmp);
4342 i++;
4345 /* small message if there are none */
4346 if (i == 1) {
4347 tmp = body;
4348 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4349 "colspan='3'>No History</td></tr>\n", body);
4350 g_free(tmp);
4353 tmp = body;
4354 body = g_strdup_printf("%s</table>", body);
4355 g_free(tmp);
4357 page = get_html_page("History", body, "", TRUE);
4358 g_free(body);
4361 * update all history manager tabs as the xtp session
4362 * key has now changed. No need to update the current tab.
4363 * Already did that above.
4365 update_history_tabs(t);
4367 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4368 g_free(page);
4370 return (0);
4374 * Generate a web page detailing the status of any downloads
4377 xtp_page_dl(struct tab *t, struct karg *args)
4379 struct download *dl;
4380 char *body, *page, *tmp;
4381 char *ref;
4382 int n_dl = 1;
4384 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4386 if (t == NULL) {
4387 show_oops(NULL, "%s invalid parameters", __func__);
4388 return (1);
4392 * Generate a new session key for next page instance.
4393 * This only happens for the top level call to xtp_page_dl()
4394 * in which case updating_dl_tabs is 0.
4396 if (!updating_dl_tabs)
4397 generate_xtp_session_key(&dl_session_key);
4399 /* header - with refresh so as to update */
4400 if (refresh_interval >= 1)
4401 ref = g_strdup_printf(
4402 "<meta http-equiv='refresh' content='%u"
4403 ";url=%s%d/%s/%d' />\n",
4404 refresh_interval,
4405 XT_XTP_STR,
4406 XT_XTP_DL,
4407 dl_session_key,
4408 XT_XTP_DL_LIST);
4409 else
4410 ref = g_strdup("");
4412 body = g_strdup_printf("<div align='center'>"
4413 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4414 "</p><table><tr><th style='width: 60%%'>"
4415 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4416 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4418 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4419 body = xtp_page_dl_row(t, body, dl);
4420 n_dl++;
4423 /* message if no downloads in list */
4424 if (n_dl == 1) {
4425 tmp = body;
4426 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4427 " style='text-align: center'>"
4428 "No downloads</td></tr>\n", body);
4429 g_free(tmp);
4432 tmp = body;
4433 body = g_strdup_printf("%s</table></div>", body);
4434 g_free(tmp);
4436 page = get_html_page("Downloads", body, ref, 1);
4437 g_free(ref);
4438 g_free(body);
4441 * update all download manager tabs as the xtp session
4442 * key has now changed. No need to update the current tab.
4443 * Already did that above.
4445 update_download_tabs(t);
4447 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4448 g_free(page);
4450 return (0);
4454 search(struct tab *t, struct karg *args)
4456 gboolean d;
4458 if (t == NULL || args == NULL) {
4459 show_oops(NULL, "search invalid parameters");
4460 return (1);
4462 if (t->search_text == NULL) {
4463 if (global_search == NULL)
4464 return (XT_CB_PASSTHROUGH);
4465 else {
4466 t->search_text = g_strdup(global_search);
4467 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4468 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4472 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4473 t->tab_id, args->i, t->search_forward, t->search_text);
4475 switch (args->i) {
4476 case XT_SEARCH_NEXT:
4477 d = t->search_forward;
4478 break;
4479 case XT_SEARCH_PREV:
4480 d = !t->search_forward;
4481 break;
4482 default:
4483 return (XT_CB_PASSTHROUGH);
4486 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4488 return (XT_CB_HANDLED);
4491 struct settings_args {
4492 char **body;
4493 int i;
4496 void
4497 print_setting(struct settings *s, char *val, void *cb_args)
4499 char *tmp, *color;
4500 struct settings_args *sa = cb_args;
4502 if (sa == NULL)
4503 return;
4505 if (s->flags & XT_SF_RUNTIME)
4506 color = "#22cc22";
4507 else
4508 color = "#cccccc";
4510 tmp = *sa->body;
4511 *sa->body = g_strdup_printf(
4512 "%s\n<tr>"
4513 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4514 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4515 *sa->body,
4516 color,
4517 s->name,
4518 color,
4521 g_free(tmp);
4522 sa->i++;
4526 set(struct tab *t, struct karg *args)
4528 char *body, *page, *tmp;
4529 int i = 1;
4530 struct settings_args sa;
4532 bzero(&sa, sizeof sa);
4533 sa.body = &body;
4535 /* body */
4536 body = g_strdup_printf("<div align='center'><table><tr>"
4537 "<th align='left'>Setting</th>"
4538 "<th align='left'>Value</th></tr>\n");
4540 settings_walk(print_setting, &sa);
4541 i = sa.i;
4543 /* small message if there are none */
4544 if (i == 1) {
4545 tmp = body;
4546 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4547 "colspan='2'>No settings</td></tr>\n", body);
4548 g_free(tmp);
4551 tmp = body;
4552 body = g_strdup_printf("%s</table></div>", body);
4553 g_free(tmp);
4555 page = get_html_page("Settings", body, "", 0);
4557 g_free(body);
4559 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4561 g_free(page);
4563 return (XT_CB_PASSTHROUGH);
4567 session_save(struct tab *t, char *filename)
4569 struct karg a;
4570 int rv = 1;
4572 if (strlen(filename) == 0)
4573 goto done;
4575 if (filename[0] == '.' || filename[0] == '/')
4576 goto done;
4578 a.s = filename;
4579 if (save_tabs(t, &a))
4580 goto done;
4581 strlcpy(named_session, filename, sizeof named_session);
4583 rv = 0;
4584 done:
4585 return (rv);
4589 session_open(struct tab *t, char *filename)
4591 struct karg a;
4592 int rv = 1;
4594 if (strlen(filename) == 0)
4595 goto done;
4597 if (filename[0] == '.' || filename[0] == '/')
4598 goto done;
4600 a.s = filename;
4601 a.i = XT_SES_CLOSETABS;
4602 if (open_tabs(t, &a))
4603 goto done;
4605 strlcpy(named_session, filename, sizeof named_session);
4607 rv = 0;
4608 done:
4609 return (rv);
4613 session_delete(struct tab *t, char *filename)
4615 char file[PATH_MAX];
4616 int rv = 1;
4618 if (strlen(filename) == 0)
4619 goto done;
4621 if (filename[0] == '.' || filename[0] == '/')
4622 goto done;
4624 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4625 if (unlink(file))
4626 goto done;
4628 if (!strcmp(filename, named_session))
4629 strlcpy(named_session, XT_SAVED_TABS_FILE,
4630 sizeof named_session);
4632 rv = 0;
4633 done:
4634 return (rv);
4638 session_cmd(struct tab *t, struct karg *args)
4640 char *filename = args->s;
4642 if (t == NULL)
4643 return (1);
4645 if (args->i & XT_SHOW)
4646 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4647 XT_SAVED_TABS_FILE : named_session);
4648 else if (args->i & XT_SAVE) {
4649 if (session_save(t, filename)) {
4650 show_oops(t, "Can't save session: %s",
4651 filename ? filename : "INVALID");
4652 goto done;
4654 } else if (args->i & XT_OPEN) {
4655 if (session_open(t, filename)) {
4656 show_oops(t, "Can't open session: %s",
4657 filename ? filename : "INVALID");
4658 goto done;
4660 } else if (args->i & XT_DELETE) {
4661 if (session_delete(t, filename)) {
4662 show_oops(t, "Can't delete session: %s",
4663 filename ? filename : "INVALID");
4664 goto done;
4667 done:
4668 return (XT_CB_PASSTHROUGH);
4672 * Make a hardcopy of the page
4675 print_page(struct tab *t, struct karg *args)
4677 WebKitWebFrame *frame;
4678 GtkPageSetup *ps;
4679 GtkPrintOperation *op;
4680 GtkPrintOperationAction action;
4681 GtkPrintOperationResult print_res;
4682 GError *g_err = NULL;
4683 int marg_l, marg_r, marg_t, marg_b;
4685 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4687 ps = gtk_page_setup_new();
4688 op = gtk_print_operation_new();
4689 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4690 frame = webkit_web_view_get_main_frame(t->wv);
4692 /* the default margins are too small, so we will bump them */
4693 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4694 XT_PRINT_EXTRA_MARGIN;
4695 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4696 XT_PRINT_EXTRA_MARGIN;
4697 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4698 XT_PRINT_EXTRA_MARGIN;
4699 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4700 XT_PRINT_EXTRA_MARGIN;
4702 /* set margins */
4703 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4704 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4705 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4706 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4708 gtk_print_operation_set_default_page_setup(op, ps);
4710 /* this appears to free 'op' and 'ps' */
4711 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4713 /* check it worked */
4714 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4715 show_oops(NULL, "can't print: %s", g_err->message);
4716 g_error_free (g_err);
4717 return (1);
4720 return (0);
4724 go_home(struct tab *t, struct karg *args)
4726 load_uri(t, home);
4727 return (0);
4731 restart(struct tab *t, struct karg *args)
4733 struct karg a;
4735 a.s = XT_RESTART_TABS_FILE;
4736 save_tabs(t, &a);
4737 execvp(start_argv[0], start_argv);
4738 /* NOTREACHED */
4740 return (0);
4743 #define CTRL GDK_CONTROL_MASK
4744 #define MOD1 GDK_MOD1_MASK
4745 #define SHFT GDK_SHIFT_MASK
4747 /* inherent to GTK not all keys will be caught at all times */
4748 /* XXX sort key bindings */
4749 struct key_binding {
4750 char *cmd;
4751 guint mask;
4752 guint use_in_entry;
4753 guint key;
4754 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4755 } keys[] = {
4756 { "cookiejar", MOD1, 0, GDK_j },
4757 { "downloadmgr", MOD1, 0, GDK_d },
4758 { "history", MOD1, 0, GDK_h },
4759 { "print", CTRL, 0, GDK_p },
4760 { "search", 0, 0, GDK_slash },
4761 { "searchb", 0, 0, GDK_question },
4762 { "command", 0, 0, GDK_colon },
4763 { "qa", CTRL, 0, GDK_q },
4764 { "restart", MOD1, 0, GDK_q },
4765 { "js toggle", CTRL, 0, GDK_j },
4766 { "cookie toggle", MOD1, 0, GDK_c },
4767 { "togglesrc", CTRL, 0, GDK_s },
4768 { "yankuri", 0, 0, GDK_y },
4769 { "pasteuricur", 0, 0, GDK_p },
4770 { "pasteurinew", 0, 0, GDK_P },
4771 { "toplevel toggle", 0, 0, GDK_F4 },
4772 { "help", 0, 0, GDK_F1 },
4774 /* search */
4775 { "searchnext", 0, 0, GDK_n },
4776 { "searchprevious", 0, 0, GDK_N },
4778 /* focus */
4779 { "focusaddress", 0, 0, GDK_F6 },
4780 { "focussearch", 0, 0, GDK_F7 },
4782 /* hinting */
4783 { "hinting", 0, 0, GDK_f },
4785 /* custom stylesheet */
4786 { "userstyle", 0, 0, GDK_i },
4788 /* navigation */
4789 { "goback", 0, 0, GDK_BackSpace },
4790 { "goback", MOD1, 0, GDK_Left },
4791 { "goforward", SHFT, 0, GDK_BackSpace },
4792 { "goforward", MOD1, 0, GDK_Right },
4793 { "reload", 0, 0, GDK_F5 },
4794 { "reload", CTRL, 0, GDK_r },
4795 { "reloadforce", CTRL, 0, GDK_R },
4796 { "reload", CTRL, 0, GDK_l },
4797 { "favorites", MOD1, 1, GDK_f },
4799 /* vertical movement */
4800 { "scrolldown", 0, 0, GDK_j },
4801 { "scrolldown", 0, 0, GDK_Down },
4802 { "scrollup", 0, 0, GDK_Up },
4803 { "scrollup", 0, 0, GDK_k },
4804 { "scrollbottom", 0, 0, GDK_G },
4805 { "scrollbottom", 0, 0, GDK_End },
4806 { "scrolltop", 0, 0, GDK_Home },
4807 { "scrolltop", 0, 0, GDK_g },
4808 { "scrollpagedown", 0, 0, GDK_space },
4809 { "scrollpagedown", CTRL, 0, GDK_f },
4810 { "scrollhalfdown", CTRL, 0, GDK_d },
4811 { "scrollpagedown", 0, 0, GDK_Page_Down },
4812 { "scrollpageup", 0, 0, GDK_Page_Up },
4813 { "scrollpageup", CTRL, 0, GDK_b },
4814 { "scrollhalfup", CTRL, 0, GDK_u },
4815 /* horizontal movement */
4816 { "scrollright", 0, 0, GDK_l },
4817 { "scrollright", 0, 0, GDK_Right },
4818 { "scrollleft", 0, 0, GDK_Left },
4819 { "scrollleft", 0, 0, GDK_h },
4820 { "scrollfarright", 0, 0, GDK_dollar },
4821 { "scrollfarleft", 0, 0, GDK_0 },
4823 /* tabs */
4824 { "tabnew", CTRL, 0, GDK_t },
4825 { "999tabnew", CTRL, 0, GDK_T },
4826 { "tabclose", CTRL, 1, GDK_w },
4827 { "tabundoclose", 0, 0, GDK_U },
4828 { "tabnext 1", CTRL, 0, GDK_1 },
4829 { "tabnext 2", CTRL, 0, GDK_2 },
4830 { "tabnext 3", CTRL, 0, GDK_3 },
4831 { "tabnext 4", CTRL, 0, GDK_4 },
4832 { "tabnext 5", CTRL, 0, GDK_5 },
4833 { "tabnext 6", CTRL, 0, GDK_6 },
4834 { "tabnext 7", CTRL, 0, GDK_7 },
4835 { "tabnext 8", CTRL, 0, GDK_8 },
4836 { "tabnext 9", CTRL, 0, GDK_9 },
4837 { "tabnext 10", CTRL, 0, GDK_0 },
4838 { "tabfirst", CTRL, 0, GDK_less },
4839 { "tablast", CTRL, 0, GDK_greater },
4840 { "tabprevious", CTRL, 0, GDK_Left },
4841 { "tabnext", CTRL, 0, GDK_Right },
4842 { "focusout", CTRL, 0, GDK_minus },
4843 { "focusin", CTRL, 0, GDK_plus },
4844 { "focusin", CTRL, 0, GDK_equal },
4846 /* command aliases (handy when -S flag is used) */
4847 { "promptopen", 0, 0, GDK_F9 },
4848 { "promptopencurrent", 0, 0, GDK_F10 },
4849 { "prompttabnew", 0, 0, GDK_F11 },
4850 { "prompttabnewcurrent",0, 0, GDK_F12 },
4852 TAILQ_HEAD(keybinding_list, key_binding);
4854 void
4855 walk_kb(struct settings *s,
4856 void (*cb)(struct settings *, char *, void *), void *cb_args)
4858 struct key_binding *k;
4859 char str[1024];
4861 if (s == NULL || cb == NULL) {
4862 show_oops(NULL, "walk_kb invalid parameters");
4863 return;
4866 TAILQ_FOREACH(k, &kbl, entry) {
4867 if (k->cmd == NULL)
4868 continue;
4869 str[0] = '\0';
4871 /* sanity */
4872 if (gdk_keyval_name(k->key) == NULL)
4873 continue;
4875 strlcat(str, k->cmd, sizeof str);
4876 strlcat(str, ",", sizeof str);
4878 if (k->mask & GDK_SHIFT_MASK)
4879 strlcat(str, "S-", sizeof str);
4880 if (k->mask & GDK_CONTROL_MASK)
4881 strlcat(str, "C-", sizeof str);
4882 if (k->mask & GDK_MOD1_MASK)
4883 strlcat(str, "M1-", sizeof str);
4884 if (k->mask & GDK_MOD2_MASK)
4885 strlcat(str, "M2-", sizeof str);
4886 if (k->mask & GDK_MOD3_MASK)
4887 strlcat(str, "M3-", sizeof str);
4888 if (k->mask & GDK_MOD4_MASK)
4889 strlcat(str, "M4-", sizeof str);
4890 if (k->mask & GDK_MOD5_MASK)
4891 strlcat(str, "M5-", sizeof str);
4893 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4894 cb(s, str, cb_args);
4898 void
4899 init_keybindings(void)
4901 int i;
4902 struct key_binding *k;
4904 for (i = 0; i < LENGTH(keys); i++) {
4905 k = g_malloc0(sizeof *k);
4906 k->cmd = keys[i].cmd;
4907 k->mask = keys[i].mask;
4908 k->use_in_entry = keys[i].use_in_entry;
4909 k->key = keys[i].key;
4910 TAILQ_INSERT_HEAD(&kbl, k, entry);
4912 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4913 k->cmd ? k->cmd : "unnamed key");
4917 void
4918 keybinding_clearall(void)
4920 struct key_binding *k, *next;
4922 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4923 next = TAILQ_NEXT(k, entry);
4924 if (k->cmd == NULL)
4925 continue;
4927 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4928 k->cmd ? k->cmd : "unnamed key");
4929 TAILQ_REMOVE(&kbl, k, entry);
4930 g_free(k);
4935 keybinding_add(char *cmd, char *key, int use_in_entry)
4937 struct key_binding *k;
4938 guint keyval, mask = 0;
4939 int i;
4941 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4943 /* Keys which are to be used in entry have been prefixed with an
4944 * exclamation mark. */
4945 if (use_in_entry)
4946 key++;
4948 /* find modifier keys */
4949 if (strstr(key, "S-"))
4950 mask |= GDK_SHIFT_MASK;
4951 if (strstr(key, "C-"))
4952 mask |= GDK_CONTROL_MASK;
4953 if (strstr(key, "M1-"))
4954 mask |= GDK_MOD1_MASK;
4955 if (strstr(key, "M2-"))
4956 mask |= GDK_MOD2_MASK;
4957 if (strstr(key, "M3-"))
4958 mask |= GDK_MOD3_MASK;
4959 if (strstr(key, "M4-"))
4960 mask |= GDK_MOD4_MASK;
4961 if (strstr(key, "M5-"))
4962 mask |= GDK_MOD5_MASK;
4964 /* find keyname */
4965 for (i = strlen(key) - 1; i > 0; i--)
4966 if (key[i] == '-')
4967 key = &key[i + 1];
4969 /* validate keyname */
4970 keyval = gdk_keyval_from_name(key);
4971 if (keyval == GDK_VoidSymbol) {
4972 warnx("invalid keybinding name %s", key);
4973 return (1);
4975 /* must run this test too, gtk+ doesn't handle 10 for example */
4976 if (gdk_keyval_name(keyval) == NULL) {
4977 warnx("invalid keybinding name %s", key);
4978 return (1);
4981 /* Remove eventual dupes. */
4982 TAILQ_FOREACH(k, &kbl, entry)
4983 if (k->key == keyval && k->mask == mask) {
4984 TAILQ_REMOVE(&kbl, k, entry);
4985 g_free(k);
4986 break;
4989 /* add keyname */
4990 k = g_malloc0(sizeof *k);
4991 k->cmd = g_strdup(cmd);
4992 k->mask = mask;
4993 k->use_in_entry = use_in_entry;
4994 k->key = keyval;
4996 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4997 k->cmd,
4998 k->mask,
4999 k->use_in_entry,
5000 k->key);
5001 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5002 k->cmd, gdk_keyval_name(keyval));
5004 TAILQ_INSERT_HEAD(&kbl, k, entry);
5006 return (0);
5010 add_kb(struct settings *s, char *entry)
5012 char *kb, *key;
5014 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5016 /* clearall is special */
5017 if (!strcmp(entry, "clearall")) {
5018 keybinding_clearall();
5019 return (0);
5022 kb = strstr(entry, ",");
5023 if (kb == NULL)
5024 return (1);
5025 *kb = '\0';
5026 key = kb + 1;
5028 return (keybinding_add(entry, key, key[0] == '!'));
5031 struct cmd {
5032 char *cmd;
5033 int level;
5034 int (*func)(struct tab *, struct karg *);
5035 int arg;
5036 int type;
5037 } cmds[] = {
5038 { "command", 0, command, ':', 0 },
5039 { "search", 0, command, '/', 0 },
5040 { "searchb", 0, command, '?', 0 },
5041 { "togglesrc", 0, toggle_src, 0, 0 },
5043 /* yanking and pasting */
5044 { "yankuri", 0, yank_uri, 0, 0 },
5045 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5046 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5047 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5049 /* search */
5050 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5051 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5053 /* focus */
5054 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5055 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5057 /* hinting */
5058 { "hinting", 0, hint, 0, 0 },
5060 /* custom stylesheet */
5061 { "userstyle", 0, userstyle, 0, 0 },
5063 /* navigation */
5064 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5065 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5066 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5067 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5069 /* vertical movement */
5070 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5071 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5072 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5073 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5074 { "1", 0, move, XT_MOVE_TOP, 0 },
5075 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5076 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5077 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5078 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5079 /* horizontal movement */
5080 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5081 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5082 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5083 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5086 { "favorites", 0, xtp_page_fl, 0, 0 },
5087 { "fav", 0, xtp_page_fl, 0, 0 },
5088 { "favadd", 0, add_favorite, 0, 0 },
5090 { "qall", 0, quit, 0, 0 },
5091 { "quitall", 0, quit, 0, 0 },
5092 { "w", 0, save_tabs, 0, 0 },
5093 { "wq", 0, save_tabs_and_quit, 0, 0 },
5094 { "help", 0, help, 0, 0 },
5095 { "about", 0, about, 0, 0 },
5096 { "stats", 0, stats, 0, 0 },
5097 { "version", 0, about, 0, 0 },
5099 /* js command */
5100 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5101 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5102 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5103 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5104 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5105 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5106 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5107 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5108 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5109 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5110 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5112 /* cookie command */
5113 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5114 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5115 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5116 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5117 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5118 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5119 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5120 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5121 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5122 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5123 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5125 /* toplevel (domain) command */
5126 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5127 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5129 /* cookie jar */
5130 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5132 /* cert command */
5133 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5134 { "save", 1, cert_cmd, XT_SAVE, 0 },
5135 { "show", 1, cert_cmd, XT_SHOW, 0 },
5137 { "ca", 0, ca_cmd, 0, 0 },
5138 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5139 { "dl", 0, xtp_page_dl, 0, 0 },
5140 { "h", 0, xtp_page_hl, 0, 0 },
5141 { "history", 0, xtp_page_hl, 0, 0 },
5142 { "home", 0, go_home, 0, 0 },
5143 { "restart", 0, restart, 0, 0 },
5144 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5145 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5146 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5147 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5149 { "print", 0, print_page, 0, 0 },
5151 /* tabs */
5152 { "focusin", 0, resizetab, 1, 0 },
5153 { "focusout", 0, resizetab, -1, 0 },
5154 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5155 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5156 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5157 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5158 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5159 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5160 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5161 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5162 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5163 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5164 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5165 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5166 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5167 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5168 { "buffers", 0, buffers, 0, 0 },
5169 { "ls", 0, buffers, 0, 0 },
5170 { "tabs", 0, buffers, 0, 0 },
5172 /* command aliases (handy when -S flag is used) */
5173 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5174 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5175 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5176 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5178 /* settings */
5179 { "set", 0, set, 0, 0 },
5180 { "fullscreen", 0, fullscreen, 0, 0 },
5181 { "f", 0, fullscreen, 0, 0 },
5183 /* sessions */
5184 { "session", 0, session_cmd, XT_SHOW, 0 },
5185 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5186 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5187 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5188 { "show", 1, session_cmd, XT_SHOW, 0 },
5191 struct {
5192 int index;
5193 int len;
5194 gchar *list[256];
5195 } cmd_status = {-1, 0};
5197 gboolean
5198 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5200 struct karg a;
5202 hide_oops(t);
5203 hide_buffers(t);
5205 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5206 /* go backward */
5207 a.i = XT_NAV_BACK;
5208 navaction(t, &a);
5210 return (TRUE);
5211 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5212 /* go forward */
5213 a.i = XT_NAV_FORWARD;
5214 navaction(t, &a);
5216 return (TRUE);
5219 return (FALSE);
5222 gboolean
5223 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5225 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5227 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5228 delete_tab(t);
5230 return (FALSE);
5234 * cancel, remove, etc. downloads
5236 void
5237 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5239 struct download find, *d = NULL;
5241 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5243 /* some commands require a valid download id */
5244 if (cmd != XT_XTP_DL_LIST) {
5245 /* lookup download in question */
5246 find.id = id;
5247 d = RB_FIND(download_list, &downloads, &find);
5249 if (d == NULL) {
5250 show_oops(t, "%s: no such download", __func__);
5251 return;
5255 /* decide what to do */
5256 switch (cmd) {
5257 case XT_XTP_DL_CANCEL:
5258 webkit_download_cancel(d->download);
5259 break;
5260 case XT_XTP_DL_REMOVE:
5261 webkit_download_cancel(d->download); /* just incase */
5262 g_object_unref(d->download);
5263 RB_REMOVE(download_list, &downloads, d);
5264 break;
5265 case XT_XTP_DL_LIST:
5266 /* Nothing */
5267 break;
5268 default:
5269 show_oops(t, "%s: unknown command", __func__);
5270 break;
5272 xtp_page_dl(t, NULL);
5276 * Actions on history, only does one thing for now, but
5277 * we provide the function for future actions
5279 void
5280 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5282 struct history *h, *next;
5283 int i = 1;
5285 switch (cmd) {
5286 case XT_XTP_HL_REMOVE:
5287 /* walk backwards, as listed in reverse */
5288 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5289 next = RB_PREV(history_list, &hl, h);
5290 if (id == i) {
5291 RB_REMOVE(history_list, &hl, h);
5292 g_free((gpointer) h->title);
5293 g_free((gpointer) h->uri);
5294 g_free(h);
5295 break;
5297 i++;
5299 break;
5300 case XT_XTP_HL_LIST:
5301 /* Nothing - just xtp_page_hl() below */
5302 break;
5303 default:
5304 show_oops(t, "%s: unknown command", __func__);
5305 break;
5308 xtp_page_hl(t, NULL);
5311 /* remove a favorite */
5312 void
5313 remove_favorite(struct tab *t, int index)
5315 char file[PATH_MAX], *title, *uri = NULL;
5316 char *new_favs, *tmp;
5317 FILE *f;
5318 int i;
5319 size_t len, lineno;
5321 /* open favorites */
5322 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5324 if ((f = fopen(file, "r")) == NULL) {
5325 show_oops(t, "%s: can't open favorites: %s",
5326 __func__, strerror(errno));
5327 return;
5330 /* build a string which will become the new favroites file */
5331 new_favs = g_strdup("");
5333 for (i = 1;;) {
5334 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5335 if (feof(f) || ferror(f))
5336 break;
5337 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5338 if (len == 0) {
5339 free(title);
5340 title = NULL;
5341 continue;
5344 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5345 if (feof(f) || ferror(f)) {
5346 show_oops(t, "%s: can't parse favorites %s",
5347 __func__, strerror(errno));
5348 goto clean;
5352 /* as long as this isn't the one we are deleting add to file */
5353 if (i != index) {
5354 tmp = new_favs;
5355 new_favs = g_strdup_printf("%s%s\n%s\n",
5356 new_favs, title, uri);
5357 g_free(tmp);
5360 free(uri);
5361 uri = NULL;
5362 free(title);
5363 title = NULL;
5364 i++;
5366 fclose(f);
5368 /* write back new favorites file */
5369 if ((f = fopen(file, "w")) == NULL) {
5370 show_oops(t, "%s: can't open favorites: %s",
5371 __func__, strerror(errno));
5372 goto clean;
5375 fwrite(new_favs, strlen(new_favs), 1, f);
5376 fclose(f);
5378 clean:
5379 if (uri)
5380 free(uri);
5381 if (title)
5382 free(title);
5384 g_free(new_favs);
5387 void
5388 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5390 switch (cmd) {
5391 case XT_XTP_FL_LIST:
5392 /* nothing, just the below call to xtp_page_fl() */
5393 break;
5394 case XT_XTP_FL_REMOVE:
5395 remove_favorite(t, arg);
5396 break;
5397 default:
5398 show_oops(t, "%s: invalid favorites command", __func__);
5399 break;
5402 xtp_page_fl(t, NULL);
5405 void
5406 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5408 switch (cmd) {
5409 case XT_XTP_CL_LIST:
5410 /* nothing, just xtp_page_cl() */
5411 break;
5412 case XT_XTP_CL_REMOVE:
5413 remove_cookie(arg);
5414 break;
5415 default:
5416 show_oops(t, "%s: unknown cookie xtp command", __func__);
5417 break;
5420 xtp_page_cl(t, NULL);
5423 /* link an XTP class to it's session key and handler function */
5424 struct xtp_despatch {
5425 uint8_t xtp_class;
5426 char **session_key;
5427 void (*handle_func)(struct tab *, uint8_t, int);
5430 struct xtp_despatch xtp_despatches[] = {
5431 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5432 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5433 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5434 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5435 { XT_XTP_INVALID, NULL, NULL }
5439 * is the url xtp protocol? (xxxt://)
5440 * if so, parse and despatch correct bahvior
5443 parse_xtp_url(struct tab *t, const char *url)
5445 char *dup = NULL, *p, *last;
5446 uint8_t n_tokens = 0;
5447 char *tokens[4] = {NULL, NULL, NULL, ""};
5448 struct xtp_despatch *dsp, *dsp_match = NULL;
5449 uint8_t req_class;
5450 int ret = FALSE;
5453 * tokens array meaning:
5454 * tokens[0] = class
5455 * tokens[1] = session key
5456 * tokens[2] = action
5457 * tokens[3] = optional argument
5460 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5462 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5463 goto clean;
5465 dup = g_strdup(url + strlen(XT_XTP_STR));
5467 /* split out the url */
5468 for ((p = strtok_r(dup, "/", &last)); p;
5469 (p = strtok_r(NULL, "/", &last))) {
5470 if (n_tokens < 4)
5471 tokens[n_tokens++] = p;
5474 /* should be atleast three fields 'class/seskey/command/arg' */
5475 if (n_tokens < 3)
5476 goto clean;
5478 dsp = xtp_despatches;
5479 req_class = atoi(tokens[0]);
5480 while (dsp->xtp_class) {
5481 if (dsp->xtp_class == req_class) {
5482 dsp_match = dsp;
5483 break;
5485 dsp++;
5488 /* did we find one atall? */
5489 if (dsp_match == NULL) {
5490 show_oops(t, "%s: no matching xtp despatch found", __func__);
5491 goto clean;
5494 /* check session key and call despatch function */
5495 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5496 ret = TRUE; /* all is well, this was a valid xtp request */
5497 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5500 clean:
5501 if (dup)
5502 g_free(dup);
5504 return (ret);
5509 void
5510 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5512 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5514 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5516 if (t == NULL) {
5517 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5518 return;
5521 if (uri == NULL) {
5522 show_oops(t, "activate_uri_entry_cb no uri");
5523 return;
5526 uri += strspn(uri, "\t ");
5528 /* if xxxt:// treat specially */
5529 if (parse_xtp_url(t, uri))
5530 return;
5532 /* otherwise continue to load page normally */
5533 load_uri(t, (gchar *)uri);
5534 focus_webview(t);
5537 void
5538 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5540 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5541 char *newuri = NULL;
5542 gchar *enc_search;
5544 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5546 if (t == NULL) {
5547 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5548 return;
5551 if (search_string == NULL) {
5552 show_oops(t, "no search_string");
5553 return;
5556 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5557 newuri = g_strdup_printf(search_string, enc_search);
5558 g_free(enc_search);
5560 webkit_web_view_load_uri(t->wv, newuri);
5561 focus_webview(t);
5563 if (newuri)
5564 g_free(newuri);
5567 void
5568 check_and_set_js(const gchar *uri, struct tab *t)
5570 struct domain *d = NULL;
5571 int es = 0;
5573 if (uri == NULL || t == NULL)
5574 return;
5576 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5577 es = 0;
5578 else
5579 es = 1;
5581 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5582 es ? "enable" : "disable", uri);
5584 g_object_set(G_OBJECT(t->settings),
5585 "enable-scripts", es, (char *)NULL);
5586 g_object_set(G_OBJECT(t->settings),
5587 "javascript-can-open-windows-automatically", es, (char *)NULL);
5588 webkit_web_view_set_settings(t->wv, t->settings);
5590 button_set_stockid(t->js_toggle,
5591 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5594 void
5595 show_ca_status(struct tab *t, const char *uri)
5597 WebKitWebFrame *frame;
5598 WebKitWebDataSource *source;
5599 WebKitNetworkRequest *request;
5600 SoupMessage *message;
5601 GdkColor color;
5602 gchar *col_str = XT_COLOR_WHITE;
5603 int r;
5605 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5606 ssl_strict_certs, ssl_ca_file, uri);
5608 if (uri == NULL)
5609 goto done;
5610 if (ssl_ca_file == NULL) {
5611 if (g_str_has_prefix(uri, "http://"))
5612 goto done;
5613 if (g_str_has_prefix(uri, "https://")) {
5614 col_str = XT_COLOR_RED;
5615 goto done;
5617 return;
5619 if (g_str_has_prefix(uri, "http://") ||
5620 !g_str_has_prefix(uri, "https://"))
5621 goto done;
5623 frame = webkit_web_view_get_main_frame(t->wv);
5624 source = webkit_web_frame_get_data_source(frame);
5625 request = webkit_web_data_source_get_request(source);
5626 message = webkit_network_request_get_message(request);
5628 if (message && (soup_message_get_flags(message) &
5629 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5630 col_str = XT_COLOR_GREEN;
5631 goto done;
5632 } else {
5633 r = load_compare_cert(t, NULL);
5634 if (r == 0)
5635 col_str = XT_COLOR_BLUE;
5636 else if (r == 1)
5637 col_str = XT_COLOR_YELLOW;
5638 else
5639 col_str = XT_COLOR_RED;
5640 goto done;
5642 done:
5643 if (col_str) {
5644 gdk_color_parse(col_str, &color);
5645 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5647 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5648 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5649 &color);
5650 gdk_color_parse(XT_COLOR_BLACK, &color);
5651 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5652 &color);
5653 } else {
5654 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5655 &color);
5656 gdk_color_parse(XT_COLOR_BLACK, &color);
5657 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5658 &color);
5663 void
5664 free_favicon(struct tab *t)
5666 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5667 __func__, t->icon_download, t->icon_request);
5669 if (t->icon_request)
5670 g_object_unref(t->icon_request);
5671 if (t->icon_dest_uri)
5672 g_free(t->icon_dest_uri);
5674 t->icon_request = NULL;
5675 t->icon_dest_uri = NULL;
5678 void
5679 xt_icon_from_name(struct tab *t, gchar *name)
5681 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5682 GTK_ENTRY_ICON_PRIMARY, "text-html");
5683 if (show_url == 0)
5684 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5685 GTK_ENTRY_ICON_PRIMARY, "text-html");
5686 else
5687 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5688 GTK_ENTRY_ICON_PRIMARY, NULL);
5691 void
5692 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5694 GdkPixbuf *pb_scaled;
5696 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5697 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16, GDK_INTERP_BILINEAR);
5698 else
5699 pb_scaled = pb;
5701 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5702 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5703 if (show_url == 0)
5704 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5705 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5706 else
5707 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5708 GTK_ENTRY_ICON_PRIMARY, NULL);
5710 if (pb_scaled != pb)
5711 g_object_unref(pb_scaled);
5714 void
5715 xt_icon_from_file(struct tab *t, char *file)
5717 GdkPixbuf *pb;
5719 if (g_str_has_prefix(file, "file://"))
5720 file += strlen("file://");
5722 pb = gdk_pixbuf_new_from_file(file, NULL);
5723 if (pb) {
5724 xt_icon_from_pixbuf(t, pb);
5725 g_object_unref(pb);
5726 } else
5727 xt_icon_from_name(t, "text-html");
5730 gboolean
5731 is_valid_icon(char *file)
5733 gboolean valid = 0;
5734 const char *mime_type;
5735 GFileInfo *fi;
5736 GFile *gf;
5738 gf = g_file_new_for_path(file);
5739 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5740 NULL, NULL);
5741 mime_type = g_file_info_get_content_type(fi);
5742 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5743 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5744 g_strcmp0(mime_type, "image/png") == 0 ||
5745 g_strcmp0(mime_type, "image/gif") == 0 ||
5746 g_strcmp0(mime_type, "application/octet-stream") == 0;
5747 g_object_unref(fi);
5748 g_object_unref(gf);
5750 return (valid);
5753 void
5754 set_favicon_from_file(struct tab *t, char *file)
5756 struct stat sb;
5758 if (t == NULL || file == NULL)
5759 return;
5761 if (g_str_has_prefix(file, "file://"))
5762 file += strlen("file://");
5763 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5765 if (!stat(file, &sb)) {
5766 if (sb.st_size == 0 || !is_valid_icon(file)) {
5767 /* corrupt icon so trash it */
5768 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5769 __func__, file);
5770 unlink(file);
5771 /* no need to set icon to default here */
5772 return;
5775 xt_icon_from_file(t, file);
5778 void
5779 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5780 WebKitWebView *wv)
5782 WebKitDownloadStatus status = webkit_download_get_status(download);
5783 struct tab *tt = NULL, *t = NULL;
5786 * find the webview instead of passing in the tab as it could have been
5787 * deleted from underneath us.
5789 TAILQ_FOREACH(tt, &tabs, entry) {
5790 if (tt->wv == wv) {
5791 t = tt;
5792 break;
5795 if (t == NULL)
5796 return;
5798 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5799 __func__, t->tab_id, status);
5801 switch (status) {
5802 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5803 /* -1 */
5804 t->icon_download = NULL;
5805 free_favicon(t);
5806 break;
5807 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5808 /* 0 */
5809 break;
5810 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5811 /* 1 */
5812 break;
5813 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5814 /* 2 */
5815 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5816 __func__, t->tab_id);
5817 t->icon_download = NULL;
5818 free_favicon(t);
5819 break;
5820 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5821 /* 3 */
5823 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5824 __func__, t->icon_dest_uri);
5825 set_favicon_from_file(t, t->icon_dest_uri);
5826 /* these will be freed post callback */
5827 t->icon_request = NULL;
5828 t->icon_download = NULL;
5829 break;
5830 default:
5831 break;
5835 void
5836 abort_favicon_download(struct tab *t)
5838 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5840 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5841 if (t->icon_download) {
5842 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5843 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5844 webkit_download_cancel(t->icon_download);
5845 t->icon_download = NULL;
5846 } else
5847 free_favicon(t);
5850 xt_icon_from_name(t, "text-html");
5853 void
5854 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5856 GdkPixbuf *pb;
5857 gchar *name_hash, file[PATH_MAX];
5858 struct stat sb;
5860 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5862 if (uri == NULL || t == NULL)
5863 return;
5865 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5866 /* take icon from WebKitIconDatabase */
5867 pb = webkit_web_view_get_icon_pixbuf(wv);
5868 if (pb) {
5869 xt_icon_from_pixbuf(t, pb);
5870 g_object_unref(pb);
5871 } else
5872 xt_icon_from_name(t, "text-html");
5874 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5875 /* download icon to cache dir */
5876 if (t->icon_request) {
5877 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5878 return;
5881 /* check to see if we got the icon in cache */
5882 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5883 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5884 g_free(name_hash);
5886 if (!stat(file, &sb)) {
5887 if (sb.st_size > 0) {
5888 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5889 __func__, file);
5890 set_favicon_from_file(t, file);
5891 return;
5894 /* corrupt icon so trash it */
5895 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5896 __func__, file);
5897 unlink(file);
5900 /* create download for icon */
5901 t->icon_request = webkit_network_request_new(uri);
5902 if (t->icon_request == NULL) {
5903 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5904 __func__, uri);
5905 return;
5908 t->icon_download = webkit_download_new(t->icon_request);
5909 if (t->icon_download == NULL)
5910 return;
5912 /* we have to free icon_dest_uri later */
5913 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5914 webkit_download_set_destination_uri(t->icon_download,
5915 t->icon_dest_uri);
5917 if (webkit_download_get_status(t->icon_download) ==
5918 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5919 g_object_unref(t->icon_request);
5920 g_free(t->icon_dest_uri);
5921 t->icon_request = NULL;
5922 t->icon_dest_uri = NULL;
5923 return;
5926 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5927 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5929 webkit_download_start(t->icon_download);
5933 void
5934 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5936 const gchar *set = NULL, *uri = NULL, *title = NULL;
5937 struct history *h, find;
5938 const gchar *s_loading;
5939 struct karg a;
5941 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5942 webkit_web_view_get_load_status(wview), get_uri(t) ? get_uri(t) : "NOTHING");
5944 if (t == NULL) {
5945 show_oops(NULL, "notify_load_status_cb invalid parameters");
5946 return;
5949 switch (webkit_web_view_get_load_status(wview)) {
5950 case WEBKIT_LOAD_PROVISIONAL:
5951 /* 0 */
5952 abort_favicon_download(t);
5953 #if GTK_CHECK_VERSION(2, 20, 0)
5954 gtk_widget_show(t->spinner);
5955 gtk_spinner_start(GTK_SPINNER(t->spinner));
5956 #endif
5957 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5959 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5961 /* take focus if we are visible */
5962 focus_webview(t);
5963 t->focus_wv = 1;
5965 break;
5967 case WEBKIT_LOAD_COMMITTED:
5968 /* 1 */
5969 if ((uri = get_uri(t)) != NULL) {
5970 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5972 if (t->status) {
5973 g_free(t->status);
5974 t->status = NULL;
5976 set_status(t, (char *)uri, XT_STATUS_LOADING);
5979 /* check if js white listing is enabled */
5980 if (enable_js_whitelist) {
5981 uri = get_uri(t);
5982 check_and_set_js(uri, t);
5985 if (t->styled)
5986 apply_style(t);
5988 show_ca_status(t, uri);
5990 /* we know enough to autosave the session */
5991 if (session_autosave) {
5992 a.s = NULL;
5993 save_tabs(t, &a);
5995 break;
5997 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5998 /* 3 */
5999 break;
6001 case WEBKIT_LOAD_FINISHED:
6002 /* 2 */
6003 uri = get_uri(t);
6004 if (uri == NULL)
6005 return;
6007 if (!strncmp(uri, "http://", strlen("http://")) ||
6008 !strncmp(uri, "https://", strlen("https://")) ||
6009 !strncmp(uri, "file://", strlen("file://"))) {
6010 find.uri = uri;
6011 h = RB_FIND(history_list, &hl, &find);
6012 if (!h) {
6013 title = webkit_web_view_get_title(wview);
6014 set = title ? title: uri;
6015 h = g_malloc(sizeof *h);
6016 h->uri = g_strdup(uri);
6017 h->title = g_strdup(set);
6018 RB_INSERT(history_list, &hl, h);
6019 completion_add_uri(h->uri);
6020 update_history_tabs(NULL);
6024 set_status(t, (char *)uri, XT_STATUS_URI);
6025 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6026 case WEBKIT_LOAD_FAILED:
6027 /* 4 */
6028 #endif
6029 #if GTK_CHECK_VERSION(2, 20, 0)
6030 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6031 gtk_widget_hide(t->spinner);
6032 #endif
6033 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
6034 if (s_loading && !strcmp(s_loading, "Loading"))
6035 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6036 default:
6037 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6038 break;
6041 if (t->item)
6042 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6043 else
6044 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6045 webkit_web_view_can_go_back(wview));
6047 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6048 webkit_web_view_can_go_forward(wview));
6051 void
6052 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6054 const gchar *set = NULL, *title = NULL;
6056 title = webkit_web_view_get_title(wview);
6057 set = title ? title : get_uri(t);
6058 if (set) {
6059 gtk_label_set_text(GTK_LABEL(t->label), set);
6060 gtk_window_set_title(GTK_WINDOW(main_window), set);
6061 } else {
6062 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6063 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6067 void
6068 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6070 run_script(t, JS_HINTING);
6073 void
6074 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6076 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6077 progress == 100 ? 0 : (double)progress / 100);
6078 if (show_url == 0) {
6079 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
6080 progress == 100 ? 0 : (double)progress / 100);
6085 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6086 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6087 WebKitWebPolicyDecision *pd, struct tab *t)
6089 char *uri;
6090 WebKitWebNavigationReason reason;
6091 struct domain *d = NULL;
6093 if (t == NULL) {
6094 show_oops(NULL, "webview_npd_cb invalid parameters");
6095 return (FALSE);
6098 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6099 t->ctrl_click,
6100 webkit_network_request_get_uri(request));
6102 uri = (char *)webkit_network_request_get_uri(request);
6104 /* if this is an xtp url, we don't load anything else */
6105 if (parse_xtp_url(t, uri))
6106 return (TRUE);
6108 if (t->ctrl_click) {
6109 t->ctrl_click = 0;
6110 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6111 webkit_web_policy_decision_ignore(pd);
6112 return (TRUE); /* we made the decission */
6116 * This is a little hairy but it comes down to this:
6117 * when we run in whitelist mode we have to assist the browser in
6118 * opening the URL that it would have opened in a new tab.
6120 reason = webkit_web_navigation_action_get_reason(na);
6121 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6122 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6123 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6124 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6125 load_uri(t, uri);
6126 webkit_web_policy_decision_use(pd);
6127 return (TRUE); /* we made the decision */
6130 return (FALSE);
6133 WebKitWebView *
6134 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6136 struct tab *tt;
6137 struct domain *d = NULL;
6138 const gchar *uri;
6139 WebKitWebView *webview = NULL;
6141 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6142 webkit_web_view_get_uri(wv));
6144 if (tabless) {
6145 /* open in current tab */
6146 webview = t->wv;
6147 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6148 uri = webkit_web_view_get_uri(wv);
6149 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6150 return (NULL);
6152 tt = create_new_tab(NULL, NULL, 1, -1);
6153 webview = tt->wv;
6154 } else if (enable_scripts == 1) {
6155 tt = create_new_tab(NULL, NULL, 1, -1);
6156 webview = tt->wv;
6159 return (webview);
6162 gboolean
6163 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6165 const gchar *uri;
6166 struct domain *d = NULL;
6168 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6170 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6171 uri = webkit_web_view_get_uri(wv);
6172 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6173 return (FALSE);
6175 delete_tab(t);
6176 } else if (enable_scripts == 1)
6177 delete_tab(t);
6179 return (TRUE);
6183 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6185 /* we can not eat the event without throwing gtk off so defer it */
6187 /* catch middle click */
6188 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6189 t->ctrl_click = 1;
6190 goto done;
6193 /* catch ctrl click */
6194 if (e->type == GDK_BUTTON_RELEASE &&
6195 CLEAN(e->state) == GDK_CONTROL_MASK)
6196 t->ctrl_click = 1;
6197 else
6198 t->ctrl_click = 0;
6199 done:
6200 return (XT_CB_PASSTHROUGH);
6204 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6206 struct mime_type *m;
6208 m = find_mime_type(mime_type);
6209 if (m == NULL)
6210 return (1);
6211 if (m->mt_download)
6212 return (1);
6214 switch (fork()) {
6215 case -1:
6216 show_oops(t, "can't fork mime handler");
6217 /* NOTREACHED */
6218 case 0:
6219 break;
6220 default:
6221 return (0);
6224 /* child */
6225 execlp(m->mt_action, m->mt_action,
6226 webkit_network_request_get_uri(request), (void *)NULL);
6228 _exit(0);
6230 /* NOTREACHED */
6231 return (0);
6234 const gchar *
6235 get_mime_type(char *file)
6237 const char *mime_type;
6238 GFileInfo *fi;
6239 GFile *gf;
6241 if (g_str_has_prefix(file, "file://"))
6242 file += strlen("file://");
6244 gf = g_file_new_for_path(file);
6245 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6246 NULL, NULL);
6247 mime_type = g_file_info_get_content_type(fi);
6248 g_object_unref(fi);
6249 g_object_unref(gf);
6251 return (mime_type);
6255 run_download_mimehandler(char *mime_type, char *file)
6257 struct mime_type *m;
6259 m = find_mime_type(mime_type);
6260 if (m == NULL)
6261 return (1);
6263 switch (fork()) {
6264 case -1:
6265 show_oops(NULL, "can't fork download mime handler");
6266 return (1);
6267 /* NOTREACHED */
6268 case 0:
6269 break;
6270 default:
6271 return (0);
6274 /* child */
6275 if (g_str_has_prefix(file, "file://"))
6276 file += strlen("file://");
6277 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6279 _exit(0);
6281 /* NOTREACHED */
6282 return (0);
6285 void
6286 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6287 WebKitWebView *wv)
6289 WebKitDownloadStatus status;
6290 const gchar *file = NULL, *mime = NULL;
6292 if (download == NULL)
6293 return;
6294 status = webkit_download_get_status(download);
6295 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6296 return;
6298 file = webkit_download_get_destination_uri(download);
6299 if (file == NULL)
6300 return;
6301 mime = get_mime_type((char *)file);
6302 if (mime == NULL)
6303 return;
6305 run_download_mimehandler((char *)mime, (char *)file);
6309 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6310 WebKitNetworkRequest *request, char *mime_type,
6311 WebKitWebPolicyDecision *decision, struct tab *t)
6313 if (t == NULL) {
6314 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6315 return (FALSE);
6318 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6319 t->tab_id, mime_type);
6321 if (run_mimehandler(t, mime_type, request) == 0) {
6322 webkit_web_policy_decision_ignore(decision);
6323 focus_webview(t);
6324 return (TRUE);
6327 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6328 webkit_web_policy_decision_download(decision);
6329 return (TRUE);
6332 return (FALSE);
6336 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6337 struct tab *t)
6339 struct stat sb;
6340 const gchar *suggested_name;
6341 gchar *filename = NULL;
6342 char *uri = NULL;
6343 struct download *download_entry;
6344 int i, ret = TRUE;
6346 if (wk_download == NULL || t == NULL) {
6347 show_oops(NULL, "%s invalid parameters", __func__);
6348 return (FALSE);
6351 suggested_name = webkit_download_get_suggested_filename(wk_download);
6352 if (suggested_name == NULL)
6353 return (FALSE); /* abort download */
6355 i = 0;
6356 do {
6357 if (filename) {
6358 g_free(filename);
6359 filename = NULL;
6361 if (i) {
6362 g_free(uri);
6363 uri = NULL;
6364 filename = g_strdup_printf("%d%s", i, suggested_name);
6366 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6367 filename : suggested_name);
6368 i++;
6369 } while (!stat(uri + strlen("file://"), &sb));
6371 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6372 "local %s\n", __func__, t->tab_id, filename, uri);
6374 webkit_download_set_destination_uri(wk_download, uri);
6376 if (webkit_download_get_status(wk_download) ==
6377 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6378 show_oops(t, "%s: download failed to start", __func__);
6379 ret = FALSE;
6380 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6381 } else {
6382 /* connect "download first" mime handler */
6383 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6384 G_CALLBACK(download_status_changed_cb), NULL);
6386 download_entry = g_malloc(sizeof(struct download));
6387 download_entry->download = wk_download;
6388 download_entry->tab = t;
6389 download_entry->id = next_download_id++;
6390 RB_INSERT(download_list, &downloads, download_entry);
6391 /* get from history */
6392 g_object_ref(wk_download);
6393 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6394 show_oops(t, "Download of '%s' started...",
6395 basename((char *)webkit_download_get_destination_uri(wk_download)));
6398 if (uri)
6399 g_free(uri);
6401 if (filename)
6402 g_free(filename);
6404 /* sync other download manager tabs */
6405 update_download_tabs(NULL);
6408 * NOTE: never redirect/render the current tab before this
6409 * function returns. This will cause the download to never start.
6411 return (ret); /* start download */
6414 void
6415 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6417 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6419 if (t == NULL) {
6420 show_oops(NULL, "webview_hover_cb");
6421 return;
6424 if (uri)
6425 set_status(t, uri, XT_STATUS_LINK);
6426 else {
6427 if (t->status)
6428 set_status(t, t->status, XT_STATUS_NOTHING);
6432 gboolean
6433 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6435 struct key_binding *k;
6437 TAILQ_FOREACH(k, &kbl, entry)
6438 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6439 if (k->mask == 0) {
6440 if ((e->state & (CTRL | MOD1)) == 0)
6441 return (cmd_execute(t, k->cmd));
6442 } else if ((e->state & k->mask) == k->mask) {
6443 return (cmd_execute(t, k->cmd));
6447 return (XT_CB_PASSTHROUGH);
6451 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6453 char s[2], buf[128];
6454 const char *errstr = NULL;
6455 long long link;
6457 /* don't use w directly; use t->whatever instead */
6459 if (t == NULL) {
6460 show_oops(NULL, "wv_keypress_after_cb");
6461 return (XT_CB_PASSTHROUGH);
6464 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6465 e->keyval, e->state, t);
6467 if (t->hints_on) {
6468 /* ESC */
6469 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6470 disable_hints(t);
6471 return (XT_CB_HANDLED);
6474 /* RETURN */
6475 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6476 link = strtonum(t->hint_num, 1, 1000, &errstr);
6477 if (errstr) {
6478 /* we have a string */
6479 } else {
6480 /* we have a number */
6481 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6482 t->hint_num);
6483 run_script(t, buf);
6485 disable_hints(t);
6488 /* BACKSPACE */
6489 /* XXX unfuck this */
6490 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6491 if (t->hint_mode == XT_HINT_NUMERICAL) {
6492 /* last input was numerical */
6493 int l;
6494 l = strlen(t->hint_num);
6495 if (l > 0) {
6496 l--;
6497 if (l == 0) {
6498 disable_hints(t);
6499 enable_hints(t);
6500 } else {
6501 t->hint_num[l] = '\0';
6502 goto num;
6505 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6506 /* last input was alphanumerical */
6507 int l;
6508 l = strlen(t->hint_buf);
6509 if (l > 0) {
6510 l--;
6511 if (l == 0) {
6512 disable_hints(t);
6513 enable_hints(t);
6514 } else {
6515 t->hint_buf[l] = '\0';
6516 goto anum;
6519 } else {
6520 /* bogus */
6521 disable_hints(t);
6525 /* numerical input */
6526 if (CLEAN(e->state) == 0 &&
6527 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6528 snprintf(s, sizeof s, "%c", e->keyval);
6529 strlcat(t->hint_num, s, sizeof t->hint_num);
6530 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6531 t->hint_num);
6532 num:
6533 link = strtonum(t->hint_num, 1, 1000, &errstr);
6534 if (errstr) {
6535 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6536 disable_hints(t);
6537 } else {
6538 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6539 t->hint_num);
6540 t->hint_mode = XT_HINT_NUMERICAL;
6541 run_script(t, buf);
6544 /* empty the counter buffer */
6545 bzero(t->hint_buf, sizeof t->hint_buf);
6546 return (XT_CB_HANDLED);
6549 /* alphanumerical input */
6550 if (
6551 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6552 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6553 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6554 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6555 snprintf(s, sizeof s, "%c", e->keyval);
6556 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6557 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6558 t->hint_buf);
6559 anum:
6560 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6561 run_script(t, buf);
6563 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6564 t->hint_buf);
6565 t->hint_mode = XT_HINT_ALPHANUM;
6566 run_script(t, buf);
6568 /* empty the counter buffer */
6569 bzero(t->hint_num, sizeof t->hint_num);
6570 return (XT_CB_HANDLED);
6573 return (XT_CB_HANDLED);
6574 } else {
6575 /* prefix input*/
6576 snprintf(s, sizeof s, "%c", e->keyval);
6577 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6578 cmd_prefix = 10 * cmd_prefix + atoi(s);
6582 return (handle_keypress(t, e, 0));
6586 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6588 hide_oops(t);
6590 /* Hide buffers, if they are visible, with escape. */
6591 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6592 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6593 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6594 hide_buffers(t);
6595 return (XT_CB_HANDLED);
6598 return (XT_CB_PASSTHROUGH);
6602 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6604 const gchar *c = gtk_entry_get_text(w);
6605 GdkColor color;
6606 int forward = TRUE;
6608 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6609 e->keyval, e->state, t);
6611 if (t == NULL) {
6612 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6613 return (XT_CB_PASSTHROUGH);
6616 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6617 e->keyval, e->state, t);
6619 if (c[0] == ':')
6620 goto done;
6621 if (strlen(c) == 1) {
6622 webkit_web_view_unmark_text_matches(t->wv);
6623 goto done;
6626 if (c[0] == '/')
6627 forward = TRUE;
6628 else if (c[0] == '?')
6629 forward = FALSE;
6630 else
6631 goto done;
6633 /* search */
6634 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6635 FALSE) {
6636 /* not found, mark red */
6637 gdk_color_parse(XT_COLOR_RED, &color);
6638 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6639 /* unmark and remove selection */
6640 webkit_web_view_unmark_text_matches(t->wv);
6641 /* my kingdom for a way to unselect text in webview */
6642 } else {
6643 /* found, highlight all */
6644 webkit_web_view_unmark_text_matches(t->wv);
6645 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6646 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6647 gdk_color_parse(XT_COLOR_WHITE, &color);
6648 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6650 done:
6651 return (XT_CB_PASSTHROUGH);
6654 gboolean
6655 match_uri(const gchar *uri, const gchar *key) {
6656 gchar *voffset;
6657 size_t len;
6658 gboolean match = FALSE;
6660 len = strlen(key);
6662 if (!strncmp(key, uri, len))
6663 match = TRUE;
6664 else {
6665 voffset = strstr(uri, "/") + 2;
6666 if (!strncmp(key, voffset, len))
6667 match = TRUE;
6668 else if (g_str_has_prefix(voffset, "www.")) {
6669 voffset = voffset + strlen("www.");
6670 if (!strncmp(key, voffset, len))
6671 match = TRUE;
6675 return (match);
6678 void
6679 cmd_getlist(int id, char *key)
6681 int i, dep, c = 0;
6682 struct history *h;
6684 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6685 RB_FOREACH_REVERSE(h, history_list, &hl)
6686 if (match_uri(h->uri, key)) {
6687 cmd_status.list[c] = (char *)h->uri;
6688 if (++c > 255)
6689 break;
6692 cmd_status.len = c;
6693 return;
6696 dep = (id == -1) ? 0 : cmds[id].level + 1;
6698 for (i = id + 1; i < LENGTH(cmds); i++) {
6699 if(cmds[i].level < dep)
6700 break;
6701 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6702 cmd_status.list[c++] = cmds[i].cmd;
6706 cmd_status.len = c;
6709 char *
6710 cmd_getnext(int dir)
6712 cmd_status.index += dir;
6714 if (cmd_status.index < 0)
6715 cmd_status.index = cmd_status.len - 1;
6716 else if (cmd_status.index >= cmd_status.len)
6717 cmd_status.index = 0;
6719 return cmd_status.list[cmd_status.index];
6723 cmd_tokenize(char *s, char *tokens[])
6725 int i = 0;
6726 char *tok, *last;
6727 size_t len = strlen(s);
6728 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6730 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6731 tokens[i] = tok;
6733 if (blank && i < 3)
6734 tokens[i++] = "";
6736 return (i);
6739 void
6740 cmd_complete(struct tab *t, char *str, int dir)
6742 GtkEntry *w = GTK_ENTRY(t->cmd);
6743 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6744 char *tok, *match, *s = g_strdup(str);
6745 char *tokens[3];
6746 char res[XT_MAX_URL_LENGTH + 32] = ":";
6747 char *sc = s;
6749 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6751 /* copy prefix*/
6752 for (i = 0; isdigit(s[i]); i++)
6753 res[i + 1] = s[i];
6755 for (; isspace(s[i]); i++)
6756 res[i + 1] = s[i];
6758 s += i;
6760 levels = cmd_tokenize(s, tokens);
6762 for (i = 0; i < levels - 1; i++) {
6763 tok = tokens[i];
6764 matchcount = 0;
6765 for (j = c; j < LENGTH(cmds); j++) {
6766 if (cmds[j].level < dep)
6767 break;
6768 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6769 matchcount++;
6770 c = j + 1;
6771 if (strlen(tok) == strlen(cmds[j].cmd)) {
6772 matchcount = 1;
6773 break;
6778 if (matchcount == 1) {
6779 strlcat(res, tok, sizeof res);
6780 strlcat(res, " ", sizeof res);
6781 dep++;
6782 } else {
6783 g_free(sc);
6784 return;
6787 parent = c - 1;
6790 if (cmd_status.index == -1)
6791 cmd_getlist(parent, tokens[i]);
6793 if (cmd_status.len > 0) {
6794 match = cmd_getnext(dir);
6795 strlcat(res, match, sizeof res);
6796 gtk_entry_set_text(w, res);
6797 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6800 g_free(sc);
6803 gboolean
6804 cmd_execute(struct tab *t, char *str)
6806 struct cmd *cmd = NULL;
6807 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6808 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6809 struct karg arg = {0, NULL, -1};
6810 int rv = XT_CB_PASSTHROUGH;
6812 sc = s;
6814 /* copy prefix*/
6815 for (j = 0; j<3 && isdigit(s[j]); j++)
6816 prefixstr[j]=s[j];
6818 prefixstr[j]='\0';
6820 s += j;
6821 while (isspace(s[0]))
6822 s++;
6824 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6825 prefix = atoi(prefixstr);
6826 else
6827 s = sc;
6829 for (tok = strtok_r(s, " ", &last); tok;
6830 tok = strtok_r(NULL, " ", &last)) {
6831 matchcount = 0;
6832 for (j = c; j < LENGTH(cmds); j++) {
6833 if (cmds[j].level < dep)
6834 break;
6835 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6836 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6837 matchcount++;
6838 c = j + 1;
6839 cmd = &cmds[j];
6840 if (len == strlen(cmds[j].cmd)) {
6841 matchcount = 1;
6842 break;
6846 if (matchcount == 1) {
6847 if (cmd->type > 0)
6848 goto execute_cmd;
6849 dep++;
6850 } else {
6851 show_oops(t, "Invalid command: %s", str);
6852 goto done;
6855 execute_cmd:
6856 arg.i = cmd->arg;
6858 if (prefix != -1)
6859 arg.p = prefix;
6860 else if (cmd_prefix > 0)
6861 arg.p = cmd_prefix;
6863 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6864 show_oops(t, "No prefix allowed: %s", str);
6865 goto done;
6867 if (cmd->type > 1)
6868 arg.s = last ? g_strdup(last) : g_strdup("");
6869 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6870 arg.p = atoi(arg.s);
6871 if (arg.p <= 0) {
6872 if (arg.s[0]=='0')
6873 show_oops(t, "Zero count");
6874 else
6875 show_oops(t, "Trailing characters");
6876 goto done;
6880 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6882 cmd->func(t, &arg);
6884 rv = XT_CB_HANDLED;
6885 done:
6886 if (j > 0)
6887 cmd_prefix = 0;
6888 g_free(sc);
6889 if (arg.s)
6890 g_free(arg.s);
6892 return (rv);
6896 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6898 if (t == NULL) {
6899 show_oops(NULL, "entry_key_cb invalid parameters");
6900 return (XT_CB_PASSTHROUGH);
6903 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6904 e->keyval, e->state, t);
6906 hide_oops(t);
6908 if (e->keyval == GDK_Escape) {
6909 /* don't use focus_webview(t) because we want to type :cmds */
6910 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6913 return (handle_keypress(t, e, 1));
6917 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6919 int rv = XT_CB_HANDLED;
6920 const gchar *c = gtk_entry_get_text(w);
6922 if (t == NULL) {
6923 show_oops(NULL, "cmd_keypress_cb parameters");
6924 return (XT_CB_PASSTHROUGH);
6927 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6928 e->keyval, e->state, t);
6930 /* sanity */
6931 if (c == NULL)
6932 e->keyval = GDK_Escape;
6933 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6934 e->keyval = GDK_Escape;
6936 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6937 cmd_status.index = -1;
6939 switch (e->keyval) {
6940 case GDK_Tab:
6941 if (c[0] == ':')
6942 cmd_complete(t, (char *)&c[1], 1);
6943 goto done;
6944 case GDK_ISO_Left_Tab:
6945 if (c[0] == ':')
6946 cmd_complete(t, (char *)&c[1], -1);
6948 goto done;
6949 case GDK_BackSpace:
6950 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6951 break;
6952 /* FALLTHROUGH */
6953 case GDK_Escape:
6954 hide_cmd(t);
6955 focus_webview(t);
6957 /* cancel search */
6958 if (c != NULL && (c[0] == '/' || c[0] == '?'))
6959 webkit_web_view_unmark_text_matches(t->wv);
6960 goto done;
6963 rv = XT_CB_PASSTHROUGH;
6964 done:
6965 return (rv);
6969 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6971 if (t == NULL) {
6972 show_oops(NULL, "cmd_focusout_cb invalid parameters");
6973 return (XT_CB_PASSTHROUGH);
6975 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6977 hide_cmd(t);
6978 hide_oops(t);
6980 if (show_url == 0 || t->focus_wv)
6981 focus_webview(t);
6982 else
6983 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6985 return (XT_CB_PASSTHROUGH);
6988 void
6989 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6991 char *s;
6992 const gchar *c = gtk_entry_get_text(entry);
6994 if (t == NULL) {
6995 show_oops(NULL, "cmd_activate_cb invalid parameters");
6996 return;
6999 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7001 hide_cmd(t);
7003 /* sanity */
7004 if (c == NULL)
7005 goto done;
7006 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7007 goto done;
7008 if (strlen(c) < 2)
7009 goto done;
7010 s = (char *)&c[1];
7012 if (c[0] == '/' || c[0] == '?') {
7013 if (t->search_text) {
7014 g_free(t->search_text);
7015 t->search_text = NULL;
7018 t->search_text = g_strdup(s);
7019 if (global_search)
7020 g_free(global_search);
7021 global_search = g_strdup(s);
7022 t->search_forward = c[0] == '/';
7024 goto done;
7027 cmd_execute(t, s);
7029 done:
7030 return;
7033 void
7034 backward_cb(GtkWidget *w, struct tab *t)
7036 struct karg a;
7038 if (t == NULL) {
7039 show_oops(NULL, "backward_cb invalid parameters");
7040 return;
7043 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7045 a.i = XT_NAV_BACK;
7046 navaction(t, &a);
7049 void
7050 forward_cb(GtkWidget *w, struct tab *t)
7052 struct karg a;
7054 if (t == NULL) {
7055 show_oops(NULL, "forward_cb invalid parameters");
7056 return;
7059 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7061 a.i = XT_NAV_FORWARD;
7062 navaction(t, &a);
7065 void
7066 home_cb(GtkWidget *w, struct tab *t)
7068 if (t == NULL) {
7069 show_oops(NULL, "home_cb invalid parameters");
7070 return;
7073 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7075 load_uri(t, home);
7078 void
7079 stop_cb(GtkWidget *w, struct tab *t)
7081 WebKitWebFrame *frame;
7083 if (t == NULL) {
7084 show_oops(NULL, "stop_cb invalid parameters");
7085 return;
7088 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7090 frame = webkit_web_view_get_main_frame(t->wv);
7091 if (frame == NULL) {
7092 show_oops(t, "stop_cb: no frame");
7093 return;
7096 webkit_web_frame_stop_loading(frame);
7097 abort_favicon_download(t);
7100 void
7101 setup_webkit(struct tab *t)
7103 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7104 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7105 FALSE, (char *)NULL);
7106 else
7107 warnx("webkit does not have \"enable-dns-prefetching\" property");
7108 g_object_set(G_OBJECT(t->settings),
7109 "user-agent", t->user_agent, (char *)NULL);
7110 g_object_set(G_OBJECT(t->settings),
7111 "enable-scripts", enable_scripts, (char *)NULL);
7112 g_object_set(G_OBJECT(t->settings),
7113 "enable-plugins", enable_plugins, (char *)NULL);
7114 g_object_set(G_OBJECT(t->settings),
7115 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7116 g_object_set(G_OBJECT(t->settings),
7117 "enable-html5-database", FALSE, (char *)NULL);
7118 g_object_set(G_OBJECT(t->settings),
7119 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7120 g_object_set(G_OBJECT(t->settings),
7121 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7122 g_object_set(G_OBJECT(t->settings),
7123 "spell_checking_languages", spell_check_languages, (char *)NULL);
7124 g_object_set(G_OBJECT(t->wv),
7125 "full-content-zoom", TRUE, (char *)NULL);
7126 adjustfont_webkit(t, XT_FONT_SET);
7128 webkit_web_view_set_settings(t->wv, t->settings);
7131 GtkWidget *
7132 create_browser(struct tab *t)
7134 GtkWidget *w;
7135 gchar *strval;
7137 if (t == NULL) {
7138 show_oops(NULL, "create_browser invalid parameters");
7139 return (NULL);
7142 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7143 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7144 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7145 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7147 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7148 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7149 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7151 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7152 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7154 /* set defaults */
7155 t->settings = webkit_web_settings_new();
7157 if (user_agent == NULL) {
7158 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7159 (char *)NULL);
7160 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7161 g_free(strval);
7162 } else
7163 t->user_agent = g_strdup(user_agent);
7165 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7167 setup_webkit(t);
7169 return (w);
7172 GtkWidget *
7173 create_window(void)
7175 GtkWidget *w;
7177 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7178 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7179 gtk_widget_set_name(w, "xxxterm");
7180 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7181 g_signal_connect(G_OBJECT(w), "delete_event",
7182 G_CALLBACK (gtk_main_quit), NULL);
7184 return (w);
7187 GtkWidget *
7188 create_kiosk_toolbar(struct tab *t)
7190 GtkWidget *toolbar = NULL, *b;
7192 b = gtk_hbox_new(FALSE, 0);
7193 toolbar = b;
7194 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7196 /* backward button */
7197 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7198 gtk_widget_set_sensitive(t->backward, FALSE);
7199 g_signal_connect(G_OBJECT(t->backward), "clicked",
7200 G_CALLBACK(backward_cb), t);
7201 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7203 /* forward button */
7204 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7205 gtk_widget_set_sensitive(t->forward, FALSE);
7206 g_signal_connect(G_OBJECT(t->forward), "clicked",
7207 G_CALLBACK(forward_cb), t);
7208 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7210 /* home button */
7211 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7212 gtk_widget_set_sensitive(t->gohome, true);
7213 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7214 G_CALLBACK(home_cb), t);
7215 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7217 /* create widgets but don't use them */
7218 t->uri_entry = gtk_entry_new();
7219 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7220 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7221 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7223 return (toolbar);
7226 GtkWidget *
7227 create_toolbar(struct tab *t)
7229 GtkWidget *toolbar = NULL, *b, *eb1;
7231 b = gtk_hbox_new(FALSE, 0);
7232 toolbar = b;
7233 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7235 if (fancy_bar) {
7236 /* backward button */
7237 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7238 gtk_widget_set_sensitive(t->backward, FALSE);
7239 g_signal_connect(G_OBJECT(t->backward), "clicked",
7240 G_CALLBACK(backward_cb), t);
7241 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7243 /* forward button */
7244 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7245 gtk_widget_set_sensitive(t->forward, FALSE);
7246 g_signal_connect(G_OBJECT(t->forward), "clicked",
7247 G_CALLBACK(forward_cb), t);
7248 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7249 FALSE, 0);
7251 /* stop button */
7252 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7253 gtk_widget_set_sensitive(t->stop, FALSE);
7254 g_signal_connect(G_OBJECT(t->stop), "clicked",
7255 G_CALLBACK(stop_cb), t);
7256 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7257 FALSE, 0);
7259 /* JS button */
7260 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7261 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7262 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7263 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7264 G_CALLBACK(js_toggle_cb), t);
7265 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7268 t->uri_entry = gtk_entry_new();
7269 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7270 G_CALLBACK(activate_uri_entry_cb), t);
7271 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7272 G_CALLBACK(entry_key_cb), t);
7273 completion_add(t);
7274 eb1 = gtk_hbox_new(FALSE, 0);
7275 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7276 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7277 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7279 /* search entry */
7280 if (fancy_bar && search_string) {
7281 GtkWidget *eb2;
7282 t->search_entry = gtk_entry_new();
7283 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7284 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7285 G_CALLBACK(activate_search_entry_cb), t);
7286 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7287 G_CALLBACK(entry_key_cb), t);
7288 gtk_widget_set_size_request(t->search_entry, -1, -1);
7289 eb2 = gtk_hbox_new(FALSE, 0);
7290 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7291 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7293 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7295 return (toolbar);
7298 GtkWidget *
7299 create_buffers(struct tab *t)
7301 GtkCellRenderer *renderer;
7302 GtkWidget *view;
7304 view = gtk_tree_view_new();
7306 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7308 renderer = gtk_cell_renderer_text_new();
7309 gtk_tree_view_insert_column_with_attributes
7310 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7312 renderer = gtk_cell_renderer_text_new();
7313 gtk_tree_view_insert_column_with_attributes
7314 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE, NULL);
7316 gtk_tree_view_set_model
7317 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7319 return view;
7322 void
7323 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7324 GtkTreeViewColumn *col, struct tab *t)
7326 GtkTreeIter iter;
7327 guint id;
7329 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7331 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path)) {
7332 gtk_tree_model_get
7333 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7334 gtk_notebook_set_current_page(notebook, id - 1);
7337 hide_buffers(t);
7340 void
7341 recalc_tabs(void)
7343 struct tab *t;
7345 TAILQ_FOREACH(t, &tabs, entry)
7346 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7350 undo_close_tab_save(struct tab *t)
7352 int m, n;
7353 const gchar *uri;
7354 struct undo *u1, *u2;
7355 GList *items;
7356 WebKitWebHistoryItem *item;
7358 if ((uri = get_uri(t)) == NULL)
7359 return (1);
7361 u1 = g_malloc0(sizeof(struct undo));
7362 u1->uri = g_strdup(uri);
7364 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7366 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7367 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7368 u1->back = n;
7370 /* forward history */
7371 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7373 while (items) {
7374 item = items->data;
7375 u1->history = g_list_prepend(u1->history,
7376 webkit_web_history_item_copy(item));
7377 items = g_list_next(items);
7380 /* current item */
7381 if (m) {
7382 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7383 u1->history = g_list_prepend(u1->history,
7384 webkit_web_history_item_copy(item));
7387 /* back history */
7388 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7390 while (items) {
7391 item = items->data;
7392 u1->history = g_list_prepend(u1->history,
7393 webkit_web_history_item_copy(item));
7394 items = g_list_next(items);
7397 TAILQ_INSERT_HEAD(&undos, u1, entry);
7399 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7400 u2 = TAILQ_LAST(&undos, undo_tailq);
7401 TAILQ_REMOVE(&undos, u2, entry);
7402 g_free(u2->uri);
7403 g_list_free(u2->history);
7404 g_free(u2);
7405 } else
7406 undo_count++;
7408 return (0);
7411 void
7412 delete_tab(struct tab *t)
7414 struct karg a;
7416 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7418 if (t == NULL)
7419 return;
7421 TAILQ_REMOVE(&tabs, t, entry);
7423 /* Halt all webkit activity. */
7424 abort_favicon_download(t);
7425 webkit_web_view_stop_loading(t->wv);
7427 /* Save the tab, so we can undo the close. */
7428 undo_close_tab_save(t);
7430 if (browser_mode == XT_BM_KIOSK) {
7431 gtk_widget_destroy(t->uri_entry);
7432 gtk_widget_destroy(t->stop);
7433 gtk_widget_destroy(t->js_toggle);
7436 gtk_widget_destroy(t->vbox);
7437 g_free(t->user_agent);
7438 g_free(t->stylesheet);
7439 g_free(t);
7441 if (TAILQ_EMPTY(&tabs)) {
7442 if (browser_mode == XT_BM_KIOSK)
7443 create_new_tab(home, NULL, 1, -1);
7444 else
7445 create_new_tab(NULL, NULL, 1, -1);
7448 /* recreate session */
7449 if (session_autosave) {
7450 a.s = NULL;
7451 save_tabs(t, &a);
7455 void
7456 adjustfont_webkit(struct tab *t, int adjust)
7458 gfloat zoom;
7460 if (t == NULL) {
7461 show_oops(NULL, "adjustfont_webkit invalid parameters");
7462 return;
7465 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7466 if (adjust == XT_FONT_SET) {
7467 t->font_size = default_font_size;
7468 zoom = default_zoom_level;
7469 t->font_size += adjust;
7470 g_object_set(G_OBJECT(t->settings), "default-font-size",
7471 t->font_size, (char *)NULL);
7472 g_object_get(G_OBJECT(t->settings), "default-font-size",
7473 &t->font_size, (char *)NULL);
7474 } else {
7475 t->font_size += adjust;
7476 zoom += adjust/25.0;
7477 if (zoom < 0.0) {
7478 zoom = 0.04;
7481 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7482 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7485 void
7486 append_tab(struct tab *t)
7488 if (t == NULL)
7489 return;
7491 TAILQ_INSERT_TAIL(&tabs, t, entry);
7492 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7495 struct tab *
7496 create_new_tab(char *title, struct undo *u, int focus, int position)
7498 struct tab *t;
7499 int load = 1, id;
7500 GtkWidget *b, *bb;
7501 WebKitWebHistoryItem *item;
7502 GList *items;
7503 GdkColor color;
7505 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7507 if (tabless && !TAILQ_EMPTY(&tabs)) {
7508 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7509 return (NULL);
7512 t = g_malloc0(sizeof *t);
7514 if (title == NULL) {
7515 title = "(untitled)";
7516 load = 0;
7519 t->vbox = gtk_vbox_new(FALSE, 0);
7521 /* label + button for tab */
7522 b = gtk_hbox_new(FALSE, 0);
7523 t->tab_content = b;
7525 #if GTK_CHECK_VERSION(2, 20, 0)
7526 t->spinner = gtk_spinner_new ();
7527 #endif
7528 t->label = gtk_label_new(title);
7529 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7530 gtk_widget_set_size_request(t->label, 100, 0);
7531 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7532 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7533 gtk_widget_set_size_request(b, 130, 0);
7535 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7536 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7537 #if GTK_CHECK_VERSION(2, 20, 0)
7538 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7539 #endif
7541 /* toolbar */
7542 if (browser_mode == XT_BM_KIOSK)
7543 t->toolbar = create_kiosk_toolbar(t);
7544 else
7545 t->toolbar = create_toolbar(t);
7547 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7549 /* browser */
7550 t->browser_win = create_browser(t);
7551 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7553 /* oops message for user feedback */
7554 t->oops = gtk_entry_new();
7555 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7556 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7557 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7558 gdk_color_parse(XT_COLOR_RED, &color);
7559 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7560 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7562 /* command entry */
7563 t->cmd = gtk_entry_new();
7564 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7565 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7566 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7567 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7569 /* status bar */
7570 t->statusbar = gtk_entry_new();
7571 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7572 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7573 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7574 gdk_color_parse(XT_COLOR_BLACK, &color);
7575 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7576 gdk_color_parse(XT_COLOR_WHITE, &color);
7577 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7578 gtk_widget_modify_font(GTK_WIDGET(t->statusbar), statusbar_font);
7579 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7581 /* buffer list */
7582 t->buffers = create_buffers(t);
7583 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
7585 /* xtp meaning is normal by default */
7586 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7588 /* set empty favicon */
7589 xt_icon_from_name(t, "text-html");
7591 /* and show it all */
7592 gtk_widget_show_all(b);
7593 gtk_widget_show_all(t->vbox);
7595 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7596 append_tab(t);
7597 else {
7598 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7599 if (id > gtk_notebook_get_n_pages(notebook))
7600 append_tab(t);
7601 else {
7602 TAILQ_INSERT_TAIL(&tabs, t, entry);
7603 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7604 recalc_tabs();
7608 #if GTK_CHECK_VERSION(2, 20, 0)
7609 /* turn spinner off if we are a new tab without uri */
7610 if (!load) {
7611 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7612 gtk_widget_hide(t->spinner);
7614 #endif
7615 /* make notebook tabs reorderable */
7616 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7618 g_object_connect(G_OBJECT(t->cmd),
7619 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7620 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7621 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7622 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7623 (char *)NULL);
7625 /* reuse wv_button_cb to hide oops */
7626 g_object_connect(G_OBJECT(t->oops),
7627 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7628 (char *)NULL);
7630 g_signal_connect(t->buffers,
7631 "row-activated", G_CALLBACK(row_activated_cb), t);
7632 g_object_connect(G_OBJECT(t->buffers),
7633 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
7635 g_object_connect(G_OBJECT(t->wv),
7636 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7637 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7638 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7639 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7640 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7641 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7642 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7643 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7644 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7645 "signal::event", G_CALLBACK(webview_event_cb), t,
7646 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7647 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7648 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7649 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7650 (char *)NULL);
7651 g_signal_connect(t->wv,
7652 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7653 g_signal_connect(t->wv,
7654 "notify::title", G_CALLBACK(notify_title_cb), t);
7656 /* hijack the unused keys as if we were the browser */
7657 g_object_connect(G_OBJECT(t->toolbar),
7658 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7659 (char *)NULL);
7661 g_signal_connect(G_OBJECT(bb), "button_press_event",
7662 G_CALLBACK(tab_close_cb), t);
7664 /* hide stuff */
7665 hide_cmd(t);
7666 hide_oops(t);
7667 hide_buffers(t);
7668 url_set_visibility();
7669 statusbar_set_visibility();
7671 if (focus) {
7672 gtk_notebook_set_current_page(notebook, t->tab_id);
7673 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7674 t->tab_id);
7677 if (load) {
7678 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7679 load_uri(t, title);
7680 } else {
7681 if (show_url == 1)
7682 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7683 else
7684 focus_webview(t);
7687 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7688 /* restore the tab's history */
7689 if (u && u->history) {
7690 items = u->history;
7691 while (items) {
7692 item = items->data;
7693 webkit_web_back_forward_list_add_item(t->bfl, item);
7694 items = g_list_next(items);
7697 item = g_list_nth_data(u->history, u->back);
7698 if (item)
7699 webkit_web_view_go_to_back_forward_item(t->wv, item);
7701 g_list_free(items);
7702 g_list_free(u->history);
7703 } else
7704 webkit_web_back_forward_list_clear(t->bfl);
7706 return (t);
7709 void
7710 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7711 gpointer *udata)
7713 struct tab *t;
7714 const gchar *uri;
7716 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7718 if (gtk_notebook_get_current_page(notebook) == -1)
7719 recalc_tabs();
7721 TAILQ_FOREACH(t, &tabs, entry) {
7722 if (t->tab_id == pn) {
7723 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7724 "%d\n", pn);
7726 uri = webkit_web_view_get_title(t->wv);
7727 if (uri == NULL)
7728 uri = XT_NAME;
7729 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7731 hide_cmd(t);
7732 hide_oops(t);
7734 if (t->focus_wv) {
7735 /* can't use focus_webview here */
7736 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7742 void
7743 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7744 gpointer *udata)
7746 recalc_tabs();
7749 void
7750 menuitem_response(struct tab *t)
7752 gtk_notebook_set_current_page(notebook, t->tab_id);
7755 gboolean
7756 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7758 GtkWidget *menu, *menu_items;
7759 GdkEventButton *bevent;
7760 const gchar *uri;
7761 struct tab *ti;
7763 if (event->type == GDK_BUTTON_PRESS) {
7764 bevent = (GdkEventButton *) event;
7765 menu = gtk_menu_new();
7767 TAILQ_FOREACH(ti, &tabs, entry) {
7768 if ((uri = get_uri(ti)) == NULL)
7769 /* XXX make sure there is something to print */
7770 /* XXX add gui pages in here to look purdy */
7771 uri = "(untitled)";
7772 menu_items = gtk_menu_item_new_with_label(uri);
7773 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7774 gtk_widget_show(menu_items);
7776 g_signal_connect_swapped((menu_items),
7777 "activate", G_CALLBACK(menuitem_response),
7778 (gpointer)ti);
7781 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7782 bevent->button, bevent->time);
7784 /* unref object so it'll free itself when popped down */
7785 #if !GTK_CHECK_VERSION(3, 0, 0)
7786 /* XXX does not need unref with gtk+3? */
7787 g_object_ref_sink(menu);
7788 g_object_unref(menu);
7789 #endif
7791 return (TRUE /* eat event */);
7794 return (FALSE /* propagate */);
7798 icon_size_map(int icon_size)
7800 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7801 icon_size > GTK_ICON_SIZE_DIALOG)
7802 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7804 return (icon_size);
7807 GtkWidget *
7808 create_button(char *name, char *stockid, int size)
7810 GtkWidget *button, *image;
7811 gchar *rcstring;
7812 int gtk_icon_size;
7814 rcstring = g_strdup_printf(
7815 "style \"%s-style\"\n"
7816 "{\n"
7817 " GtkWidget::focus-padding = 0\n"
7818 " GtkWidget::focus-line-width = 0\n"
7819 " xthickness = 0\n"
7820 " ythickness = 0\n"
7821 "}\n"
7822 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7823 gtk_rc_parse_string(rcstring);
7824 g_free(rcstring);
7825 button = gtk_button_new();
7826 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7827 gtk_icon_size = icon_size_map(size ? size : icon_size);
7829 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7830 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7831 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7832 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7833 gtk_widget_set_name(button, name);
7834 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7836 return (button);
7839 void
7840 button_set_stockid(GtkWidget *button, char *stockid)
7842 GtkWidget *image;
7844 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7845 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7846 gtk_button_set_image(GTK_BUTTON(button), image);
7849 void
7850 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
7852 GtkClipboard *clipboard;
7853 gchar *p = NULL, *s = NULL;
7856 * This code is very aggressive!
7857 * It basically ensures that the primary and regular clipboard are
7858 * always set the same. This obviously messes with standard X protocol
7859 * but those clowns should have come up with something better.
7862 /* XXX make this setting? */
7863 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7864 p = gtk_clipboard_wait_for_text(primary);
7865 if (p == NULL) {
7866 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
7867 p = gtk_clipboard_wait_for_text(clipboard);
7868 if (p)
7869 gtk_clipboard_set_text(primary, p, -1);
7870 } else {
7871 DNPRINTF(XT_D_CLIP, "primary got selection\n");
7872 s = gtk_clipboard_wait_for_text(clipboard);
7873 if (s) {
7875 * if s and p are the same the string was set by
7876 * clipb_clipboard_cb so do nothing in that case
7877 * to prevent endless loop
7879 if (!strcmp(s, p))
7880 goto done;
7882 gtk_clipboard_set_text(clipboard, p, -1);
7884 done:
7885 if (p)
7886 g_free(p);
7887 if (s)
7888 g_free(s);
7891 void
7892 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
7894 GtkClipboard *primary;
7895 gchar *p = NULL, *s = NULL;
7897 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
7899 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7900 p = gtk_clipboard_wait_for_text(clipboard);
7901 if (p) {
7902 s = gtk_clipboard_wait_for_text(primary);
7903 if (s) {
7905 * if s and p are the same the string was set by
7906 * clipb_primary_cb so do nothing in that case
7907 * to prevent endless loop and deselection of text
7909 if (!strcmp(s, p))
7910 goto done;
7912 gtk_clipboard_set_text(primary, p, -1);
7914 done:
7915 if (p)
7916 g_free(p);
7917 if (s)
7918 g_free(s);
7921 void
7922 create_canvas(void)
7924 GtkWidget *vbox;
7925 GList *l = NULL;
7926 GdkPixbuf *pb;
7927 char file[PATH_MAX];
7928 int i;
7930 vbox = gtk_vbox_new(FALSE, 0);
7931 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7932 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7933 #if !GTK_CHECK_VERSION(3, 0, 0)
7934 /* XXX seems to be needed with gtk+2 */
7935 gtk_notebook_set_tab_hborder(notebook, 0);
7936 gtk_notebook_set_tab_vborder(notebook, 0);
7937 #endif
7938 gtk_notebook_set_scrollable(notebook, TRUE);
7939 notebook_tab_set_visibility(notebook);
7940 gtk_notebook_set_show_border(notebook, FALSE);
7941 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7943 abtn = gtk_button_new();
7944 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7945 gtk_widget_set_size_request(arrow, -1, -1);
7946 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7947 gtk_widget_set_size_request(abtn, -1, 20);
7949 #if GTK_CHECK_VERSION(2, 20, 0)
7950 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7951 #endif
7952 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7953 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7954 gtk_widget_set_size_request(vbox, -1, -1);
7956 g_object_connect(G_OBJECT(notebook),
7957 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7958 (char *)NULL);
7959 g_object_connect(G_OBJECT(notebook),
7960 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
7961 (char *)NULL);
7962 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7963 G_CALLBACK(arrow_cb), NULL);
7965 main_window = create_window();
7966 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7967 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7969 /* icons */
7970 for (i = 0; i < LENGTH(icons); i++) {
7971 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7972 pb = gdk_pixbuf_new_from_file(file, NULL);
7973 l = g_list_append(l, pb);
7975 gtk_window_set_default_icon_list(l);
7977 /* clipboard */
7978 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
7979 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
7980 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
7981 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
7983 gtk_widget_show_all(abtn);
7984 gtk_widget_show_all(main_window);
7987 void
7988 set_hook(void **hook, char *name)
7990 if (hook == NULL)
7991 errx(1, "set_hook");
7993 if (*hook == NULL) {
7994 *hook = dlsym(RTLD_NEXT, name);
7995 if (*hook == NULL)
7996 errx(1, "can't hook %s", name);
8000 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8001 gboolean
8002 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8004 g_return_val_if_fail(cookie1, FALSE);
8005 g_return_val_if_fail(cookie2, FALSE);
8007 return (!strcmp (cookie1->name, cookie2->name) &&
8008 !strcmp (cookie1->value, cookie2->value) &&
8009 !strcmp (cookie1->path, cookie2->path) &&
8010 !strcmp (cookie1->domain, cookie2->domain));
8013 void
8014 transfer_cookies(void)
8016 GSList *cf;
8017 SoupCookie *sc, *pc;
8019 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8021 for (;cf; cf = cf->next) {
8022 pc = cf->data;
8023 sc = soup_cookie_copy(pc);
8024 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8027 soup_cookies_free(cf);
8030 void
8031 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8033 GSList *cf;
8034 SoupCookie *ci;
8036 print_cookie("soup_cookie_jar_delete_cookie", c);
8038 if (cookies_enabled == 0)
8039 return;
8041 if (jar == NULL || c == NULL)
8042 return;
8044 /* find and remove from persistent jar */
8045 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8047 for (;cf; cf = cf->next) {
8048 ci = cf->data;
8049 if (soup_cookie_equal(ci, c)) {
8050 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8051 break;
8055 soup_cookies_free(cf);
8057 /* delete from session jar */
8058 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8061 void
8062 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8064 struct domain *d = NULL;
8065 SoupCookie *c;
8066 FILE *r_cookie_f;
8068 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8069 jar, p_cookiejar, s_cookiejar);
8071 if (cookies_enabled == 0)
8072 return;
8074 /* see if we are up and running */
8075 if (p_cookiejar == NULL) {
8076 _soup_cookie_jar_add_cookie(jar, cookie);
8077 return;
8079 /* disallow p_cookiejar adds, shouldn't happen */
8080 if (jar == p_cookiejar)
8081 return;
8083 /* sanity */
8084 if (jar == NULL || cookie == NULL)
8085 return;
8087 if (enable_cookie_whitelist &&
8088 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8089 blocked_cookies++;
8090 DNPRINTF(XT_D_COOKIE,
8091 "soup_cookie_jar_add_cookie: reject %s\n",
8092 cookie->domain);
8093 if (save_rejected_cookies) {
8094 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8095 show_oops(NULL, "can't open reject cookie file");
8096 return;
8098 fseek(r_cookie_f, 0, SEEK_END);
8099 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8100 cookie->http_only ? "#HttpOnly_" : "",
8101 cookie->domain,
8102 *cookie->domain == '.' ? "TRUE" : "FALSE",
8103 cookie->path,
8104 cookie->secure ? "TRUE" : "FALSE",
8105 cookie->expires ?
8106 (gulong)soup_date_to_time_t(cookie->expires) :
8108 cookie->name,
8109 cookie->value);
8110 fflush(r_cookie_f);
8111 fclose(r_cookie_f);
8113 if (!allow_volatile_cookies)
8114 return;
8117 if (cookie->expires == NULL && session_timeout) {
8118 soup_cookie_set_expires(cookie,
8119 soup_date_new_from_now(session_timeout));
8120 print_cookie("modified add cookie", cookie);
8123 /* see if we are white listed for persistence */
8124 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8125 /* add to persistent jar */
8126 c = soup_cookie_copy(cookie);
8127 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8128 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8131 /* add to session jar */
8132 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8133 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8136 void
8137 setup_cookies(void)
8139 char file[PATH_MAX];
8141 set_hook((void *)&_soup_cookie_jar_add_cookie,
8142 "soup_cookie_jar_add_cookie");
8143 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8144 "soup_cookie_jar_delete_cookie");
8146 if (cookies_enabled == 0)
8147 return;
8150 * the following code is intricate due to overriding several libsoup
8151 * functions.
8152 * do not alter order of these operations.
8155 /* rejected cookies */
8156 if (save_rejected_cookies)
8157 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8159 /* persistent cookies */
8160 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8161 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8163 /* session cookies */
8164 s_cookiejar = soup_cookie_jar_new();
8165 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8166 cookie_policy, (void *)NULL);
8167 transfer_cookies();
8169 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8172 void
8173 setup_proxy(char *uri)
8175 if (proxy_uri) {
8176 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8177 soup_uri_free(proxy_uri);
8178 proxy_uri = NULL;
8180 if (http_proxy) {
8181 if (http_proxy != uri) {
8182 g_free(http_proxy);
8183 http_proxy = NULL;
8187 if (uri) {
8188 http_proxy = g_strdup(uri);
8189 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8190 proxy_uri = soup_uri_new(http_proxy);
8191 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8196 send_cmd_to_socket(char *cmd)
8198 int s, len, rv = 1;
8199 struct sockaddr_un sa;
8201 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8202 warnx("%s: socket", __func__);
8203 return (rv);
8206 sa.sun_family = AF_UNIX;
8207 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8208 work_dir, XT_SOCKET_FILE);
8209 len = SUN_LEN(&sa);
8211 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8212 warnx("%s: connect", __func__);
8213 goto done;
8216 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8217 warnx("%s: send", __func__);
8218 goto done;
8221 rv = 0;
8222 done:
8223 close(s);
8224 return (rv);
8227 gboolean
8228 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8230 int s, n;
8231 char str[XT_MAX_URL_LENGTH];
8232 socklen_t t = sizeof(struct sockaddr_un);
8233 struct sockaddr_un sa;
8234 struct passwd *p;
8235 uid_t uid;
8236 gid_t gid;
8237 struct tab *tt;
8238 gint fd = g_io_channel_unix_get_fd(source);
8240 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8241 warn("accept");
8242 return (FALSE);
8245 if (getpeereid(s, &uid, &gid) == -1) {
8246 warn("getpeereid");
8247 return (FALSE);
8249 if (uid != getuid() || gid != getgid()) {
8250 warnx("unauthorized user");
8251 return (FALSE);
8254 p = getpwuid(uid);
8255 if (p == NULL) {
8256 warnx("not a valid user");
8257 return (FALSE);
8260 n = recv(s, str, sizeof(str), 0);
8261 if (n <= 0)
8262 return (FALSE);
8264 tt = TAILQ_LAST(&tabs, tab_list);
8265 cmd_execute(tt, str);
8266 return (TRUE);
8270 is_running(void)
8272 int s, len, rv = 1;
8273 struct sockaddr_un sa;
8275 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8276 warn("is_running: socket");
8277 return (-1);
8280 sa.sun_family = AF_UNIX;
8281 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8282 work_dir, XT_SOCKET_FILE);
8283 len = SUN_LEN(&sa);
8285 /* connect to see if there is a listener */
8286 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8287 rv = 0; /* not running */
8288 else
8289 rv = 1; /* already running */
8291 close(s);
8293 return (rv);
8297 build_socket(void)
8299 int s, len;
8300 struct sockaddr_un sa;
8302 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8303 warn("build_socket: socket");
8304 return (-1);
8307 sa.sun_family = AF_UNIX;
8308 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8309 work_dir, XT_SOCKET_FILE);
8310 len = SUN_LEN(&sa);
8312 /* connect to see if there is a listener */
8313 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8314 /* no listener so we will */
8315 unlink(sa.sun_path);
8317 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8318 warn("build_socket: bind");
8319 goto done;
8322 if (listen(s, 1) == -1) {
8323 warn("build_socket: listen");
8324 goto done;
8327 return (s);
8330 done:
8331 close(s);
8332 return (-1);
8335 gboolean
8336 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8337 GtkTreeIter *iter, struct tab *t)
8339 gchar *value;
8341 gtk_tree_model_get(model, iter, 0, &value, -1);
8342 load_uri(t, value);
8343 g_free(value);
8345 return (FALSE);
8348 gboolean
8349 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8350 GtkTreeIter *iter, struct tab *t)
8352 gchar *value;
8354 gtk_tree_model_get(model, iter, 0, &value, -1);
8355 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8356 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8357 g_free(value);
8359 return (TRUE);
8362 void
8363 completion_add_uri(const gchar *uri)
8365 GtkTreeIter iter;
8367 /* add uri to list_store */
8368 gtk_list_store_append(completion_model, &iter);
8369 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8372 gboolean
8373 completion_match(GtkEntryCompletion *completion, const gchar *key,
8374 GtkTreeIter *iter, gpointer user_data)
8376 gchar *value;
8377 gboolean match = FALSE;
8379 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8380 -1);
8382 if (value == NULL)
8383 return FALSE;
8385 match = match_uri(value, key);
8387 g_free(value);
8388 return (match);
8391 void
8392 completion_add(struct tab *t)
8394 /* enable completion for tab */
8395 t->completion = gtk_entry_completion_new();
8396 gtk_entry_completion_set_text_column(t->completion, 0);
8397 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8398 gtk_entry_completion_set_model(t->completion,
8399 GTK_TREE_MODEL(completion_model));
8400 gtk_entry_completion_set_match_func(t->completion, completion_match,
8401 NULL, NULL);
8402 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8403 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8404 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8405 G_CALLBACK(completion_select_cb), t);
8406 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8407 G_CALLBACK(completion_hover_cb), t);
8410 void
8411 xxx_dir(char *dir)
8413 struct stat sb;
8415 if (stat(dir, &sb)) {
8416 if (mkdir(dir, S_IRWXU) == -1)
8417 err(1, "mkdir %s", dir);
8418 if (stat(dir, &sb))
8419 err(1, "stat %s", dir);
8421 if (S_ISDIR(sb.st_mode) == 0)
8422 errx(1, "%s not a dir", dir);
8423 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8424 warnx("fixing invalid permissions on %s", dir);
8425 if (chmod(dir, S_IRWXU) == -1)
8426 err(1, "chmod %s", dir);
8430 void
8431 usage(void)
8433 fprintf(stderr,
8434 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8435 exit(0);
8440 main(int argc, char *argv[])
8442 struct stat sb;
8443 int c, s, optn = 0, opte = 0, focus = 1;
8444 char conf[PATH_MAX] = { '\0' };
8445 char file[PATH_MAX];
8446 char *env_proxy = NULL;
8447 FILE *f = NULL;
8448 struct karg a;
8449 struct sigaction sact;
8450 GIOChannel *channel;
8451 struct rlimit rlp;
8453 start_argv = argv;
8455 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8457 /* fiddle with ulimits */
8458 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8459 warn("getrlimit");
8460 else {
8461 /* just use them all */
8462 rlp.rlim_cur = rlp.rlim_max;
8463 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8464 warn("setrlimit");
8465 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8466 warn("getrlimit");
8467 else if (rlp.rlim_cur <= 256)
8468 warnx("%s requires at least 256 file descriptors",
8469 __progname);
8472 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8473 switch (c) {
8474 case 'S':
8475 show_url = 0;
8476 break;
8477 case 'T':
8478 show_tabs = 0;
8479 break;
8480 case 'V':
8481 errx(0 , "Version: %s", version);
8482 break;
8483 case 'f':
8484 strlcpy(conf, optarg, sizeof(conf));
8485 break;
8486 case 's':
8487 strlcpy(named_session, optarg, sizeof(named_session));
8488 break;
8489 case 't':
8490 tabless = 1;
8491 break;
8492 case 'n':
8493 optn = 1;
8494 break;
8495 case 'e':
8496 opte = 1;
8497 break;
8498 default:
8499 usage();
8500 /* NOTREACHED */
8503 argc -= optind;
8504 argv += optind;
8506 RB_INIT(&hl);
8507 RB_INIT(&js_wl);
8508 RB_INIT(&downloads);
8510 TAILQ_INIT(&tabs);
8511 TAILQ_INIT(&mtl);
8512 TAILQ_INIT(&aliases);
8513 TAILQ_INIT(&undos);
8514 TAILQ_INIT(&kbl);
8516 init_keybindings();
8518 gnutls_global_init();
8520 /* generate session keys for xtp pages */
8521 generate_xtp_session_key(&dl_session_key);
8522 generate_xtp_session_key(&hl_session_key);
8523 generate_xtp_session_key(&cl_session_key);
8524 generate_xtp_session_key(&fl_session_key);
8526 /* prepare gtk */
8527 gtk_init(&argc, &argv);
8528 if (!g_thread_supported())
8529 g_thread_init(NULL);
8531 /* signals */
8532 bzero(&sact, sizeof(sact));
8533 sigemptyset(&sact.sa_mask);
8534 sact.sa_handler = sigchild;
8535 sact.sa_flags = SA_NOCLDSTOP;
8536 sigaction(SIGCHLD, &sact, NULL);
8538 /* set download dir */
8539 pwd = getpwuid(getuid());
8540 if (pwd == NULL)
8541 errx(1, "invalid user %d", getuid());
8542 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8544 /* set default string settings */
8545 home = g_strdup("https://www.cyphertite.com");
8546 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8547 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8548 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
8549 cmd_font_name = g_strdup("monospace normal 9");
8550 statusbar_font_name = g_strdup("monospace normal 9");
8553 /* read config file */
8554 if (strlen(conf) == 0)
8555 snprintf(conf, sizeof conf, "%s/.%s",
8556 pwd->pw_dir, XT_CONF_FILE);
8557 config_parse(conf, 0);
8559 /* init fonts */
8560 cmd_font = pango_font_description_from_string(cmd_font_name);
8561 statusbar_font = pango_font_description_from_string(statusbar_font_name);
8563 /* working directory */
8564 if (strlen(work_dir) == 0)
8565 snprintf(work_dir, sizeof work_dir, "%s/%s",
8566 pwd->pw_dir, XT_DIR);
8567 xxx_dir(work_dir);
8569 /* icon cache dir */
8570 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8571 xxx_dir(cache_dir);
8573 /* certs dir */
8574 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8575 xxx_dir(certs_dir);
8577 /* sessions dir */
8578 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8579 work_dir, XT_SESSIONS_DIR);
8580 xxx_dir(sessions_dir);
8582 /* runtime settings that can override config file */
8583 if (runtime_settings[0] != '\0')
8584 config_parse(runtime_settings, 1);
8586 /* download dir */
8587 if (!strcmp(download_dir, pwd->pw_dir))
8588 strlcat(download_dir, "/downloads", sizeof download_dir);
8589 xxx_dir(download_dir);
8591 /* favorites file */
8592 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8593 if (stat(file, &sb)) {
8594 warnx("favorites file doesn't exist, creating it");
8595 if ((f = fopen(file, "w")) == NULL)
8596 err(1, "favorites");
8597 fclose(f);
8600 /* cookies */
8601 session = webkit_get_default_session();
8602 setup_cookies();
8604 /* certs */
8605 if (ssl_ca_file) {
8606 if (stat(ssl_ca_file, &sb)) {
8607 warnx("no CA file: %s", ssl_ca_file);
8608 g_free(ssl_ca_file);
8609 ssl_ca_file = NULL;
8610 } else
8611 g_object_set(session,
8612 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8613 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8614 (void *)NULL);
8617 /* proxy */
8618 env_proxy = getenv("http_proxy");
8619 if (env_proxy)
8620 setup_proxy(env_proxy);
8621 else
8622 setup_proxy(http_proxy);
8624 if (opte) {
8625 send_cmd_to_socket(argv[0]);
8626 exit(0);
8629 /* set some connection parameters */
8630 /* XXX webkit 1.4.X overwrites these values! */
8631 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
8632 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8633 g_object_set(session, "max-conns-per-host", max_host_connections,
8634 (char *)NULL);
8636 /* see if there is already an xxxterm running */
8637 if (single_instance && is_running()) {
8638 optn = 1;
8639 warnx("already running");
8642 char *cmd = NULL;
8643 if (optn) {
8644 while (argc) {
8645 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8646 send_cmd_to_socket(cmd);
8647 if (cmd)
8648 g_free(cmd);
8650 argc--;
8651 argv++;
8653 exit(0);
8656 /* uri completion */
8657 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8659 /* buffers */
8660 buffers_store = gtk_list_store_new
8661 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
8663 /* go graphical */
8664 create_canvas();
8666 if (save_global_history)
8667 restore_global_history();
8669 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8670 restore_saved_tabs();
8671 else {
8672 a.s = named_session;
8673 a.i = XT_SES_DONOTHING;
8674 open_tabs(NULL, &a);
8677 while (argc) {
8678 create_new_tab(argv[0], NULL, focus, -1);
8679 focus = 0;
8681 argc--;
8682 argv++;
8685 if (TAILQ_EMPTY(&tabs))
8686 create_new_tab(home, NULL, 1, -1);
8688 if (enable_socket)
8689 if ((s = build_socket()) != -1) {
8690 channel = g_io_channel_unix_new(s);
8691 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8694 gtk_main();
8696 gnutls_global_deinit();
8698 return (0);