add s
[xxxterm.git] / xxxterm.c
blobdaa743ace6484ae6d4e3da06e9a10f4764cab083
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <err.h>
34 #include <pwd.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pthread.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <libgen.h>
42 #include <ctype.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/stat.h>
59 #include <sys/socket.h>
60 #include <sys/un.h>
62 #include <gtk/gtk.h>
63 #include <gdk/gdkkeysyms.h>
64 #include <webkit/webkit.h>
65 #include <libsoup/soup.h>
66 #include <gnutls/gnutls.h>
67 #include <JavaScriptCore/JavaScript.h>
68 #include <gnutls/x509.h>
70 #include "javascript.h"
73 javascript.h borrowed from vimprobable2 under the following license:
75 Copyright (c) 2009 Leon Winter
76 Copyright (c) 2009 Hannes Schueller
77 Copyright (c) 2009 Matto Fransen
79 Permission is hereby granted, free of charge, to any person obtaining a copy
80 of this software and associated documentation files (the "Software"), to deal
81 in the Software without restriction, including without limitation the rights
82 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83 copies of the Software, and to permit persons to whom the Software is
84 furnished to do so, subject to the following conditions:
86 The above copyright notice and this permission notice shall be included in
87 all copies or substantial portions of the Software.
89 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
95 THE SOFTWARE.
98 static char *version = "$xxxterm$";
100 /* hooked functions */
101 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
102 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
103 SoupCookie *);
105 /*#define XT_DEBUG*/
106 #ifdef XT_DEBUG
107 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
108 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
109 #define XT_D_MOVE 0x0001
110 #define XT_D_KEY 0x0002
111 #define XT_D_TAB 0x0004
112 #define XT_D_URL 0x0008
113 #define XT_D_CMD 0x0010
114 #define XT_D_NAV 0x0020
115 #define XT_D_DOWNLOAD 0x0040
116 #define XT_D_CONFIG 0x0080
117 #define XT_D_JS 0x0100
118 #define XT_D_FAVORITE 0x0200
119 #define XT_D_PRINTING 0x0400
120 #define XT_D_COOKIE 0x0800
121 #define XT_D_KEYBINDING 0x1000
122 u_int32_t swm_debug = 0
123 | XT_D_MOVE
124 | XT_D_KEY
125 | XT_D_TAB
126 | XT_D_URL
127 | XT_D_CMD
128 | XT_D_NAV
129 | XT_D_DOWNLOAD
130 | XT_D_CONFIG
131 | XT_D_JS
132 | XT_D_FAVORITE
133 | XT_D_PRINTING
134 | XT_D_COOKIE
135 | XT_D_KEYBINDING
137 #else
138 #define DPRINTF(x...)
139 #define DNPRINTF(n,x...)
140 #endif
142 #define LENGTH(x) (sizeof x / sizeof x[0])
143 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
144 ~(GDK_BUTTON1_MASK) & \
145 ~(GDK_BUTTON2_MASK) & \
146 ~(GDK_BUTTON3_MASK) & \
147 ~(GDK_BUTTON4_MASK) & \
148 ~(GDK_BUTTON5_MASK))
150 char *icons[] = {
151 "xxxtermicon16.png",
152 "xxxtermicon32.png",
153 "xxxtermicon48.png",
154 "xxxtermicon64.png",
155 "xxxtermicon128.png"
158 struct tab {
159 TAILQ_ENTRY(tab) entry;
160 GtkWidget *vbox;
161 GtkWidget *tab_content;
162 GtkWidget *label;
163 GtkWidget *spinner;
164 GtkWidget *uri_entry;
165 GtkWidget *search_entry;
166 GtkWidget *toolbar;
167 GtkWidget *browser_win;
168 GtkWidget *statusbar;
169 GtkWidget *cmd;
170 GtkWidget *oops;
171 GtkWidget *backward;
172 GtkWidget *forward;
173 GtkWidget *stop;
174 GtkWidget *gohome;
175 GtkWidget *js_toggle;
176 GtkEntryCompletion *completion;
177 guint tab_id;
178 WebKitWebView *wv;
180 WebKitWebHistoryItem *item;
181 WebKitWebBackForwardList *bfl;
183 /* favicon */
184 WebKitNetworkRequest *icon_request;
185 WebKitDownload *icon_download;
186 GdkPixbuf *icon_pixbuf;
187 gchar *icon_dest_uri;
189 /* adjustments for browser */
190 GtkScrollbar *sb_h;
191 GtkScrollbar *sb_v;
192 GtkAdjustment *adjust_h;
193 GtkAdjustment *adjust_v;
195 /* flags */
196 int focus_wv;
197 int ctrl_click;
198 gchar *status;
199 int xtp_meaning; /* identifies dls/favorites */
201 /* hints */
202 int hints_on;
203 int hint_mode;
204 #define XT_HINT_NONE (0)
205 #define XT_HINT_NUMERICAL (1)
206 #define XT_HINT_ALPHANUM (2)
207 char hint_buf[128];
208 char hint_num[128];
210 /* custom stylesheet */
211 int styled;
212 char *stylesheet;
214 /* search */
215 char *search_text;
216 int search_forward;
218 /* settings */
219 WebKitWebSettings *settings;
220 int font_size;
221 gchar *user_agent;
223 TAILQ_HEAD(tab_list, tab);
225 struct history {
226 RB_ENTRY(history) entry;
227 const gchar *uri;
228 const gchar *title;
230 RB_HEAD(history_list, history);
232 struct download {
233 RB_ENTRY(download) entry;
234 int id;
235 WebKitDownload *download;
236 struct tab *tab;
238 RB_HEAD(download_list, download);
240 struct domain {
241 RB_ENTRY(domain) entry;
242 gchar *d;
243 int handy; /* app use */
245 RB_HEAD(domain_list, domain);
247 struct undo {
248 TAILQ_ENTRY(undo) entry;
249 gchar *uri;
250 GList *history;
251 int back; /* Keeps track of how many back
252 * history items there are. */
254 TAILQ_HEAD(undo_tailq, undo);
256 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
257 int next_download_id = 1;
259 struct karg {
260 int i;
261 char *s;
262 int p;
265 /* defines */
266 #define XT_NAME ("XXXTerm")
267 #define XT_DIR (".xxxterm")
268 #define XT_CACHE_DIR ("cache")
269 #define XT_CERT_DIR ("certs/")
270 #define XT_SESSIONS_DIR ("sessions/")
271 #define XT_CONF_FILE ("xxxterm.conf")
272 #define XT_FAVS_FILE ("favorites")
273 #define XT_SAVED_TABS_FILE ("main_session")
274 #define XT_RESTART_TABS_FILE ("restart_tabs")
275 #define XT_SOCKET_FILE ("socket")
276 #define XT_HISTORY_FILE ("history")
277 #define XT_REJECT_FILE ("rejected.txt")
278 #define XT_COOKIE_FILE ("cookies.txt")
279 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
280 #define XT_CB_HANDLED (TRUE)
281 #define XT_CB_PASSTHROUGH (FALSE)
282 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
283 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
284 #define XT_DLMAN_REFRESH "10"
285 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
286 "td {overflow: hidden;" \
287 " padding: 2px 2px 2px 2px;" \
288 " border: 1px solid black}\n" \
289 "tr:hover {background: #ffff99 ;}\n" \
290 "th {background-color: #cccccc;" \
291 " border: 1px solid black}" \
292 "table {border-spacing: 0; " \
293 " width: 90%%;" \
294 " border: 1px black solid;}\n" \
295 ".progress-outer{" \
296 " border: 1px solid black;" \
297 " height: 8px;" \
298 " width: 90%%;}" \
299 ".progress-inner{" \
300 " float: left;" \
301 " height: 8px;" \
302 " background: green;}" \
303 ".dlstatus{" \
304 " font-size: small;" \
305 " text-align: center;}" \
306 "</style>\n\n"
307 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
308 #define XT_MAX_UNDO_CLOSE_TAB (32)
309 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
310 #define XT_PRINT_EXTRA_MARGIN 10
312 /* colors */
313 #define XT_COLOR_RED "#cc0000"
314 #define XT_COLOR_YELLOW "#ffff66"
315 #define XT_COLOR_BLUE "lightblue"
316 #define XT_COLOR_GREEN "#99ff66"
317 #define XT_COLOR_WHITE "white"
318 #define XT_COLOR_BLACK "black"
321 * xxxterm "protocol" (xtp)
322 * We use this for managing stuff like downloads and favorites. They
323 * make magical HTML pages in memory which have xxxt:// links in order
324 * to communicate with xxxterm's internals. These links take the format:
325 * xxxt://class/session_key/action/arg
327 * Don't begin xtp class/actions as 0. atoi returns that on error.
329 * Typically we have not put addition of items in this framework, as
330 * adding items is either done via an ex-command or via a keybinding instead.
333 #define XT_XTP_STR "xxxt://"
335 /* XTP classes (xxxt://<class>) */
336 #define XT_XTP_INVALID 0 /* invalid */
337 #define XT_XTP_DL 1 /* downloads */
338 #define XT_XTP_HL 2 /* history */
339 #define XT_XTP_CL 3 /* cookies */
340 #define XT_XTP_FL 4 /* favorites */
342 /* XTP download actions */
343 #define XT_XTP_DL_LIST 1
344 #define XT_XTP_DL_CANCEL 2
345 #define XT_XTP_DL_REMOVE 3
347 /* XTP history actions */
348 #define XT_XTP_HL_LIST 1
349 #define XT_XTP_HL_REMOVE 2
351 /* XTP cookie actions */
352 #define XT_XTP_CL_LIST 1
353 #define XT_XTP_CL_REMOVE 2
355 /* XTP cookie actions */
356 #define XT_XTP_FL_LIST 1
357 #define XT_XTP_FL_REMOVE 2
359 /* xtp tab meanings - identifies which tabs have xtp pages in */
360 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
361 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
362 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
363 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
364 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
366 /* actions */
367 #define XT_MOVE_INVALID (0)
368 #define XT_MOVE_DOWN (1)
369 #define XT_MOVE_UP (2)
370 #define XT_MOVE_BOTTOM (3)
371 #define XT_MOVE_TOP (4)
372 #define XT_MOVE_PAGEDOWN (5)
373 #define XT_MOVE_PAGEUP (6)
374 #define XT_MOVE_HALFDOWN (7)
375 #define XT_MOVE_HALFUP (8)
376 #define XT_MOVE_LEFT (9)
377 #define XT_MOVE_FARLEFT (10)
378 #define XT_MOVE_RIGHT (11)
379 #define XT_MOVE_FARRIGHT (12)
381 #define XT_TAB_LAST (-4)
382 #define XT_TAB_FIRST (-3)
383 #define XT_TAB_PREV (-2)
384 #define XT_TAB_NEXT (-1)
385 #define XT_TAB_INVALID (0)
386 #define XT_TAB_NEW (1)
387 #define XT_TAB_DELETE (2)
388 #define XT_TAB_DELQUIT (3)
389 #define XT_TAB_OPEN (4)
390 #define XT_TAB_UNDO_CLOSE (5)
391 #define XT_TAB_SHOW (6)
392 #define XT_TAB_HIDE (7)
394 #define XT_NAV_INVALID (0)
395 #define XT_NAV_BACK (1)
396 #define XT_NAV_FORWARD (2)
397 #define XT_NAV_RELOAD (3)
398 #define XT_NAV_RELOAD_CACHE (4)
400 #define XT_FOCUS_INVALID (0)
401 #define XT_FOCUS_URI (1)
402 #define XT_FOCUS_SEARCH (2)
404 #define XT_SEARCH_INVALID (0)
405 #define XT_SEARCH_NEXT (1)
406 #define XT_SEARCH_PREV (2)
408 #define XT_PASTE_CURRENT_TAB (0)
409 #define XT_PASTE_NEW_TAB (1)
411 #define XT_FONT_SET (0)
413 #define XT_URL_SHOW (1)
414 #define XT_URL_HIDE (2)
416 #define XT_STATUSBAR_SHOW (1)
417 #define XT_STATUSBAR_HIDE (2)
419 #define XT_WL_TOGGLE (1<<0)
420 #define XT_WL_ENABLE (1<<1)
421 #define XT_WL_DISABLE (1<<2)
422 #define XT_WL_FQDN (1<<3) /* default */
423 #define XT_WL_TOPLEVEL (1<<4)
424 #define XT_WL_PERSISTENT (1<<5)
425 #define XT_WL_SESSION (1<<6)
427 #define XT_SHOW (1<<7)
428 #define XT_DELETE (1<<8)
429 #define XT_SAVE (1<<9)
430 #define XT_OPEN (1<<10)
432 #define XT_CMD_OPEN (0)
433 #define XT_CMD_OPEN_CURRENT (1)
434 #define XT_CMD_TABNEW (2)
435 #define XT_CMD_TABNEW_CURRENT (3)
437 #define XT_STATUS_NOTHING (0)
438 #define XT_STATUS_LINK (1)
439 #define XT_STATUS_URI (2)
440 #define XT_STATUS_LOADING (3)
442 #define XT_SES_DONOTHING (0)
443 #define XT_SES_CLOSETABS (1)
445 #define XT_BM_NORMAL (0)
446 #define XT_BM_WHITELIST (1)
447 #define XT_BM_KIOSK (2)
449 #define XT_PREFIX (1<<0)
450 #define XT_USERARG (1<<1)
451 #define XT_URLARG (1<<2)
453 /* mime types */
454 struct mime_type {
455 char *mt_type;
456 char *mt_action;
457 int mt_default;
458 int mt_download;
459 TAILQ_ENTRY(mime_type) entry;
461 TAILQ_HEAD(mime_type_list, mime_type);
463 /* uri aliases */
464 struct alias {
465 char *a_name;
466 char *a_uri;
467 TAILQ_ENTRY(alias) entry;
469 TAILQ_HEAD(alias_list, alias);
471 /* settings that require restart */
472 int tabless = 0; /* allow only 1 tab */
473 int enable_socket = 0;
474 int single_instance = 0; /* only allow one xxxterm to run */
475 int fancy_bar = 1; /* fancy toolbar */
476 int browser_mode = XT_BM_NORMAL;
478 /* runtime settings */
479 int show_tabs = 1; /* show tabs on notebook */
480 int show_url = 1; /* show url toolbar on notebook */
481 int show_statusbar = 0; /* vimperator style status bar */
482 int ctrl_click_focus = 0; /* ctrl click gets focus */
483 int cookies_enabled = 1; /* enable cookies */
484 int read_only_cookies = 0; /* enable to not write cookies */
485 int enable_scripts = 1;
486 int enable_plugins = 0;
487 int default_font_size = 12;
488 gfloat default_zoom_level = 1.0;
489 int window_height = 768;
490 int window_width = 1024;
491 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
492 unsigned refresh_interval = 10; /* download refresh interval */
493 int enable_cookie_whitelist = 0;
494 int enable_js_whitelist = 0;
495 time_t session_timeout = 3600; /* cookie session timeout */
496 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
497 char *ssl_ca_file = NULL;
498 char *resource_dir = NULL;
499 gboolean ssl_strict_certs = FALSE;
500 int append_next = 1; /* append tab after current tab */
501 char *home = NULL;
502 char *search_string = NULL;
503 char *http_proxy = NULL;
504 char download_dir[PATH_MAX];
505 char runtime_settings[PATH_MAX]; /* override of settings */
506 int allow_volatile_cookies = 0;
507 int save_global_history = 0; /* save global history to disk */
508 char *user_agent = NULL;
509 int save_rejected_cookies = 0;
510 time_t session_autosave = 0;
511 int guess_search = 0;
512 int dns_prefetch = FALSE;
513 gint max_connections = 25;
514 gint max_host_connections = 5;
516 struct settings;
517 struct key_binding;
518 int set_download_dir(struct settings *, char *);
519 int set_work_dir(struct settings *, char *);
520 int set_runtime_dir(struct settings *, char *);
521 int set_browser_mode(struct settings *, char *);
522 int set_cookie_policy(struct settings *, char *);
523 int add_alias(struct settings *, char *);
524 int add_mime_type(struct settings *, char *);
525 int add_cookie_wl(struct settings *, char *);
526 int add_js_wl(struct settings *, char *);
527 int add_kb(struct settings *, char *);
528 void button_set_stockid(GtkWidget *, char *);
529 GtkWidget * create_button(char *, char *, int);
531 char *get_browser_mode(struct settings *);
532 char *get_cookie_policy(struct settings *);
534 char *get_download_dir(struct settings *);
535 char *get_work_dir(struct settings *);
536 char *get_runtime_dir(struct settings *);
538 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
539 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
540 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
541 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
542 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
544 struct special {
545 int (*set)(struct settings *, char *);
546 char *(*get)(struct settings *);
547 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
550 struct special s_browser_mode = {
551 set_browser_mode,
552 get_browser_mode,
553 NULL
556 struct special s_cookie = {
557 set_cookie_policy,
558 get_cookie_policy,
559 NULL
562 struct special s_alias = {
563 add_alias,
564 NULL,
565 walk_alias
568 struct special s_mime = {
569 add_mime_type,
570 NULL,
571 walk_mime_type
574 struct special s_js = {
575 add_js_wl,
576 NULL,
577 walk_js_wl
580 struct special s_kb = {
581 add_kb,
582 NULL,
583 walk_kb
586 struct special s_cookie_wl = {
587 add_cookie_wl,
588 NULL,
589 walk_cookie_wl
592 struct special s_download_dir = {
593 set_download_dir,
594 get_download_dir,
595 NULL
598 struct special s_work_dir = {
599 set_work_dir,
600 get_work_dir,
601 NULL
604 struct settings {
605 char *name;
606 int type;
607 #define XT_S_INVALID (0)
608 #define XT_S_INT (1)
609 #define XT_S_STR (2)
610 #define XT_S_FLOAT (3)
611 uint32_t flags;
612 #define XT_SF_RESTART (1<<0)
613 #define XT_SF_RUNTIME (1<<1)
614 int *ival;
615 char **sval;
616 struct special *s;
617 gfloat *fval;
618 } rs[] = {
619 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
620 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
621 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
622 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
623 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
624 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
625 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
626 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
627 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
628 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
629 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
630 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
631 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
632 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
633 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
634 { "home", XT_S_STR, 0, NULL, &home, NULL },
635 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
636 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
637 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
638 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
639 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
640 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
641 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
642 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
643 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
644 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
645 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
646 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
647 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
648 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
649 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
650 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
651 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
652 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
653 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
654 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
655 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
656 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
657 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
659 /* runtime settings */
660 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
661 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
662 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
663 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
664 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
667 int about(struct tab *, struct karg *);
668 int blank(struct tab *, struct karg *);
669 int cookie_show_wl(struct tab *, struct karg *);
670 int js_show_wl(struct tab *, struct karg *);
671 int help(struct tab *, struct karg *);
672 int set(struct tab *, struct karg *);
673 int stats(struct tab *, struct karg *);
674 int marco(struct tab *, struct karg *);
675 const char * marco_message(int *);
676 int xtp_page_cl(struct tab *, struct karg *);
677 int xtp_page_dl(struct tab *, struct karg *);
678 int xtp_page_fl(struct tab *, struct karg *);
679 int xtp_page_hl(struct tab *, struct karg *);
681 #define XT_URI_ABOUT ("about:")
682 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
683 #define XT_URI_ABOUT_ABOUT ("about")
684 #define XT_URI_ABOUT_BLANK ("blank")
685 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
686 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
687 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
688 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
689 #define XT_URI_ABOUT_FAVORITES ("favorites")
690 #define XT_URI_ABOUT_HELP ("help")
691 #define XT_URI_ABOUT_HISTORY ("history")
692 #define XT_URI_ABOUT_JSWL ("jswl")
693 #define XT_URI_ABOUT_SET ("set")
694 #define XT_URI_ABOUT_STATS ("stats")
695 #define XT_URI_ABOUT_MARCO ("marco")
697 struct about_type {
698 char *name;
699 int (*func)(struct tab *, struct karg *);
700 } about_list[] = {
701 { XT_URI_ABOUT_ABOUT, about },
702 { XT_URI_ABOUT_BLANK, blank },
703 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
704 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
705 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
706 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
707 { XT_URI_ABOUT_HELP, help },
708 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
709 { XT_URI_ABOUT_JSWL, js_show_wl },
710 { XT_URI_ABOUT_SET, set },
711 { XT_URI_ABOUT_STATS, stats },
712 { XT_URI_ABOUT_MARCO, marco },
715 /* globals */
716 extern char *__progname;
717 char **start_argv;
718 struct passwd *pwd;
719 GtkWidget *main_window;
720 GtkNotebook *notebook;
721 GtkWidget *arrow, *abtn;
722 struct tab_list tabs;
723 struct history_list hl;
724 struct download_list downloads;
725 struct domain_list c_wl;
726 struct domain_list js_wl;
727 struct undo_tailq undos;
728 struct keybinding_list kbl;
729 int undo_count;
730 int updating_dl_tabs = 0;
731 int updating_hl_tabs = 0;
732 int updating_cl_tabs = 0;
733 int updating_fl_tabs = 0;
734 char *global_search;
735 uint64_t blocked_cookies = 0;
736 char named_session[PATH_MAX];
737 void update_favicon(struct tab *);
738 int icon_size_map(int);
740 GtkListStore *completion_model;
741 void completion_add(struct tab *);
742 void completion_add_uri(const gchar *);
744 void
745 sigchild(int sig)
747 int saved_errno, status;
748 pid_t pid;
750 saved_errno = errno;
752 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
753 if (pid == -1) {
754 if (errno == EINTR)
755 continue;
756 if (errno != ECHILD) {
758 clog_warn("sigchild: waitpid:");
761 break;
764 if (WIFEXITED(status)) {
765 if (WEXITSTATUS(status) != 0) {
767 clog_warnx("sigchild: child exit status: %d",
768 WEXITSTATUS(status));
771 } else {
773 clog_warnx("sigchild: child is terminated abnormally");
778 errno = saved_errno;
782 is_g_object_setting(GObject *o, char *str)
784 guint n_props = 0, i;
785 GParamSpec **proplist;
787 if (! G_IS_OBJECT(o))
788 return (0);
790 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
791 &n_props);
793 for (i=0; i < n_props; i++) {
794 if (! strcmp(proplist[i]->name, str))
795 return (1);
797 return (0);
801 * Display a web page from a HTML string in memory, rather than from a URL
803 void
804 load_webkit_string(struct tab *t, const char *str, gchar *title)
806 gchar *uri;
807 char file[PATH_MAX];
808 GdkPixbuf *pb;
810 /* we set this to indicate we want to manually do navaction */
811 if (t->bfl)
812 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
814 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
815 #if GTK_CHECK_VERSION(2, 20, 0)
816 gtk_spinner_stop(GTK_SPINNER(t->spinner));
817 gtk_widget_hide(t->spinner);
818 #endif
820 if (title) {
821 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
822 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
823 g_free(uri);
825 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
826 pb = gdk_pixbuf_new_from_file(file, NULL);
827 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
828 GTK_ENTRY_ICON_PRIMARY, pb);
829 gdk_pixbuf_unref(pb);
833 void
834 set_status(struct tab *t, gchar *s, int status)
836 gchar *type = NULL;
838 if (s == NULL)
839 return;
841 switch (status) {
842 case XT_STATUS_LOADING:
843 type = g_strdup_printf("Loading: %s", s);
844 s = type;
845 break;
846 case XT_STATUS_LINK:
847 type = g_strdup_printf("Link: %s", s);
848 if (!t->status)
849 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
850 s = type;
851 break;
852 case XT_STATUS_URI:
853 type = g_strdup_printf("%s", s);
854 if (!t->status) {
855 t->status = g_strdup(type);
857 s = type;
858 if (!t->status)
859 t->status = g_strdup(s);
860 break;
861 case XT_STATUS_NOTHING:
862 /* FALL THROUGH */
863 default:
864 break;
866 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
867 if (type)
868 g_free(type);
871 void
872 hide_oops(struct tab *t)
874 gtk_widget_hide(t->oops);
877 void
878 hide_cmd(struct tab *t)
880 gtk_widget_hide(t->cmd);
883 void
884 show_cmd(struct tab *t)
886 gtk_widget_hide(t->oops);
887 gtk_widget_show(t->cmd);
890 void
891 show_oops(struct tab *t, const char *fmt, ...)
893 va_list ap;
894 char *msg;
896 if (fmt == NULL)
897 return;
899 va_start(ap, fmt);
900 if (vasprintf(&msg, fmt, ap) == -1)
901 errx(1, "show_oops failed");
902 va_end(ap);
904 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
905 gtk_widget_hide(t->cmd);
906 gtk_widget_show(t->oops);
909 /* XXX collapse with show_oops */
910 void
911 show_oops_s(const char *fmt, ...)
913 va_list ap;
914 char *msg;
915 struct tab *ti, *t = NULL;
917 if (fmt == NULL)
918 return;
920 TAILQ_FOREACH(ti, &tabs, entry)
921 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
922 t = ti;
923 break;
925 if (t == NULL)
926 return;
928 va_start(ap, fmt);
929 if (vasprintf(&msg, fmt, ap) == -1)
930 errx(1, "show_oops_s failed");
931 va_end(ap);
933 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
934 gtk_widget_hide(t->cmd);
935 gtk_widget_show(t->oops);
938 char *
939 get_as_string(struct settings *s)
941 char *r = NULL;
943 if (s == NULL)
944 return (NULL);
946 if (s->s) {
947 if (s->s->get)
948 r = s->s->get(s);
949 else
950 warnx("get_as_string skip %s\n", s->name);
951 } else if (s->type == XT_S_INT)
952 r = g_strdup_printf("%d", *s->ival);
953 else if (s->type == XT_S_STR)
954 r = g_strdup(*s->sval);
955 else if (s->type == XT_S_FLOAT)
956 r = g_strdup_printf("%f", *s->fval);
957 else
958 r = g_strdup_printf("INVALID TYPE");
960 return (r);
963 void
964 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
966 int i;
967 char *s;
969 for (i = 0; i < LENGTH(rs); i++) {
970 if (rs[i].s && rs[i].s->walk)
971 rs[i].s->walk(&rs[i], cb, cb_args);
972 else {
973 s = get_as_string(&rs[i]);
974 cb(&rs[i], s, cb_args);
975 g_free(s);
981 set_browser_mode(struct settings *s, char *val)
983 if (!strcmp(val, "whitelist")) {
984 browser_mode = XT_BM_WHITELIST;
985 allow_volatile_cookies = 0;
986 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
987 cookies_enabled = 1;
988 enable_cookie_whitelist = 1;
989 read_only_cookies = 0;
990 save_rejected_cookies = 0;
991 session_timeout = 3600;
992 enable_scripts = 0;
993 enable_js_whitelist = 1;
994 } else if (!strcmp(val, "normal")) {
995 browser_mode = XT_BM_NORMAL;
996 allow_volatile_cookies = 0;
997 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
998 cookies_enabled = 1;
999 enable_cookie_whitelist = 0;
1000 read_only_cookies = 0;
1001 save_rejected_cookies = 0;
1002 session_timeout = 3600;
1003 enable_scripts = 1;
1004 enable_js_whitelist = 0;
1005 } else if (!strcmp(val, "kiosk")) {
1006 browser_mode = XT_BM_KIOSK;
1007 allow_volatile_cookies = 0;
1008 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1009 cookies_enabled = 1;
1010 enable_cookie_whitelist = 0;
1011 read_only_cookies = 0;
1012 save_rejected_cookies = 0;
1013 session_timeout = 3600;
1014 enable_scripts = 1;
1015 enable_js_whitelist = 0;
1016 show_tabs = 0;
1017 tabless = 1;
1018 } else
1019 return (1);
1021 return (0);
1024 char *
1025 get_browser_mode(struct settings *s)
1027 char *r = NULL;
1029 if (browser_mode == XT_BM_WHITELIST)
1030 r = g_strdup("whitelist");
1031 else if (browser_mode == XT_BM_NORMAL)
1032 r = g_strdup("normal");
1033 else if (browser_mode == XT_BM_KIOSK)
1034 r = g_strdup("kiosk");
1035 else
1036 return (NULL);
1038 return (r);
1042 set_cookie_policy(struct settings *s, char *val)
1044 if (!strcmp(val, "no3rdparty"))
1045 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1046 else if (!strcmp(val, "accept"))
1047 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1048 else if (!strcmp(val, "reject"))
1049 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1050 else
1051 return (1);
1053 return (0);
1056 char *
1057 get_cookie_policy(struct settings *s)
1059 char *r = NULL;
1061 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1062 r = g_strdup("no3rdparty");
1063 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1064 r = g_strdup("accept");
1065 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1066 r = g_strdup("reject");
1067 else
1068 return (NULL);
1070 return (r);
1073 char *
1074 get_download_dir(struct settings *s)
1076 if (download_dir[0] == '\0')
1077 return (0);
1078 return (g_strdup(download_dir));
1082 set_download_dir(struct settings *s, char *val)
1084 if (val[0] == '~')
1085 snprintf(download_dir, sizeof download_dir, "%s/%s",
1086 pwd->pw_dir, &val[1]);
1087 else
1088 strlcpy(download_dir, val, sizeof download_dir);
1090 return (0);
1094 * Session IDs.
1095 * We use these to prevent people putting xxxt:// URLs on
1096 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1098 #define XT_XTP_SES_KEY_SZ 8
1099 #define XT_XTP_SES_KEY_HEX_FMT \
1100 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1101 char *dl_session_key; /* downloads */
1102 char *hl_session_key; /* history list */
1103 char *cl_session_key; /* cookie list */
1104 char *fl_session_key; /* favorites list */
1106 char work_dir[PATH_MAX];
1107 char certs_dir[PATH_MAX];
1108 char cache_dir[PATH_MAX];
1109 char sessions_dir[PATH_MAX];
1110 char cookie_file[PATH_MAX];
1111 SoupURI *proxy_uri = NULL;
1112 SoupSession *session;
1113 SoupCookieJar *s_cookiejar;
1114 SoupCookieJar *p_cookiejar;
1115 char rc_fname[PATH_MAX];
1117 struct mime_type_list mtl;
1118 struct alias_list aliases;
1120 /* protos */
1121 struct tab *create_new_tab(char *, struct undo *, int);
1122 void delete_tab(struct tab *);
1123 void adjustfont_webkit(struct tab *, int);
1124 int run_script(struct tab *, char *);
1125 int download_rb_cmp(struct download *, struct download *);
1126 gboolean cmd_execute(struct tab *t, char *str);
1129 history_rb_cmp(struct history *h1, struct history *h2)
1131 return (strcmp(h1->uri, h2->uri));
1133 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1136 domain_rb_cmp(struct domain *d1, struct domain *d2)
1138 return (strcmp(d1->d, d2->d));
1140 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1142 char *
1143 get_work_dir(struct settings *s)
1145 if (work_dir[0] == '\0')
1146 return (0);
1147 return (g_strdup(work_dir));
1151 set_work_dir(struct settings *s, char *val)
1153 if (val[0] == '~')
1154 snprintf(work_dir, sizeof work_dir, "%s/%s",
1155 pwd->pw_dir, &val[1]);
1156 else
1157 strlcpy(work_dir, val, sizeof work_dir);
1159 return (0);
1163 * generate a session key to secure xtp commands.
1164 * pass in a ptr to the key in question and it will
1165 * be modified in place.
1167 void
1168 generate_xtp_session_key(char **key)
1170 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1172 /* free old key */
1173 if (*key)
1174 g_free(*key);
1176 /* make a new one */
1177 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1178 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1179 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1180 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1182 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1186 * validate a xtp session key.
1187 * return 1 if OK
1190 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1192 if (strcmp(trusted, untrusted) != 0) {
1193 show_oops(t, "%s: xtp session key mismatch possible spoof",
1194 __func__);
1195 return (0);
1198 return (1);
1202 download_rb_cmp(struct download *e1, struct download *e2)
1204 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1206 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1208 struct valid_url_types {
1209 char *type;
1210 } vut[] = {
1211 { "http://" },
1212 { "https://" },
1213 { "ftp://" },
1214 { "file://" },
1215 { XT_XTP_STR },
1219 valid_url_type(char *url)
1221 int i;
1223 for (i = 0; i < LENGTH(vut); i++)
1224 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1225 return (0);
1227 return (1);
1230 void
1231 print_cookie(char *msg, SoupCookie *c)
1233 if (c == NULL)
1234 return;
1236 if (msg)
1237 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1238 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1239 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1240 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1241 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1242 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1243 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1244 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1245 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1246 DNPRINTF(XT_D_COOKIE, "====================================\n");
1249 void
1250 walk_alias(struct settings *s,
1251 void (*cb)(struct settings *, char *, void *), void *cb_args)
1253 struct alias *a;
1254 char *str;
1256 if (s == NULL || cb == NULL) {
1257 show_oops_s("walk_alias invalid parameters");
1258 return;
1261 TAILQ_FOREACH(a, &aliases, entry) {
1262 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1263 cb(s, str, cb_args);
1264 g_free(str);
1268 char *
1269 match_alias(char *url_in)
1271 struct alias *a;
1272 char *arg;
1273 char *url_out = NULL, *search, *enc_arg;
1275 search = g_strdup(url_in);
1276 arg = search;
1277 if (strsep(&arg, " \t") == NULL) {
1278 show_oops_s("match_alias: NULL URL");
1279 goto done;
1282 TAILQ_FOREACH(a, &aliases, entry) {
1283 if (!strcmp(search, a->a_name))
1284 break;
1287 if (a != NULL) {
1288 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1289 a->a_name);
1290 if (arg != NULL) {
1291 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1292 url_out = g_strdup_printf(a->a_uri, enc_arg);
1293 g_free(enc_arg);
1294 } else
1295 url_out = g_strdup(a->a_uri);
1297 done:
1298 g_free(search);
1299 return (url_out);
1302 char *
1303 guess_url_type(char *url_in)
1305 struct stat sb;
1306 char *url_out = NULL, *enc_search = NULL;
1308 url_out = match_alias(url_in);
1309 if (url_out != NULL)
1310 return (url_out);
1312 if (guess_search) {
1314 * If there is no dot nor slash in the string and it isn't a
1315 * path to a local file and doesn't resolves to an IP, assume
1316 * that the user wants to search for the string.
1319 if (strchr(url_in, '.') == NULL &&
1320 strchr(url_in, '/') == NULL &&
1321 stat(url_in, &sb) != 0 &&
1322 gethostbyname(url_in) == NULL) {
1324 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1325 url_out = g_strdup_printf(search_string, enc_search);
1326 g_free(enc_search);
1327 return (url_out);
1331 /* XXX not sure about this heuristic */
1332 if (stat(url_in, &sb) == 0)
1333 url_out = g_strdup_printf("file://%s", url_in);
1334 else
1335 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1337 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1339 return (url_out);
1342 void
1343 load_uri(struct tab *t, gchar *uri)
1345 struct karg args;
1346 gchar *newuri = NULL;
1347 int i;
1349 if (uri == NULL)
1350 return;
1352 /* Strip leading spaces. */
1353 while(*uri && isspace(*uri))
1354 uri++;
1356 if (strlen(uri) == 0) {
1357 blank(t, NULL);
1358 return;
1361 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1362 for (i = 0; i < LENGTH(about_list); i++)
1363 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1364 bzero(&args, sizeof args);
1365 about_list[i].func(t, &args);
1366 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1367 FALSE);
1368 return;
1370 show_oops(t, "invalid about page");
1371 return;
1374 if (valid_url_type(uri)) {
1375 newuri = guess_url_type(uri);
1376 uri = newuri;
1379 set_status(t, (char *)uri, XT_STATUS_LOADING);
1380 webkit_web_view_load_uri(t->wv, uri);
1382 if (newuri)
1383 g_free(newuri);
1386 const gchar *
1387 get_uri(WebKitWebView *wv)
1389 WebKitWebFrame *frame;
1390 const gchar *uri;
1392 frame = webkit_web_view_get_main_frame(wv);
1393 uri = webkit_web_frame_get_uri(frame);
1395 if (uri && strlen(uri) > 0)
1396 return (uri);
1397 else
1398 return (NULL);
1402 add_alias(struct settings *s, char *line)
1404 char *l, *alias;
1405 struct alias *a = NULL;
1407 if (s == NULL || line == NULL) {
1408 show_oops_s("add_alias invalid parameters");
1409 return (1);
1412 l = line;
1413 a = g_malloc(sizeof(*a));
1415 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1416 show_oops_s("add_alias: incomplete alias definition");
1417 goto bad;
1419 if (strlen(alias) == 0 || strlen(l) == 0) {
1420 show_oops_s("add_alias: invalid alias definition");
1421 goto bad;
1424 a->a_name = g_strdup(alias);
1425 a->a_uri = g_strdup(l);
1427 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1429 TAILQ_INSERT_TAIL(&aliases, a, entry);
1431 return (0);
1432 bad:
1433 if (a)
1434 g_free(a);
1435 return (1);
1439 add_mime_type(struct settings *s, char *line)
1441 char *mime_type;
1442 char *l;
1443 struct mime_type *m = NULL;
1444 int downloadfirst = 0;
1446 /* XXX this could be smarter */
1448 if (line == NULL && strlen(line) == 0) {
1449 show_oops_s("add_mime_type invalid parameters");
1450 return (1);
1453 l = line;
1454 if (*l == '@') {
1455 downloadfirst = 1;
1456 l++;
1458 m = g_malloc(sizeof(*m));
1460 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1461 show_oops_s("add_mime_type: invalid mime_type");
1462 goto bad;
1464 if (mime_type[strlen(mime_type) - 1] == '*') {
1465 mime_type[strlen(mime_type) - 1] = '\0';
1466 m->mt_default = 1;
1467 } else
1468 m->mt_default = 0;
1470 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1471 show_oops_s("add_mime_type: invalid mime_type");
1472 goto bad;
1475 m->mt_type = g_strdup(mime_type);
1476 m->mt_action = g_strdup(l);
1477 m->mt_download = downloadfirst;
1479 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1480 m->mt_type, m->mt_action, m->mt_default);
1482 TAILQ_INSERT_TAIL(&mtl, m, entry);
1484 return (0);
1485 bad:
1486 if (m)
1487 g_free(m);
1488 return (1);
1491 struct mime_type *
1492 find_mime_type(char *mime_type)
1494 struct mime_type *m, *def = NULL, *rv = NULL;
1496 TAILQ_FOREACH(m, &mtl, entry) {
1497 if (m->mt_default &&
1498 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1499 def = m;
1501 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1502 rv = m;
1503 break;
1507 if (rv == NULL)
1508 rv = def;
1510 return (rv);
1513 void
1514 walk_mime_type(struct settings *s,
1515 void (*cb)(struct settings *, char *, void *), void *cb_args)
1517 struct mime_type *m;
1518 char *str;
1520 if (s == NULL || cb == NULL)
1521 show_oops_s("walk_mime_type invalid parameters");
1523 TAILQ_FOREACH(m, &mtl, entry) {
1524 str = g_strdup_printf("%s%s --> %s",
1525 m->mt_type,
1526 m->mt_default ? "*" : "",
1527 m->mt_action);
1528 cb(s, str, cb_args);
1529 g_free(str);
1533 void
1534 wl_add(char *str, struct domain_list *wl, int handy)
1536 struct domain *d;
1537 int add_dot = 0;
1539 if (str == NULL || wl == NULL || strlen(str) < 2)
1540 return;
1542 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1544 /* treat *.moo.com the same as .moo.com */
1545 if (str[0] == '*' && str[1] == '.')
1546 str = &str[1];
1547 else if (str[0] == '.')
1548 str = &str[0];
1549 else
1550 add_dot = 1;
1552 d = g_malloc(sizeof *d);
1553 if (add_dot)
1554 d->d = g_strdup_printf(".%s", str);
1555 else
1556 d->d = g_strdup(str);
1557 d->handy = handy;
1559 if (RB_INSERT(domain_list, wl, d))
1560 goto unwind;
1562 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1563 return;
1564 unwind:
1565 if (d) {
1566 if (d->d)
1567 g_free(d->d);
1568 g_free(d);
1573 add_cookie_wl(struct settings *s, char *entry)
1575 wl_add(entry, &c_wl, 1);
1576 return (0);
1579 void
1580 walk_cookie_wl(struct settings *s,
1581 void (*cb)(struct settings *, char *, void *), void *cb_args)
1583 struct domain *d;
1585 if (s == NULL || cb == NULL) {
1586 show_oops_s("walk_cookie_wl invalid parameters");
1587 return;
1590 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1591 cb(s, d->d, cb_args);
1594 void
1595 walk_js_wl(struct settings *s,
1596 void (*cb)(struct settings *, char *, void *), void *cb_args)
1598 struct domain *d;
1600 if (s == NULL || cb == NULL) {
1601 show_oops_s("walk_js_wl invalid parameters");
1602 return;
1605 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1606 cb(s, d->d, cb_args);
1610 add_js_wl(struct settings *s, char *entry)
1612 wl_add(entry, &js_wl, 1 /* persistent */);
1613 return (0);
1616 struct domain *
1617 wl_find(const gchar *search, struct domain_list *wl)
1619 int i;
1620 struct domain *d = NULL, dfind;
1621 gchar *s = NULL;
1623 if (search == NULL || wl == NULL)
1624 return (NULL);
1625 if (strlen(search) < 2)
1626 return (NULL);
1628 if (search[0] != '.')
1629 s = g_strdup_printf(".%s", search);
1630 else
1631 s = g_strdup(search);
1633 for (i = strlen(s) - 1; i >= 0; i--) {
1634 if (s[i] == '.') {
1635 dfind.d = &s[i];
1636 d = RB_FIND(domain_list, wl, &dfind);
1637 if (d)
1638 goto done;
1642 done:
1643 if (s)
1644 g_free(s);
1646 return (d);
1649 struct domain *
1650 wl_find_uri(const gchar *s, struct domain_list *wl)
1652 int i;
1653 char *ss;
1654 struct domain *r;
1656 if (s == NULL || wl == NULL)
1657 return (NULL);
1659 if (!strncmp(s, "http://", strlen("http://")))
1660 s = &s[strlen("http://")];
1661 else if (!strncmp(s, "https://", strlen("https://")))
1662 s = &s[strlen("https://")];
1664 if (strlen(s) < 2)
1665 return (NULL);
1667 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1668 /* chop string at first slash */
1669 if (s[i] == '/' || s[i] == '\0') {
1670 ss = g_strdup(s);
1671 ss[i] = '\0';
1672 r = wl_find(ss, wl);
1673 g_free(ss);
1674 return (r);
1677 return (NULL);
1680 char *
1681 get_toplevel_domain(char *domain)
1683 char *s;
1684 int found = 0;
1686 if (domain == NULL)
1687 return (NULL);
1688 if (strlen(domain) < 2)
1689 return (NULL);
1691 s = &domain[strlen(domain) - 1];
1692 while (s != domain) {
1693 if (*s == '.') {
1694 found++;
1695 if (found == 2)
1696 return (s);
1698 s--;
1701 if (found)
1702 return (domain);
1704 return (NULL);
1708 settings_add(char *var, char *val)
1710 int i, rv, *p;
1711 gfloat *f;
1712 char **s;
1714 /* get settings */
1715 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1716 if (strcmp(var, rs[i].name))
1717 continue;
1719 if (rs[i].s) {
1720 if (rs[i].s->set(&rs[i], val))
1721 errx(1, "invalid value for %s: %s", var, val);
1722 rv = 1;
1723 break;
1724 } else
1725 switch (rs[i].type) {
1726 case XT_S_INT:
1727 p = rs[i].ival;
1728 *p = atoi(val);
1729 rv = 1;
1730 break;
1731 case XT_S_STR:
1732 s = rs[i].sval;
1733 if (s == NULL)
1734 errx(1, "invalid sval for %s",
1735 rs[i].name);
1736 if (*s)
1737 g_free(*s);
1738 *s = g_strdup(val);
1739 rv = 1;
1740 break;
1741 case XT_S_FLOAT:
1742 f = rs[i].fval;
1743 *f = atof(val);
1744 rv = 1;
1745 break;
1746 case XT_S_INVALID:
1747 default:
1748 errx(1, "invalid type for %s", var);
1750 break;
1752 return (rv);
1755 #define WS "\n= \t"
1756 void
1757 config_parse(char *filename, int runtime)
1759 FILE *config, *f;
1760 char *line, *cp, *var, *val;
1761 size_t len, lineno = 0;
1762 int handled;
1763 char file[PATH_MAX];
1764 struct stat sb;
1766 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1768 if (filename == NULL)
1769 return;
1771 if (runtime && runtime_settings[0] != '\0') {
1772 snprintf(file, sizeof file, "%s/%s",
1773 work_dir, runtime_settings);
1774 if (stat(file, &sb)) {
1775 warnx("runtime file doesn't exist, creating it");
1776 if ((f = fopen(file, "w")) == NULL)
1777 err(1, "runtime");
1778 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1779 fclose(f);
1781 } else
1782 strlcpy(file, filename, sizeof file);
1784 if ((config = fopen(file, "r")) == NULL) {
1785 warn("config_parse: cannot open %s", filename);
1786 return;
1789 for (;;) {
1790 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1791 if (feof(config) || ferror(config))
1792 break;
1794 cp = line;
1795 cp += (long)strspn(cp, WS);
1796 if (cp[0] == '\0') {
1797 /* empty line */
1798 free(line);
1799 continue;
1802 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1803 errx(1, "invalid config file entry: %s", line);
1805 cp += (long)strspn(cp, WS);
1807 if ((val = strsep(&cp, "\0")) == NULL)
1808 break;
1810 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1811 handled = settings_add(var, val);
1812 if (handled == 0)
1813 errx(1, "invalid conf file entry: %s=%s", var, val);
1815 free(line);
1818 fclose(config);
1821 char *
1822 js_ref_to_string(JSContextRef context, JSValueRef ref)
1824 char *s = NULL;
1825 size_t l;
1826 JSStringRef jsref;
1828 jsref = JSValueToStringCopy(context, ref, NULL);
1829 if (jsref == NULL)
1830 return (NULL);
1832 l = JSStringGetMaximumUTF8CStringSize(jsref);
1833 s = g_malloc(l);
1834 if (s)
1835 JSStringGetUTF8CString(jsref, s, l);
1836 JSStringRelease(jsref);
1838 return (s);
1841 void
1842 disable_hints(struct tab *t)
1844 bzero(t->hint_buf, sizeof t->hint_buf);
1845 bzero(t->hint_num, sizeof t->hint_num);
1846 run_script(t, "vimprobable_clear()");
1847 t->hints_on = 0;
1848 t->hint_mode = XT_HINT_NONE;
1851 void
1852 enable_hints(struct tab *t)
1854 bzero(t->hint_buf, sizeof t->hint_buf);
1855 run_script(t, "vimprobable_show_hints()");
1856 t->hints_on = 1;
1857 t->hint_mode = XT_HINT_NONE;
1860 #define XT_JS_OPEN ("open;")
1861 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1862 #define XT_JS_FIRE ("fire;")
1863 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1864 #define XT_JS_FOUND ("found;")
1865 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1868 run_script(struct tab *t, char *s)
1870 JSGlobalContextRef ctx;
1871 WebKitWebFrame *frame;
1872 JSStringRef str;
1873 JSValueRef val, exception;
1874 char *es, buf[128];
1876 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1877 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1879 frame = webkit_web_view_get_main_frame(t->wv);
1880 ctx = webkit_web_frame_get_global_context(frame);
1882 str = JSStringCreateWithUTF8CString(s);
1883 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1884 NULL, 0, &exception);
1885 JSStringRelease(str);
1887 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1888 if (val == NULL) {
1889 es = js_ref_to_string(ctx, exception);
1890 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1891 g_free(es);
1892 return (1);
1893 } else {
1894 es = js_ref_to_string(ctx, val);
1895 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1897 /* handle return value right here */
1898 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1899 disable_hints(t);
1900 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1903 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1904 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1905 &es[XT_JS_FIRE_LEN]);
1906 run_script(t, buf);
1907 disable_hints(t);
1910 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1911 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1912 disable_hints(t);
1915 g_free(es);
1918 return (0);
1922 hint(struct tab *t, struct karg *args)
1925 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1927 if (t->hints_on == 0)
1928 enable_hints(t);
1929 else
1930 disable_hints(t);
1932 return (0);
1935 void
1936 apply_style(struct tab *t)
1938 g_object_set(G_OBJECT(t->settings),
1939 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1943 userstyle(struct tab *t, struct karg *args)
1945 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1947 if (t->styled) {
1948 t->styled = 0;
1949 g_object_set(G_OBJECT(t->settings),
1950 "user-stylesheet-uri", NULL, (char *)NULL);
1951 } else {
1952 t->styled = 1;
1953 apply_style(t);
1955 return (0);
1959 * Doesn't work fully, due to the following bug:
1960 * https://bugs.webkit.org/show_bug.cgi?id=51747
1963 restore_global_history(void)
1965 char file[PATH_MAX];
1966 FILE *f;
1967 struct history *h;
1968 gchar *uri;
1969 gchar *title;
1971 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1973 if ((f = fopen(file, "r")) == NULL) {
1974 warnx("%s: fopen", __func__);
1975 return (1);
1978 for (;;) {
1979 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1980 if (feof(f) || ferror(f))
1981 break;
1983 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1984 if (feof(f) || ferror(f)) {
1985 free(uri);
1986 warnx("%s: broken history file\n", __func__);
1987 return (1);
1990 if (uri && strlen(uri) && title && strlen(title)) {
1991 webkit_web_history_item_new_with_data(uri, title);
1992 h = g_malloc(sizeof(struct history));
1993 h->uri = g_strdup(uri);
1994 h->title = g_strdup(title);
1995 RB_INSERT(history_list, &hl, h);
1996 completion_add_uri(h->uri);
1997 } else {
1998 warnx("%s: failed to restore history\n", __func__);
1999 free(uri);
2000 free(title);
2001 return (1);
2004 free(uri);
2005 free(title);
2006 uri = NULL;
2007 title = NULL;
2010 return (0);
2014 save_global_history_to_disk(struct tab *t)
2016 char file[PATH_MAX];
2017 FILE *f;
2018 struct history *h;
2020 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2022 if ((f = fopen(file, "w")) == NULL) {
2023 show_oops(t, "%s: global history file: %s",
2024 __func__, strerror(errno));
2025 return (1);
2028 RB_FOREACH_REVERSE(h, history_list, &hl) {
2029 if (h->uri && h->title)
2030 fprintf(f, "%s\n%s\n", h->uri, h->title);
2033 fclose(f);
2035 return (0);
2039 quit(struct tab *t, struct karg *args)
2041 if (save_global_history)
2042 save_global_history_to_disk(t);
2044 gtk_main_quit();
2046 return (1);
2050 open_tabs(struct tab *t, struct karg *a)
2052 char file[PATH_MAX];
2053 FILE *f = NULL;
2054 char *uri = NULL;
2055 int rv = 1;
2056 struct tab *ti, *tt;
2058 if (a == NULL)
2059 goto done;
2061 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2062 if ((f = fopen(file, "r")) == NULL)
2063 goto done;
2065 ti = TAILQ_LAST(&tabs, tab_list);
2067 for (;;) {
2068 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2069 if (feof(f) || ferror(f))
2070 break;
2072 /* retrieve session name */
2073 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2074 strlcpy(named_session,
2075 &uri[strlen(XT_SAVE_SESSION_ID)],
2076 sizeof named_session);
2077 continue;
2080 if (uri && strlen(uri))
2081 create_new_tab(uri, NULL, 1);
2083 free(uri);
2084 uri = NULL;
2087 /* close open tabs */
2088 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2089 for (;;) {
2090 tt = TAILQ_FIRST(&tabs);
2091 if (tt != ti) {
2092 delete_tab(tt);
2093 continue;
2095 delete_tab(tt);
2096 break;
2100 rv = 0;
2101 done:
2102 if (f)
2103 fclose(f);
2105 return (rv);
2109 restore_saved_tabs(void)
2111 char file[PATH_MAX];
2112 int unlink_file = 0;
2113 struct stat sb;
2114 struct karg a;
2115 int rv = 0;
2117 snprintf(file, sizeof file, "%s/%s",
2118 sessions_dir, XT_RESTART_TABS_FILE);
2119 if (stat(file, &sb) == -1)
2120 a.s = XT_SAVED_TABS_FILE;
2121 else {
2122 unlink_file = 1;
2123 a.s = XT_RESTART_TABS_FILE;
2126 a.i = XT_SES_DONOTHING;
2127 rv = open_tabs(NULL, &a);
2129 if (unlink_file)
2130 unlink(file);
2132 return (rv);
2136 save_tabs(struct tab *t, struct karg *a)
2138 char file[PATH_MAX];
2139 FILE *f;
2140 struct tab *ti;
2141 const gchar *uri;
2142 int len = 0, i;
2143 const gchar **arr = NULL;
2145 if (a == NULL)
2146 return (1);
2147 if (a->s == NULL)
2148 snprintf(file, sizeof file, "%s/%s",
2149 sessions_dir, named_session);
2150 else
2151 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2153 if ((f = fopen(file, "w")) == NULL) {
2154 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2155 return (1);
2158 /* save session name */
2159 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2161 /* save tabs, in the order they are arranged in the notebook */
2162 TAILQ_FOREACH(ti, &tabs, entry)
2163 len++;
2165 arr = g_malloc0(len * sizeof(gchar *));
2167 TAILQ_FOREACH(ti, &tabs, entry) {
2168 if ((uri = get_uri(ti->wv)) != NULL)
2169 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2172 for (i = 0; i < len; i++)
2173 if (arr[i])
2174 fprintf(f, "%s\n", arr[i]);
2176 g_free(arr);
2177 fclose(f);
2179 return (0);
2183 save_tabs_and_quit(struct tab *t, struct karg *args)
2185 struct karg a;
2187 a.s = NULL;
2188 save_tabs(t, &a);
2189 quit(t, NULL);
2191 return (1);
2195 yank_uri(struct tab *t, struct karg *args)
2197 const gchar *uri;
2198 GtkClipboard *clipboard;
2200 if ((uri = get_uri(t->wv)) == NULL)
2201 return (1);
2203 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2204 gtk_clipboard_set_text(clipboard, uri, -1);
2206 return (0);
2210 paste_uri(struct tab *t, struct karg *args)
2212 GtkClipboard *clipboard;
2213 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2214 gint len;
2215 gchar *p = NULL, *uri;
2217 /* try primary clipboard first */
2218 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2219 p = gtk_clipboard_wait_for_text(clipboard);
2221 /* if it failed get whatever text is in cut_buffer0 */
2222 if (p == NULL)
2223 if (gdk_property_get(gdk_get_default_root_window(),
2224 atom,
2225 gdk_atom_intern("STRING", FALSE),
2227 65536 /* picked out of my butt */,
2228 FALSE,
2229 NULL,
2230 NULL,
2231 &len,
2232 (guchar **)&p)) {
2233 /* yes sir, we need to NUL the string */
2234 p[len] = '\0';
2237 if (p) {
2238 uri = p;
2239 while(*uri && isspace(*uri))
2240 uri++;
2241 if (strlen(uri) == 0) {
2242 show_oops(t, "empty paste buffer");
2243 goto done;
2245 if (valid_url_type(uri)) {
2246 /* we can be clever and paste this in search box */
2247 show_oops(t, "not a valid URL");
2248 goto done;
2251 if (args->i == XT_PASTE_CURRENT_TAB)
2252 load_uri(t, uri);
2253 else if (args->i == XT_PASTE_NEW_TAB)
2254 create_new_tab(uri, NULL, 1);
2257 done:
2258 if (p)
2259 g_free(p);
2261 return (0);
2264 char *
2265 find_domain(const gchar *s, int add_dot)
2267 int i;
2268 char *r = NULL, *ss = NULL;
2270 if (s == NULL)
2271 return (NULL);
2273 if (!strncmp(s, "http://", strlen("http://")))
2274 s = &s[strlen("http://")];
2275 else if (!strncmp(s, "https://", strlen("https://")))
2276 s = &s[strlen("https://")];
2278 if (strlen(s) < 2)
2279 return (NULL);
2281 ss = g_strdup(s);
2282 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2283 /* chop string at first slash */
2284 if (ss[i] == '/' || ss[i] == '\0') {
2285 ss[i] = '\0';
2286 if (add_dot)
2287 r = g_strdup_printf(".%s", ss);
2288 else
2289 r = g_strdup(ss);
2290 break;
2292 g_free(ss);
2294 return (r);
2298 toggle_cwl(struct tab *t, struct karg *args)
2300 struct domain *d;
2301 const gchar *uri;
2302 char *dom = NULL, *dom_toggle = NULL;
2303 int es;
2305 if (args == NULL)
2306 return (1);
2308 uri = get_uri(t->wv);
2309 dom = find_domain(uri, 1);
2310 d = wl_find(dom, &c_wl);
2312 if (d == NULL)
2313 es = 0;
2314 else
2315 es = 1;
2317 if (args->i & XT_WL_TOGGLE)
2318 es = !es;
2319 else if ((args->i & XT_WL_ENABLE) && es != 1)
2320 es = 1;
2321 else if ((args->i & XT_WL_DISABLE) && es != 0)
2322 es = 0;
2324 if (args->i & XT_WL_TOPLEVEL)
2325 dom_toggle = get_toplevel_domain(dom);
2326 else
2327 dom_toggle = dom;
2329 if (es)
2330 /* enable cookies for domain */
2331 wl_add(dom_toggle, &c_wl, 0);
2332 else
2333 /* disable cookies for domain */
2334 RB_REMOVE(domain_list, &c_wl, d);
2336 webkit_web_view_reload(t->wv);
2338 g_free(dom);
2339 return (0);
2343 toggle_js(struct tab *t, struct karg *args)
2345 int es;
2346 const gchar *uri;
2347 struct domain *d;
2348 char *dom = NULL, *dom_toggle = NULL;
2350 if (args == NULL)
2351 return (1);
2353 g_object_get(G_OBJECT(t->settings),
2354 "enable-scripts", &es, (char *)NULL);
2355 if (args->i & XT_WL_TOGGLE)
2356 es = !es;
2357 else if ((args->i & XT_WL_ENABLE) && es != 1)
2358 es = 1;
2359 else if ((args->i & XT_WL_DISABLE) && es != 0)
2360 es = 0;
2361 else
2362 return (1);
2364 uri = get_uri(t->wv);
2365 dom = find_domain(uri, 1);
2367 if (uri == NULL || dom == NULL) {
2368 show_oops(t, "Can't toggle domain in JavaScript white list");
2369 goto done;
2372 if (args->i & XT_WL_TOPLEVEL)
2373 dom_toggle = get_toplevel_domain(dom);
2374 else
2375 dom_toggle = dom;
2377 if (es) {
2378 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2379 wl_add(dom_toggle, &js_wl, 0 /* session */);
2380 } else {
2381 d = wl_find(dom_toggle, &js_wl);
2382 if (d)
2383 RB_REMOVE(domain_list, &js_wl, d);
2384 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2386 g_object_set(G_OBJECT(t->settings),
2387 "enable-scripts", es, (char *)NULL);
2388 g_object_set(G_OBJECT(t->settings),
2389 "javascript-can-open-windows-automatically", es, (char *)NULL);
2390 webkit_web_view_set_settings(t->wv, t->settings);
2391 webkit_web_view_reload(t->wv);
2392 done:
2393 if (dom)
2394 g_free(dom);
2395 return (0);
2398 void
2399 js_toggle_cb(GtkWidget *w, struct tab *t)
2401 struct karg a;
2403 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2404 toggle_js(t, &a);
2408 toggle_src(struct tab *t, struct karg *args)
2410 gboolean mode;
2412 if (t == NULL)
2413 return (0);
2415 mode = webkit_web_view_get_view_source_mode(t->wv);
2416 webkit_web_view_set_view_source_mode(t->wv, !mode);
2417 webkit_web_view_reload(t->wv);
2419 return (0);
2422 void
2423 focus_webview(struct tab *t)
2425 if (t == NULL)
2426 return;
2428 /* only grab focus if we are visible */
2429 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2430 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2434 focus(struct tab *t, struct karg *args)
2436 if (t == NULL || args == NULL)
2437 return (1);
2439 if (show_url == 0)
2440 return (0);
2442 if (args->i == XT_FOCUS_URI)
2443 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2444 else if (args->i == XT_FOCUS_SEARCH)
2445 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2447 return (0);
2451 stats(struct tab *t, struct karg *args)
2453 char *stats, *s, line[64 * 1024];
2454 uint64_t line_count = 0;
2455 FILE *r_cookie_f;
2457 if (t == NULL)
2458 show_oops_s("stats invalid parameters");
2460 line[0] = '\0';
2461 if (save_rejected_cookies) {
2462 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2463 for (;;) {
2464 s = fgets(line, sizeof line, r_cookie_f);
2465 if (s == NULL || feof(r_cookie_f) ||
2466 ferror(r_cookie_f))
2467 break;
2468 line_count++;
2470 fclose(r_cookie_f);
2471 snprintf(line, sizeof line,
2472 "<br>Cookies blocked(*) total: %llu", line_count);
2473 } else
2474 show_oops(t, "Can't open blocked cookies file: %s",
2475 strerror(errno));
2478 stats = g_strdup_printf(XT_DOCTYPE
2479 "<html>"
2480 "<head>"
2481 "<title>Statistics</title>"
2482 "</head>"
2483 "<h1>Statistics</h1>"
2484 "<body>"
2485 "Cookies blocked(*) this session: %llu"
2486 "%s"
2487 "<p><small><b>*</b> results vary based on settings"
2488 "</body>"
2489 "</html>",
2490 blocked_cookies,
2491 line);
2493 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2494 g_free(stats);
2496 return (0);
2500 marco(struct tab *t, struct karg *args)
2502 char *message, line[64 * 1024];
2503 int len;
2505 if (t == NULL)
2506 show_oops_s("marco invalid parameters");
2508 line[0] = '\0';
2509 snprintf(line, sizeof line, "<br>%s", marco_message(&len));
2511 message = g_strdup_printf(XT_DOCTYPE
2512 "<html>"
2513 "<head>"
2514 "<title>Marco Sez...</title>"
2515 "</head>"
2516 "<h1>Moo</h1>"
2517 "<body>"
2518 "%s"
2519 "</body>"
2520 "</html>",
2521 line);
2523 load_webkit_string(t, message, XT_URI_ABOUT_MARCO);
2524 g_free(message);
2526 return (0);
2530 blank(struct tab *t, struct karg *args)
2532 if (t == NULL)
2533 show_oops_s("blank invalid parameters");
2535 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2537 return (0);
2540 about(struct tab *t, struct karg *args)
2542 char *about;
2544 if (t == NULL)
2545 show_oops_s("about invalid parameters");
2547 about = g_strdup_printf(XT_DOCTYPE
2548 "<html>"
2549 "<head>"
2550 "<title>About</title>"
2551 "</head>"
2552 "<h1>About</h1>"
2553 "<body>"
2554 "<b>Version: %s</b><p>"
2555 "Authors:"
2556 "<ul>"
2557 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2558 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2559 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2560 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2561 "</ul>"
2562 "Copyrights and licenses can be found on the XXXterm "
2563 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2564 "</body>"
2565 "</html>",
2566 version
2569 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2570 g_free(about);
2572 return (0);
2576 help(struct tab *t, struct karg *args)
2578 char *help;
2580 if (t == NULL)
2581 show_oops_s("help invalid parameters");
2583 help = XT_DOCTYPE
2584 "<html>"
2585 "<head>"
2586 "<title>XXXterm</title>"
2587 "<meta http-equiv=\"REFRESH\" content=\"0;"
2588 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2589 "</head>"
2590 "<body>"
2591 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2592 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2593 "cgi-bin/man-cgi?xxxterm</a>"
2594 "</body>"
2595 "</html>"
2598 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2600 return (0);
2604 * update all favorite tabs apart from one. Pass NULL if
2605 * you want to update all.
2607 void
2608 update_favorite_tabs(struct tab *apart_from)
2610 struct tab *t;
2611 if (!updating_fl_tabs) {
2612 updating_fl_tabs = 1; /* stop infinite recursion */
2613 TAILQ_FOREACH(t, &tabs, entry)
2614 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2615 && (t != apart_from))
2616 xtp_page_fl(t, NULL);
2617 updating_fl_tabs = 0;
2621 /* show a list of favorites (bookmarks) */
2623 xtp_page_fl(struct tab *t, struct karg *args)
2625 char file[PATH_MAX];
2626 FILE *f;
2627 char *uri = NULL, *title = NULL;
2628 size_t len, lineno = 0;
2629 int i, failed = 0;
2630 char *header, *body, *tmp, *html = NULL;
2632 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2634 if (t == NULL)
2635 warn("%s: bad param", __func__);
2637 /* mark tab as favorite list */
2638 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2640 /* new session key */
2641 if (!updating_fl_tabs)
2642 generate_xtp_session_key(&fl_session_key);
2644 /* open favorites */
2645 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2646 if ((f = fopen(file, "r")) == NULL) {
2647 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2648 return (1);
2651 /* header */
2652 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2653 "<title>Favorites</title>\n"
2654 "%s"
2655 "</head>"
2656 "<h1>Favorites</h1>\n",
2657 XT_PAGE_STYLE);
2659 /* body */
2660 body = g_strdup_printf("<div align='center'><table><tr>"
2661 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2662 "<th style='width: 15%%'>Remove</th></tr>\n");
2664 for (i = 1;;) {
2665 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2666 if (feof(f) || ferror(f))
2667 break;
2668 if (len == 0) {
2669 free(title);
2670 title = NULL;
2671 continue;
2674 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2675 if (feof(f) || ferror(f)) {
2676 show_oops(t, "favorites file corrupt");
2677 failed = 1;
2678 break;
2681 tmp = body;
2682 body = g_strdup_printf("%s<tr>"
2683 "<td>%d</td>"
2684 "<td><a href='%s'>%s</a></td>"
2685 "<td style='text-align: center'>"
2686 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2687 "</tr>\n",
2688 body, i, uri, title,
2689 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2691 g_free(tmp);
2693 free(uri);
2694 uri = NULL;
2695 free(title);
2696 title = NULL;
2697 i++;
2699 fclose(f);
2701 /* if none, say so */
2702 if (i == 1) {
2703 tmp = body;
2704 body = g_strdup_printf("%s<tr>"
2705 "<td colspan='3' style='text-align: center'>"
2706 "No favorites - To add one use the 'favadd' command."
2707 "</td></tr>", body);
2708 g_free(tmp);
2711 if (uri)
2712 free(uri);
2713 if (title)
2714 free(title);
2716 /* render */
2717 if (!failed) {
2718 html = g_strdup_printf("%s%s</table></div></html>",
2719 header, body);
2720 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2723 update_favorite_tabs(t);
2725 if (header)
2726 g_free(header);
2727 if (body)
2728 g_free(body);
2729 if (html)
2730 g_free(html);
2732 return (failed);
2735 void
2736 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2737 size_t cert_count, char *title)
2739 gnutls_datum_t cinfo;
2740 char *tmp, *header, *body, *footer;
2741 int i;
2743 header = g_strdup_printf("<html><head><title>%s</title></head><body>", title);
2744 footer = g_strdup("</body></html>");
2745 body = g_strdup("");
2747 for (i = 0; i < cert_count; i++) {
2748 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2749 &cinfo))
2750 return;
2752 tmp = body;
2753 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2754 body, i, cinfo.data);
2755 gnutls_free(cinfo.data);
2756 g_free(tmp);
2759 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2760 g_free(header);
2761 g_free(body);
2762 g_free(footer);
2763 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2764 g_free(tmp);
2768 ca_cmd(struct tab *t, struct karg *args)
2770 FILE *f = NULL;
2771 int rv = 1, certs = 0, certs_read;
2772 struct stat sb;
2773 gnutls_datum dt;
2774 gnutls_x509_crt_t *c = NULL;
2775 char *certs_buf = NULL, *s;
2777 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2778 show_oops(t, "Can't open CA file: %s", strerror(errno));
2779 return (1);
2782 if (fstat(fileno(f), &sb) == -1) {
2783 show_oops(t, "Can't stat CA file: %s", strerror(errno));
2784 goto done;
2787 certs_buf = g_malloc(sb.st_size + 1);
2788 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2789 show_oops(t, "Can't read CA file: %s", strerror(errno));
2790 goto done;
2792 certs_buf[sb.st_size] = '\0';
2794 s = certs_buf;
2795 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2796 certs++;
2797 s += strlen("BEGIN CERTIFICATE");
2800 bzero(&dt, sizeof dt);
2801 dt.data = certs_buf;
2802 dt.size = sb.st_size;
2803 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2804 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2805 GNUTLS_X509_FMT_PEM, 0);
2806 if (certs_read <= 0) {
2807 show_oops(t, "No cert(s) available");
2808 goto done;
2810 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2811 done:
2812 if (c)
2813 g_free(c);
2814 if (certs_buf)
2815 g_free(certs_buf);
2816 if (f)
2817 fclose(f);
2819 return (rv);
2823 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2825 SoupURI *su = NULL;
2826 struct addrinfo hints, *res = NULL, *ai;
2827 int s = -1, on;
2828 char port[8];
2830 if (uri && !g_str_has_prefix(uri, "https://"))
2831 goto done;
2833 su = soup_uri_new(uri);
2834 if (su == NULL)
2835 goto done;
2836 if (!SOUP_URI_VALID_FOR_HTTP(su))
2837 goto done;
2839 snprintf(port, sizeof port, "%d", su->port);
2840 bzero(&hints, sizeof(struct addrinfo));
2841 hints.ai_flags = AI_CANONNAME;
2842 hints.ai_family = AF_UNSPEC;
2843 hints.ai_socktype = SOCK_STREAM;
2845 if (getaddrinfo(su->host, port, &hints, &res))
2846 goto done;
2848 for (ai = res; ai; ai = ai->ai_next) {
2849 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2850 continue;
2852 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2853 if (s < 0)
2854 goto done;
2855 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2856 sizeof(on)) == -1)
2857 goto done;
2859 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2860 goto done;
2863 if (domain)
2864 strlcpy(domain, su->host, domain_sz);
2865 done:
2866 if (su)
2867 soup_uri_free(su);
2868 if (res)
2869 freeaddrinfo(res);
2871 return (s);
2875 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2877 if (gsession)
2878 gnutls_deinit(gsession);
2879 if (xcred)
2880 gnutls_certificate_free_credentials(xcred);
2882 return (0);
2886 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2887 gnutls_certificate_credentials_t *xc)
2889 gnutls_certificate_credentials_t xcred;
2890 gnutls_session_t gsession;
2891 int rv = 1;
2893 if (gs == NULL || xc == NULL)
2894 goto done;
2896 *gs = NULL;
2897 *xc = NULL;
2899 gnutls_certificate_allocate_credentials(&xcred);
2900 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2901 GNUTLS_X509_FMT_PEM);
2902 gnutls_init(&gsession, GNUTLS_CLIENT);
2903 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2904 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2905 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2906 if ((rv = gnutls_handshake(gsession)) < 0) {
2907 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2909 gnutls_error_is_fatal(rv),
2910 gnutls_strerror_name(rv));
2911 stop_tls(gsession, xcred);
2912 goto done;
2915 gnutls_credentials_type_t cred;
2916 cred = gnutls_auth_get_type(gsession);
2917 if (cred != GNUTLS_CRD_CERTIFICATE) {
2918 stop_tls(gsession, xcred);
2919 goto done;
2922 *gs = gsession;
2923 *xc = xcred;
2924 rv = 0;
2925 done:
2926 return (rv);
2930 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2931 size_t *cert_count)
2933 unsigned int len;
2934 const gnutls_datum_t *cl;
2935 gnutls_x509_crt_t *all_certs;
2936 int i, rv = 1;
2938 if (certs == NULL || cert_count == NULL)
2939 goto done;
2940 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2941 goto done;
2942 cl = gnutls_certificate_get_peers(gsession, &len);
2943 if (len == 0)
2944 goto done;
2946 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2947 for (i = 0; i < len; i++) {
2948 gnutls_x509_crt_init(&all_certs[i]);
2949 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2950 GNUTLS_X509_FMT_PEM < 0)) {
2951 g_free(all_certs);
2952 goto done;
2956 *certs = all_certs;
2957 *cert_count = len;
2958 rv = 0;
2959 done:
2960 return (rv);
2963 void
2964 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2966 int i;
2968 for (i = 0; i < cert_count; i++)
2969 gnutls_x509_crt_deinit(certs[i]);
2970 g_free(certs);
2973 void
2974 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2975 size_t cert_count, char *domain)
2977 size_t cert_buf_sz;
2978 char cert_buf[64 * 1024], file[PATH_MAX];
2979 int i;
2980 FILE *f;
2981 GdkColor color;
2983 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2984 return;
2986 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2987 if ((f = fopen(file, "w")) == NULL) {
2988 show_oops(t, "Can't create cert file %s %s",
2989 file, strerror(errno));
2990 return;
2993 for (i = 0; i < cert_count; i++) {
2994 cert_buf_sz = sizeof cert_buf;
2995 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2996 cert_buf, &cert_buf_sz)) {
2997 show_oops(t, "gnutls_x509_crt_export failed");
2998 goto done;
3000 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3001 show_oops(t, "Can't write certs: %s", strerror(errno));
3002 goto done;
3006 /* not the best spot but oh well */
3007 gdk_color_parse("lightblue", &color);
3008 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3009 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3010 gdk_color_parse(XT_COLOR_BLACK, &color);
3011 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3012 done:
3013 fclose(f);
3017 load_compare_cert(struct tab *t, struct karg *args)
3019 const gchar *uri;
3020 char domain[8182], file[PATH_MAX];
3021 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3022 int s = -1, rv = 1, i;
3023 size_t cert_count;
3024 FILE *f = NULL;
3025 size_t cert_buf_sz;
3026 gnutls_session_t gsession;
3027 gnutls_x509_crt_t *certs;
3028 gnutls_certificate_credentials_t xcred;
3030 if (t == NULL)
3031 return (1);
3033 if ((uri = get_uri(t->wv)) == NULL)
3034 return (1);
3036 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3037 return (1);
3039 /* go ssl/tls */
3040 if (start_tls(t, s, &gsession, &xcred)) {
3041 show_oops(t, "Start TLS failed");
3042 goto done;
3045 /* get certs */
3046 if (get_connection_certs(gsession, &certs, &cert_count)) {
3047 show_oops(t, "Can't get connection certificates");
3048 goto done;
3051 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3052 if ((f = fopen(file, "r")) == NULL)
3053 goto freeit;
3055 for (i = 0; i < cert_count; i++) {
3056 cert_buf_sz = sizeof cert_buf;
3057 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3058 cert_buf, &cert_buf_sz)) {
3059 goto freeit;
3061 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3062 rv = -1; /* critical */
3063 goto freeit;
3065 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3066 rv = -1; /* critical */
3067 goto freeit;
3071 rv = 0;
3072 freeit:
3073 if (f)
3074 fclose(f);
3075 free_connection_certs(certs, cert_count);
3076 done:
3077 /* we close the socket first for speed */
3078 if (s != -1)
3079 close(s);
3080 stop_tls(gsession, xcred);
3082 return (rv);
3086 cert_cmd(struct tab *t, struct karg *args)
3088 const gchar *uri;
3089 char domain[8182];
3090 int s = -1;
3091 size_t cert_count;
3092 gnutls_session_t gsession;
3093 gnutls_x509_crt_t *certs;
3094 gnutls_certificate_credentials_t xcred;
3096 if (t == NULL)
3097 return (1);
3099 if ((uri = get_uri(t->wv)) == NULL) {
3100 show_oops(t, "Invalid URI");
3101 return (1);
3104 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3105 show_oops(t, "Invalid certidicate URI: %s", uri);
3106 return (1);
3109 /* go ssl/tls */
3110 if (start_tls(t, s, &gsession, &xcred)) {
3111 show_oops(t, "Start TLS failed");
3112 goto done;
3115 /* get certs */
3116 if (get_connection_certs(gsession, &certs, &cert_count)) {
3117 show_oops(t, "get_connection_certs failed");
3118 goto done;
3121 if (args->i & XT_SHOW)
3122 show_certs(t, certs, cert_count, "Certificate Chain");
3123 else if (args->i & XT_SAVE)
3124 save_certs(t, certs, cert_count, domain);
3126 free_connection_certs(certs, cert_count);
3127 done:
3128 /* we close the socket first for speed */
3129 if (s != -1)
3130 close(s);
3131 stop_tls(gsession, xcred);
3133 return (0);
3137 remove_cookie(int index)
3139 int i, rv = 1;
3140 GSList *cf;
3141 SoupCookie *c;
3143 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3145 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3147 for (i = 1; cf; cf = cf->next, i++) {
3148 if (i != index)
3149 continue;
3150 c = cf->data;
3151 print_cookie("remove cookie", c);
3152 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3153 rv = 0;
3154 break;
3157 soup_cookies_free(cf);
3159 return (rv);
3163 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3165 struct domain *d;
3166 char *tmp, *header, *body, *footer;
3168 /* we set this to indicate we want to manually do navaction */
3169 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3171 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3172 title, title);
3173 footer = g_strdup("</body></html>");
3174 body = g_strdup("");
3176 /* p list */
3177 if (args->i & XT_WL_PERSISTENT) {
3178 tmp = body;
3179 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3180 g_free(tmp);
3181 RB_FOREACH(d, domain_list, wl) {
3182 if (d->handy == 0)
3183 continue;
3184 tmp = body;
3185 body = g_strdup_printf("%s%s<br>", body, d->d);
3186 g_free(tmp);
3190 /* s list */
3191 if (args->i & XT_WL_SESSION) {
3192 tmp = body;
3193 body = g_strdup_printf("%s<h2>Session</h2>", body);
3194 g_free(tmp);
3195 RB_FOREACH(d, domain_list, wl) {
3196 if (d->handy == 1)
3197 continue;
3198 tmp = body;
3199 body = g_strdup_printf("%s%s<br>", body, d->d);
3200 g_free(tmp);
3204 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3205 g_free(header);
3206 g_free(body);
3207 g_free(footer);
3208 if (wl == &js_wl)
3209 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3210 else
3211 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3212 g_free(tmp);
3213 return (0);
3217 wl_save(struct tab *t, struct karg *args, int js)
3219 char file[PATH_MAX];
3220 FILE *f;
3221 char *line = NULL, *lt = NULL;
3222 size_t linelen;
3223 const gchar *uri;
3224 char *dom = NULL, *dom_save = NULL;
3225 struct karg a;
3226 struct domain *d;
3227 GSList *cf;
3228 SoupCookie *ci, *c;
3230 if (t == NULL || args == NULL)
3231 return (1);
3233 if (runtime_settings[0] == '\0')
3234 return (1);
3236 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3237 if ((f = fopen(file, "r+")) == NULL)
3238 return (1);
3240 uri = get_uri(t->wv);
3241 dom = find_domain(uri, 1);
3242 if (uri == NULL || dom == NULL) {
3243 show_oops(t, "Can't add domain to %s white list",
3244 js ? "JavaScript" : "cookie");
3245 goto done;
3248 if (args->i & XT_WL_TOPLEVEL) {
3249 /* save domain */
3250 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3251 show_oops(t, "invalid domain: %s", dom);
3252 goto done;
3254 } else if (args->i & XT_WL_FQDN) {
3255 /* save fqdn */
3256 dom_save = dom;
3257 } else
3258 goto done;
3260 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3262 while (!feof(f)) {
3263 line = fparseln(f, &linelen, NULL, NULL, 0);
3264 if (line == NULL)
3265 continue;
3266 if (!strcmp(line, lt))
3267 goto done;
3268 free(line);
3269 line = NULL;
3272 fprintf(f, "%s\n", lt);
3274 a.i = XT_WL_ENABLE;
3275 a.i |= args->i;
3276 if (js) {
3277 d = wl_find(dom_save, &js_wl);
3278 if (!d) {
3279 settings_add("js_wl", dom_save);
3280 d = wl_find(dom_save, &js_wl);
3282 toggle_js(t, &a);
3283 } else {
3284 d = wl_find(dom_save, &c_wl);
3285 if (!d) {
3286 settings_add("cookie_wl", dom_save);
3287 d = wl_find(dom_save, &c_wl);
3289 toggle_cwl(t, &a);
3291 /* find and add to persistent jar */
3292 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3293 for (;cf; cf = cf->next) {
3294 ci = cf->data;
3295 if (!strcmp(dom_save, ci->domain) ||
3296 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3297 c = soup_cookie_copy(ci);
3298 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3301 soup_cookies_free(cf);
3303 if (d)
3304 d->handy = 1;
3306 done:
3307 if (line)
3308 free(line);
3309 if (dom)
3310 g_free(dom);
3311 if (lt)
3312 g_free(lt);
3313 fclose(f);
3315 return (0);
3319 js_show_wl(struct tab *t, struct karg *args)
3321 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3322 wl_show(t, args, "JavaScript White List", &js_wl);
3324 return (0);
3328 cookie_show_wl(struct tab *t, struct karg *args)
3330 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3331 wl_show(t, args, "Cookie White List", &c_wl);
3333 return (0);
3337 cookie_cmd(struct tab *t, struct karg *args)
3339 if (args->i & XT_SHOW)
3340 wl_show(t, args, "Cookie White List", &c_wl);
3341 else if (args->i & XT_WL_TOGGLE)
3342 toggle_cwl(t, args);
3343 else if (args->i & XT_SAVE)
3344 wl_save(t, args, 0);
3345 else if (args->i & XT_DELETE)
3346 show_oops(t, "'cookie delete' currently unimplemented");
3348 return (0);
3352 js_cmd(struct tab *t, struct karg *args)
3354 if (args->i & XT_SHOW)
3355 wl_show(t, args, "JavaScript White List", &js_wl);
3356 else if (args->i & XT_SAVE)
3357 wl_save(t, args, 1);
3358 else if (args->i & XT_WL_TOGGLE)
3359 toggle_js(t, args);
3360 else if (args->i & XT_DELETE)
3361 show_oops(t, "'js delete' currently unimplemented");
3363 return (0);
3367 add_favorite(struct tab *t, struct karg *args)
3369 char file[PATH_MAX];
3370 FILE *f;
3371 char *line = NULL;
3372 size_t urilen, linelen;
3373 const gchar *uri, *title;
3375 if (t == NULL)
3376 return (1);
3378 /* don't allow adding of xtp pages to favorites */
3379 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3380 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3381 return (1);
3384 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3385 if ((f = fopen(file, "r+")) == NULL) {
3386 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3387 return (1);
3390 title = webkit_web_view_get_title(t->wv);
3391 uri = get_uri(t->wv);
3393 if (title == NULL)
3394 title = uri;
3396 if (title == NULL || uri == NULL) {
3397 show_oops(t, "can't add page to favorites");
3398 goto done;
3401 urilen = strlen(uri);
3403 for (;;) {
3404 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3405 if (feof(f) || ferror(f))
3406 break;
3408 if (linelen == urilen && !strcmp(line, uri))
3409 goto done;
3411 free(line);
3412 line = NULL;
3415 fprintf(f, "\n%s\n%s", title, uri);
3416 done:
3417 if (line)
3418 free(line);
3419 fclose(f);
3421 update_favorite_tabs(NULL);
3423 return (0);
3427 navaction(struct tab *t, struct karg *args)
3429 WebKitWebHistoryItem *item;
3431 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3432 t->tab_id, args->i);
3434 if (t->item) {
3435 if (args->i == XT_NAV_BACK)
3436 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3437 else
3438 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3439 if (item == NULL)
3440 return (XT_CB_PASSTHROUGH);
3441 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3442 t->item = NULL;
3443 return (XT_CB_PASSTHROUGH);
3446 switch (args->i) {
3447 case XT_NAV_BACK:
3448 webkit_web_view_go_back(t->wv);
3449 break;
3450 case XT_NAV_FORWARD:
3451 webkit_web_view_go_forward(t->wv);
3452 break;
3453 case XT_NAV_RELOAD:
3454 webkit_web_view_reload(t->wv);
3455 break;
3456 case XT_NAV_RELOAD_CACHE:
3457 webkit_web_view_reload_bypass_cache(t->wv);
3458 break;
3460 return (XT_CB_PASSTHROUGH);
3464 move(struct tab *t, struct karg *args)
3466 GtkAdjustment *adjust;
3467 double pi, si, pos, ps, upper, lower, max;
3469 switch (args->i) {
3470 case XT_MOVE_DOWN:
3471 case XT_MOVE_UP:
3472 case XT_MOVE_BOTTOM:
3473 case XT_MOVE_TOP:
3474 case XT_MOVE_PAGEDOWN:
3475 case XT_MOVE_PAGEUP:
3476 case XT_MOVE_HALFDOWN:
3477 case XT_MOVE_HALFUP:
3478 adjust = t->adjust_v;
3479 break;
3480 default:
3481 adjust = t->adjust_h;
3482 break;
3485 pos = gtk_adjustment_get_value(adjust);
3486 ps = gtk_adjustment_get_page_size(adjust);
3487 upper = gtk_adjustment_get_upper(adjust);
3488 lower = gtk_adjustment_get_lower(adjust);
3489 si = gtk_adjustment_get_step_increment(adjust);
3490 pi = gtk_adjustment_get_page_increment(adjust);
3491 max = upper - ps;
3493 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3494 "max %f si %f pi %f\n",
3495 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3496 pos, ps, upper, lower, max, si, pi);
3498 switch (args->i) {
3499 case XT_MOVE_DOWN:
3500 case XT_MOVE_RIGHT:
3501 pos += si;
3502 gtk_adjustment_set_value(adjust, MIN(pos, max));
3503 break;
3504 case XT_MOVE_UP:
3505 case XT_MOVE_LEFT:
3506 pos -= si;
3507 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3508 break;
3509 case XT_MOVE_BOTTOM:
3510 case XT_MOVE_FARRIGHT:
3511 gtk_adjustment_set_value(adjust, max);
3512 break;
3513 case XT_MOVE_TOP:
3514 case XT_MOVE_FARLEFT:
3515 gtk_adjustment_set_value(adjust, lower);
3516 break;
3517 case XT_MOVE_PAGEDOWN:
3518 pos += pi;
3519 gtk_adjustment_set_value(adjust, MIN(pos, max));
3520 break;
3521 case XT_MOVE_PAGEUP:
3522 pos -= pi;
3523 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3524 break;
3525 case XT_MOVE_HALFDOWN:
3526 pos += pi / 2;
3527 gtk_adjustment_set_value(adjust, MIN(pos, max));
3528 break;
3529 case XT_MOVE_HALFUP:
3530 pos -= pi / 2;
3531 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3532 break;
3533 default:
3534 return (XT_CB_PASSTHROUGH);
3537 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3539 return (XT_CB_HANDLED);
3542 void
3543 url_set_visibility(void)
3545 struct tab *t;
3547 TAILQ_FOREACH(t, &tabs, entry) {
3548 if (show_url == 0) {
3549 gtk_widget_hide(t->toolbar);
3550 focus_webview(t);
3551 } else
3552 gtk_widget_show(t->toolbar);
3556 void
3557 notebook_tab_set_visibility(GtkNotebook *notebook)
3559 if (show_tabs == 0)
3560 gtk_notebook_set_show_tabs(notebook, FALSE);
3561 else
3562 gtk_notebook_set_show_tabs(notebook, TRUE);
3565 void
3566 statusbar_set_visibility(void)
3568 struct tab *t;
3570 TAILQ_FOREACH(t, &tabs, entry) {
3571 if (show_statusbar == 0) {
3572 gtk_widget_hide(t->statusbar);
3573 focus_webview(t);
3574 } else
3575 gtk_widget_show(t->statusbar);
3579 void
3580 url_set(struct tab *t, int enable_url_entry)
3582 GdkPixbuf *pixbuf;
3583 int progress;
3585 show_url = enable_url_entry;
3587 if (enable_url_entry) {
3588 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3589 GTK_ENTRY_ICON_PRIMARY, NULL);
3590 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3591 } else {
3592 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3593 GTK_ENTRY_ICON_PRIMARY);
3594 progress =
3595 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3596 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3597 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3598 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3599 progress);
3604 fullscreen(struct tab *t, struct karg *args)
3606 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3608 if (t == NULL)
3609 return (XT_CB_PASSTHROUGH);
3611 if (show_url == 0) {
3612 url_set(t, 1);
3613 show_tabs = 1;
3614 } else {
3615 url_set(t, 0);
3616 show_tabs = 0;
3619 url_set_visibility();
3620 notebook_tab_set_visibility(notebook);
3622 return (XT_CB_HANDLED);
3626 statusaction(struct tab *t, struct karg *args)
3628 int rv = XT_CB_HANDLED;
3630 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3632 if (t == NULL)
3633 return (XT_CB_PASSTHROUGH);
3635 switch (args->i) {
3636 case XT_STATUSBAR_SHOW:
3637 if (show_statusbar == 0) {
3638 show_statusbar = 1;
3639 statusbar_set_visibility();
3641 break;
3642 case XT_STATUSBAR_HIDE:
3643 if (show_statusbar == 1) {
3644 show_statusbar = 0;
3645 statusbar_set_visibility();
3647 break;
3649 return (rv);
3653 urlaction(struct tab *t, struct karg *args)
3655 int rv = XT_CB_HANDLED;
3657 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3659 if (t == NULL)
3660 return (XT_CB_PASSTHROUGH);
3662 switch (args->i) {
3663 case XT_URL_SHOW:
3664 if (show_url == 0) {
3665 url_set(t, 1);
3666 url_set_visibility();
3668 break;
3669 case XT_URL_HIDE:
3670 if (show_url == 1) {
3671 url_set(t, 0);
3672 url_set_visibility();
3674 break;
3676 return (rv);
3680 tabaction(struct tab *t, struct karg *args)
3682 int rv = XT_CB_HANDLED;
3683 char *url = args->s;
3684 struct undo *u;
3686 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3688 if (t == NULL)
3689 return (XT_CB_PASSTHROUGH);
3691 switch (args->i) {
3692 case XT_TAB_NEW:
3693 if (strlen(url) > 0)
3694 create_new_tab(url, NULL, 1);
3695 else
3696 create_new_tab(NULL, NULL, 1);
3697 break;
3698 case XT_TAB_DELETE:
3699 delete_tab(t);
3700 break;
3701 case XT_TAB_DELQUIT:
3702 if (gtk_notebook_get_n_pages(notebook) > 1)
3703 delete_tab(t);
3704 else
3705 quit(t, args);
3706 break;
3707 case XT_TAB_OPEN:
3708 if (strlen(url) > 0)
3710 else {
3711 rv = XT_CB_PASSTHROUGH;
3712 goto done;
3714 load_uri(t, url);
3715 break;
3716 case XT_TAB_SHOW:
3717 if (show_tabs == 0) {
3718 show_tabs = 1;
3719 notebook_tab_set_visibility(notebook);
3721 break;
3722 case XT_TAB_HIDE:
3723 if (show_tabs == 1) {
3724 show_tabs = 0;
3725 notebook_tab_set_visibility(notebook);
3727 break;
3728 case XT_TAB_UNDO_CLOSE:
3729 if (undo_count == 0) {
3730 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3731 goto done;
3732 } else {
3733 undo_count--;
3734 u = TAILQ_FIRST(&undos);
3735 create_new_tab(u->uri, u, 1);
3737 TAILQ_REMOVE(&undos, u, entry);
3738 g_free(u->uri);
3739 /* u->history is freed in create_new_tab() */
3740 g_free(u);
3742 break;
3743 default:
3744 rv = XT_CB_PASSTHROUGH;
3745 goto done;
3748 done:
3749 if (args->s) {
3750 g_free(args->s);
3751 args->s = NULL;
3754 return (rv);
3758 resizetab(struct tab *t, struct karg *args)
3760 if (t == NULL || args == NULL) {
3761 show_oops_s("resizetab invalid parameters");
3762 return (XT_CB_PASSTHROUGH);
3765 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3766 t->tab_id, args->i);
3768 adjustfont_webkit(t, args->i);
3770 return (XT_CB_HANDLED);
3774 movetab(struct tab *t, struct karg *args)
3776 struct tab *tt;
3777 int x;
3779 if (t == NULL || args == NULL) {
3780 show_oops_s("movetab invalid parameters");
3781 return (XT_CB_PASSTHROUGH);
3784 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3785 t->tab_id, args->i);
3787 if (args->i == XT_TAB_INVALID)
3788 return (XT_CB_PASSTHROUGH);
3790 if (args->i < XT_TAB_INVALID) {
3791 /* next or previous tab */
3792 if (TAILQ_EMPTY(&tabs))
3793 return (XT_CB_PASSTHROUGH);
3795 switch (args->i) {
3796 case XT_TAB_NEXT:
3797 if (strlen(args->s) == 0) {
3798 /* if at the last page, loop around to the first */
3799 if (gtk_notebook_get_current_page(notebook) ==
3800 gtk_notebook_get_n_pages(notebook) - 1)
3801 gtk_notebook_set_current_page(notebook, 0);
3802 else
3803 gtk_notebook_next_page(notebook);
3804 } else {
3805 x = atoi(args->s) - 1;
3806 if (x < 0)
3807 return (XT_CB_PASSTHROUGH);
3809 if (t->tab_id == x) {
3810 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3811 return (XT_CB_HANDLED);
3813 TAILQ_FOREACH(tt, &tabs, entry) {
3814 if (tt->tab_id == x) {
3815 gtk_notebook_set_current_page(notebook, x);
3816 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3817 break;
3822 break;
3823 case XT_TAB_PREV:
3824 /* if at the first page, loop around to the last */
3825 if (gtk_notebook_current_page(notebook) == 0)
3826 gtk_notebook_set_current_page(notebook,
3827 gtk_notebook_get_n_pages(notebook) - 1);
3828 else
3829 gtk_notebook_prev_page(notebook);
3830 break;
3831 case XT_TAB_FIRST:
3832 gtk_notebook_set_current_page(notebook, 0);
3833 break;
3834 case XT_TAB_LAST:
3835 gtk_notebook_set_current_page(notebook, -1);
3836 break;
3837 default:
3838 return (XT_CB_PASSTHROUGH);
3841 return (XT_CB_HANDLED);
3844 return (XT_CB_HANDLED);
3847 int cmd_prefix = 0;
3850 command(struct tab *t, struct karg *args)
3852 char *s = NULL, *ss = NULL;
3853 GdkColor color;
3854 const gchar *uri;
3856 if (t == NULL || args == NULL) {
3857 show_oops_s("command invalid parameters");
3858 return (XT_CB_PASSTHROUGH);
3861 switch (args->i) {
3862 case '/':
3863 s = "/";
3864 break;
3865 case '?':
3866 s = "?";
3867 break;
3868 case ':':
3869 if (cmd_prefix == 0)
3870 s = ":";
3871 else {
3872 ss = g_strdup_printf(":%d", cmd_prefix);
3873 s = ss;
3874 cmd_prefix = 0;
3876 break;
3877 case XT_CMD_OPEN:
3878 s = ":open ";
3879 break;
3880 case XT_CMD_TABNEW:
3881 s = ":tabnew ";
3882 break;
3883 case XT_CMD_OPEN_CURRENT:
3884 s = ":open ";
3885 /* FALL THROUGH */
3886 case XT_CMD_TABNEW_CURRENT:
3887 if (!s) /* FALL THROUGH? */
3888 s = ":tabnew ";
3889 if ((uri = get_uri(t->wv)) != NULL) {
3890 ss = g_strdup_printf("%s%s", s, uri);
3891 s = ss;
3893 break;
3894 default:
3895 show_oops(t, "command: invalid opcode %d", args->i);
3896 return (XT_CB_PASSTHROUGH);
3899 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3901 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3902 gdk_color_parse(XT_COLOR_WHITE, &color);
3903 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3904 show_cmd(t);
3905 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3906 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3908 if (ss)
3909 g_free(ss);
3911 return (XT_CB_HANDLED);
3915 * Return a new string with a download row (in html)
3916 * appended. Old string is freed.
3918 char *
3919 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3922 WebKitDownloadStatus stat;
3923 char *status_html = NULL, *cmd_html = NULL, *new_html;
3924 gdouble progress;
3925 char cur_sz[FMT_SCALED_STRSIZE];
3926 char tot_sz[FMT_SCALED_STRSIZE];
3927 char *xtp_prefix;
3929 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3931 /* All actions wil take this form:
3932 * xxxt://class/seskey
3934 xtp_prefix = g_strdup_printf("%s%d/%s/",
3935 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3937 stat = webkit_download_get_status(dl->download);
3939 switch (stat) {
3940 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3941 status_html = g_strdup_printf("Finished");
3942 cmd_html = g_strdup_printf(
3943 "<a href='%s%d/%d'>Remove</a>",
3944 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3945 break;
3946 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3947 /* gather size info */
3948 progress = 100 * webkit_download_get_progress(dl->download);
3950 fmt_scaled(
3951 webkit_download_get_current_size(dl->download), cur_sz);
3952 fmt_scaled(
3953 webkit_download_get_total_size(dl->download), tot_sz);
3955 status_html = g_strdup_printf(
3956 "<div style='width: 100%%' align='center'>"
3957 "<div class='progress-outer'>"
3958 "<div class='progress-inner' style='width: %.2f%%'>"
3959 "</div></div></div>"
3960 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3961 progress, cur_sz, tot_sz, progress);
3963 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3964 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3966 break;
3967 /* LLL */
3968 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3969 status_html = g_strdup_printf("Cancelled");
3970 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3971 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3972 break;
3973 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3974 status_html = g_strdup_printf("Error!");
3975 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3976 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3977 break;
3978 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3979 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3980 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3981 status_html = g_strdup_printf("Starting");
3982 break;
3983 default:
3984 show_oops(t, "%s: unknown download status", __func__);
3987 new_html = g_strdup_printf(
3988 "%s\n<tr><td>%s</td><td>%s</td>"
3989 "<td style='text-align:center'>%s</td></tr>\n",
3990 html, basename(webkit_download_get_destination_uri(dl->download)),
3991 status_html, cmd_html);
3992 g_free(html);
3994 if (status_html)
3995 g_free(status_html);
3997 if (cmd_html)
3998 g_free(cmd_html);
4000 g_free(xtp_prefix);
4002 return new_html;
4006 * update all download tabs apart from one. Pass NULL if
4007 * you want to update all.
4009 void
4010 update_download_tabs(struct tab *apart_from)
4012 struct tab *t;
4013 if (!updating_dl_tabs) {
4014 updating_dl_tabs = 1; /* stop infinite recursion */
4015 TAILQ_FOREACH(t, &tabs, entry)
4016 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4017 && (t != apart_from))
4018 xtp_page_dl(t, NULL);
4019 updating_dl_tabs = 0;
4024 * update all cookie tabs apart from one. Pass NULL if
4025 * you want to update all.
4027 void
4028 update_cookie_tabs(struct tab *apart_from)
4030 struct tab *t;
4031 if (!updating_cl_tabs) {
4032 updating_cl_tabs = 1; /* stop infinite recursion */
4033 TAILQ_FOREACH(t, &tabs, entry)
4034 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4035 && (t != apart_from))
4036 xtp_page_cl(t, NULL);
4037 updating_cl_tabs = 0;
4042 * update all history tabs apart from one. Pass NULL if
4043 * you want to update all.
4045 void
4046 update_history_tabs(struct tab *apart_from)
4048 struct tab *t;
4050 if (!updating_hl_tabs) {
4051 updating_hl_tabs = 1; /* stop infinite recursion */
4052 TAILQ_FOREACH(t, &tabs, entry)
4053 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4054 && (t != apart_from))
4055 xtp_page_hl(t, NULL);
4056 updating_hl_tabs = 0;
4060 /* cookie management XTP page */
4062 xtp_page_cl(struct tab *t, struct karg *args)
4064 char *header, *body, *footer, *page, *tmp;
4065 int i = 1; /* all ids start 1 */
4066 GSList *sc, *pc, *pc_start;
4067 SoupCookie *c;
4068 char *type, *table_headers;
4069 char *last_domain = strdup("");
4071 DNPRINTF(XT_D_CMD, "%s", __func__);
4073 if (t == NULL) {
4074 show_oops_s("%s invalid parameters", __func__);
4075 return (1);
4077 /* mark this tab as cookie jar */
4078 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4080 /* Generate a new session key */
4081 if (!updating_cl_tabs)
4082 generate_xtp_session_key(&cl_session_key);
4084 /* header */
4085 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4086 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4087 "</head><body><h1>Cookie Jar</h1>\n");
4089 /* table headers */
4090 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4091 "<th>Type</th>"
4092 "<th>Name</th>"
4093 "<th>Value</th>"
4094 "<th>Path</th>"
4095 "<th>Expires</th>"
4096 "<th>Secure</th>"
4097 "<th>HTTP<br />only</th>"
4098 "<th>Rm</th></tr>\n");
4100 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4101 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4102 pc_start = pc;
4104 body = NULL;
4105 for (; sc; sc = sc->next) {
4106 c = sc->data;
4108 if (strcmp(last_domain, c->domain) != 0) {
4109 /* new domain */
4110 free(last_domain);
4111 last_domain = strdup(c->domain);
4113 if (body != NULL) {
4114 tmp = body;
4115 body = g_strdup_printf("%s</table></div>"
4116 "<h2>%s</h2>%s\n",
4117 body, c->domain, table_headers);
4118 g_free(tmp);
4119 } else {
4120 /* first domain */
4121 body = g_strdup_printf("<h2>%s</h2>%s\n",
4122 c->domain, table_headers);
4126 type = "Session";
4127 for (pc = pc_start; pc; pc = pc->next)
4128 if (soup_cookie_equal(pc->data, c)) {
4129 type = "Session + Persistent";
4130 break;
4133 tmp = body;
4134 body = g_strdup_printf(
4135 "%s\n<tr>"
4136 "<td style='width: text-align: center'>%s</td>"
4137 "<td style='width: 1px'>%s</td>"
4138 "<td style='width=70%%;overflow: visible'>"
4139 " <textarea rows='4'>%s</textarea>"
4140 "</td>"
4141 "<td>%s</td>"
4142 "<td>%s</td>"
4143 "<td style='width: 1px; text-align: center'>%d</td>"
4144 "<td style='width: 1px; text-align: center'>%d</td>"
4145 "<td style='width: 1px; text-align: center'>"
4146 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4147 body,
4148 type,
4149 c->name,
4150 c->value,
4151 c->path,
4152 c->expires ?
4153 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4154 c->secure,
4155 c->http_only,
4157 XT_XTP_STR,
4158 XT_XTP_CL,
4159 cl_session_key,
4160 XT_XTP_CL_REMOVE,
4164 g_free(tmp);
4165 i++;
4168 soup_cookies_free(sc);
4169 soup_cookies_free(pc);
4171 /* small message if there are none */
4172 if (i == 1) {
4173 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4174 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4177 /* footer */
4178 footer = g_strdup_printf("</table></div></body></html>");
4180 page = g_strdup_printf("%s%s%s", header, body, footer);
4182 g_free(header);
4183 g_free(body);
4184 g_free(footer);
4185 g_free(table_headers);
4186 g_free(last_domain);
4188 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4189 update_cookie_tabs(t);
4191 g_free(page);
4193 return (0);
4197 xtp_page_hl(struct tab *t, struct karg *args)
4199 char *header, *body, *footer, *page, *tmp;
4200 struct history *h;
4201 int i = 1; /* all ids start 1 */
4203 DNPRINTF(XT_D_CMD, "%s", __func__);
4205 if (t == NULL) {
4206 show_oops_s("%s invalid parameters", __func__);
4207 return (1);
4210 /* mark this tab as history manager */
4211 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4213 /* Generate a new session key */
4214 if (!updating_hl_tabs)
4215 generate_xtp_session_key(&hl_session_key);
4217 /* header */
4218 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4219 "<title>History</title>\n"
4220 "%s"
4221 "</head>"
4222 "<h1>History</h1>\n",
4223 XT_PAGE_STYLE);
4225 /* body */
4226 body = g_strdup_printf("<div align='center'><table><tr>"
4227 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4229 RB_FOREACH_REVERSE(h, history_list, &hl) {
4230 tmp = body;
4231 body = g_strdup_printf(
4232 "%s\n<tr>"
4233 "<td><a href='%s'>%s</a></td>"
4234 "<td>%s</td>"
4235 "<td style='text-align: center'>"
4236 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4237 body, h->uri, h->uri, h->title,
4238 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4239 XT_XTP_HL_REMOVE, i);
4241 g_free(tmp);
4242 i++;
4245 /* small message if there are none */
4246 if (i == 1) {
4247 tmp = body;
4248 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4249 "colspan='3'>No History</td></tr>\n", body);
4250 g_free(tmp);
4253 /* footer */
4254 footer = g_strdup_printf("</table></div></body></html>");
4256 page = g_strdup_printf("%s%s%s", header, body, footer);
4259 * update all history manager tabs as the xtp session
4260 * key has now changed. No need to update the current tab.
4261 * Already did that above.
4263 update_history_tabs(t);
4265 g_free(header);
4266 g_free(body);
4267 g_free(footer);
4269 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4270 g_free(page);
4272 return (0);
4276 * Generate a web page detailing the status of any downloads
4279 xtp_page_dl(struct tab *t, struct karg *args)
4281 struct download *dl;
4282 char *header, *body, *footer, *page, *tmp;
4283 char *ref;
4284 int n_dl = 1;
4286 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4288 if (t == NULL) {
4289 show_oops_s("%s invalid parameters", __func__);
4290 return (1);
4292 /* mark as a download manager tab */
4293 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4296 * Generate a new session key for next page instance.
4297 * This only happens for the top level call to xtp_page_dl()
4298 * in which case updating_dl_tabs is 0.
4300 if (!updating_dl_tabs)
4301 generate_xtp_session_key(&dl_session_key);
4303 /* header - with refresh so as to update */
4304 if (refresh_interval >= 1)
4305 ref = g_strdup_printf(
4306 "<meta http-equiv='refresh' content='%u"
4307 ";url=%s%d/%s/%d' />\n",
4308 refresh_interval,
4309 XT_XTP_STR,
4310 XT_XTP_DL,
4311 dl_session_key,
4312 XT_XTP_DL_LIST);
4313 else
4314 ref = g_strdup("");
4317 header = g_strdup_printf(
4318 "%s\n<head>"
4319 "<title>Downloads</title>\n%s%s</head>\n",
4320 XT_DOCTYPE XT_HTML_TAG,
4321 ref,
4322 XT_PAGE_STYLE);
4324 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4325 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4326 "</p><table><tr><th style='width: 60%%'>"
4327 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4328 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4330 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4331 body = xtp_page_dl_row(t, body, dl);
4332 n_dl++;
4335 /* message if no downloads in list */
4336 if (n_dl == 1) {
4337 tmp = body;
4338 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4339 " style='text-align: center'>"
4340 "No downloads</td></tr>\n", body);
4341 g_free(tmp);
4344 /* footer */
4345 footer = g_strdup_printf("</table></div></body></html>");
4347 page = g_strdup_printf("%s%s%s", header, body, footer);
4351 * update all download manager tabs as the xtp session
4352 * key has now changed. No need to update the current tab.
4353 * Already did that above.
4355 update_download_tabs(t);
4357 g_free(ref);
4358 g_free(header);
4359 g_free(body);
4360 g_free(footer);
4362 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4363 g_free(page);
4365 return (0);
4369 search(struct tab *t, struct karg *args)
4371 gboolean d;
4373 if (t == NULL || args == NULL) {
4374 show_oops_s("search invalid parameters");
4375 return (1);
4377 if (t->search_text == NULL) {
4378 if (global_search == NULL)
4379 return (XT_CB_PASSTHROUGH);
4380 else {
4381 t->search_text = g_strdup(global_search);
4382 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4383 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4387 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4388 t->tab_id, args->i, t->search_forward, t->search_text);
4390 switch (args->i) {
4391 case XT_SEARCH_NEXT:
4392 d = t->search_forward;
4393 break;
4394 case XT_SEARCH_PREV:
4395 d = !t->search_forward;
4396 break;
4397 default:
4398 return (XT_CB_PASSTHROUGH);
4401 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4403 return (XT_CB_HANDLED);
4406 struct settings_args {
4407 char **body;
4408 int i;
4411 void
4412 print_setting(struct settings *s, char *val, void *cb_args)
4414 char *tmp, *color;
4415 struct settings_args *sa = cb_args;
4417 if (sa == NULL)
4418 return;
4420 if (s->flags & XT_SF_RUNTIME)
4421 color = "#22cc22";
4422 else
4423 color = "#cccccc";
4425 tmp = *sa->body;
4426 *sa->body = g_strdup_printf(
4427 "%s\n<tr>"
4428 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4429 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4430 *sa->body,
4431 color,
4432 s->name,
4433 color,
4436 g_free(tmp);
4437 sa->i++;
4441 set(struct tab *t, struct karg *args)
4443 char *header, *body, *footer, *page, *tmp;
4444 int i = 1;
4445 struct settings_args sa;
4447 bzero(&sa, sizeof sa);
4448 sa.body = &body;
4450 /* header */
4451 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4452 "\n<head><title>Settings</title>\n"
4453 "</head><body><h1>Settings</h1>\n");
4455 /* body */
4456 body = g_strdup_printf("<div align='center'><table><tr>"
4457 "<th align='left'>Setting</th>"
4458 "<th align='left'>Value</th></tr>\n");
4460 settings_walk(print_setting, &sa);
4461 i = sa.i;
4463 /* small message if there are none */
4464 if (i == 1) {
4465 tmp = body;
4466 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4467 "colspan='2'>No settings</td></tr>\n", body);
4468 g_free(tmp);
4471 /* footer */
4472 footer = g_strdup_printf("</table></div></body></html>");
4474 page = g_strdup_printf("%s%s%s", header, body, footer);
4476 g_free(header);
4477 g_free(body);
4478 g_free(footer);
4480 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4482 return (XT_CB_PASSTHROUGH);
4486 session_save(struct tab *t, char *filename)
4488 struct karg a;
4489 int rv = 1;
4491 if (strlen(filename) == 0)
4492 goto done;
4494 if (filename[0] == '.' || filename[0] == '/')
4495 goto done;
4497 a.s = filename;
4498 if (save_tabs(t, &a))
4499 goto done;
4500 strlcpy(named_session, filename, sizeof named_session);
4502 rv = 0;
4503 done:
4504 return (rv);
4508 session_open(struct tab *t, char *filename)
4510 struct karg a;
4511 int rv = 1;
4513 if (strlen(filename) == 0)
4514 goto done;
4516 if (filename[0] == '.' || filename[0] == '/')
4517 goto done;
4519 a.s = filename;
4520 a.i = XT_SES_CLOSETABS;
4521 if (open_tabs(t, &a))
4522 goto done;
4524 strlcpy(named_session, filename, sizeof named_session);
4526 rv = 0;
4527 done:
4528 return (rv);
4532 session_delete(struct tab *t, char *filename)
4534 char file[PATH_MAX];
4535 int rv = 1;
4537 if (strlen(filename) == 0)
4538 goto done;
4540 if (filename[0] == '.' || filename[0] == '/')
4541 goto done;
4543 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4544 if (unlink(file))
4545 goto done;
4547 if (!strcmp(filename, named_session))
4548 strlcpy(named_session, XT_SAVED_TABS_FILE,
4549 sizeof named_session);
4551 rv = 0;
4552 done:
4553 return (rv);
4557 session_cmd(struct tab *t, struct karg *args)
4559 char *filename = args->s;
4561 if (t == NULL)
4562 return (1);
4564 if (args->i & XT_SHOW)
4565 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4566 XT_SAVED_TABS_FILE : named_session);
4567 else if (args->i & XT_SAVE) {
4568 if (session_save(t, filename)) {
4569 show_oops(t, "Can't save session: %s",
4570 filename ? filename : "INVALID");
4571 goto done;
4573 } else if (args->i & XT_OPEN) {
4574 if (session_open(t, filename)) {
4575 show_oops(t, "Can't open session: %s",
4576 filename ? filename : "INVALID");
4577 goto done;
4579 } else if (args->i & XT_DELETE) {
4580 if (session_delete(t, filename)) {
4581 show_oops(t, "Can't delete session: %s",
4582 filename ? filename : "INVALID");
4583 goto done;
4586 done:
4587 return (XT_CB_PASSTHROUGH);
4591 * Make a hardcopy of the page
4594 print_page(struct tab *t, struct karg *args)
4596 WebKitWebFrame *frame;
4597 GtkPageSetup *ps;
4598 GtkPrintOperation *op;
4599 GtkPrintOperationAction action;
4600 GtkPrintOperationResult print_res;
4601 GError *g_err = NULL;
4602 int marg_l, marg_r, marg_t, marg_b;
4604 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4606 ps = gtk_page_setup_new();
4607 op = gtk_print_operation_new();
4608 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4609 frame = webkit_web_view_get_main_frame(t->wv);
4611 /* the default margins are too small, so we will bump them */
4612 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4613 XT_PRINT_EXTRA_MARGIN;
4614 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4615 XT_PRINT_EXTRA_MARGIN;
4616 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4617 XT_PRINT_EXTRA_MARGIN;
4618 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4619 XT_PRINT_EXTRA_MARGIN;
4621 /* set margins */
4622 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4623 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4624 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4625 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4627 gtk_print_operation_set_default_page_setup(op, ps);
4629 /* this appears to free 'op' and 'ps' */
4630 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4632 /* check it worked */
4633 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4634 show_oops_s("can't print: %s", g_err->message);
4635 g_error_free (g_err);
4636 return (1);
4639 return (0);
4643 go_home(struct tab *t, struct karg *args)
4645 load_uri(t, home);
4646 return (0);
4650 restart(struct tab *t, struct karg *args)
4652 struct karg a;
4654 a.s = XT_RESTART_TABS_FILE;
4655 save_tabs(t, &a);
4656 execvp(start_argv[0], start_argv);
4657 /* NOTREACHED */
4659 return (0);
4662 #define CTRL GDK_CONTROL_MASK
4663 #define MOD1 GDK_MOD1_MASK
4664 #define SHFT GDK_SHIFT_MASK
4666 /* inherent to GTK not all keys will be caught at all times */
4667 /* XXX sort key bindings */
4668 struct key_binding {
4669 char *cmd;
4670 guint mask;
4671 guint use_in_entry;
4672 guint key;
4673 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4674 } keys[] = {
4675 { "cookiejar", MOD1, 0, GDK_j },
4676 { "downloadmgr", MOD1, 0, GDK_d },
4677 { "history", MOD1, 0, GDK_h },
4678 { "print", CTRL, 0, GDK_p },
4679 { "search", 0, 0, GDK_slash },
4680 { "searchb", 0, 0, GDK_question },
4681 { "command", 0, 0, GDK_colon },
4682 { "qa", CTRL, 0, GDK_q },
4683 { "restart", MOD1, 0, GDK_q },
4684 { "js toggle", CTRL, 0, GDK_j },
4685 { "cookie toggle", MOD1, 0, GDK_c },
4686 { "togglesrc", CTRL, 0, GDK_s },
4687 { "yankuri", 0, 0, GDK_y },
4688 { "pasteuricur", 0, 0, GDK_p },
4689 { "pasteurinew", 0, 0, GDK_P },
4691 /* search */
4692 { "searchnext", 0, 0, GDK_n },
4693 { "searchprevious", 0, 0, GDK_N },
4695 /* focus */
4696 { "focusaddress", 0, 0, GDK_F6 },
4697 { "focussearch", 0, 0, GDK_F7 },
4699 /* hinting */
4700 { "hinting", 0, 0, GDK_f },
4702 /* custom stylesheet */
4703 { "userstyle", 0, 0, GDK_i },
4705 /* navigation */
4706 { "goback", 0, 0, GDK_BackSpace },
4707 { "goback", MOD1, 0, GDK_Left },
4708 { "goforward", SHFT, 0, GDK_BackSpace },
4709 { "goforward", MOD1, 0, GDK_Right },
4710 { "reload", 0, 0, GDK_F5 },
4711 { "reload", CTRL, 0, GDK_r },
4712 { "reloadforce", CTRL, 0, GDK_R },
4713 { "reload", CTRL, 0, GDK_l },
4714 { "favorites", MOD1, 1, GDK_f },
4716 /* vertical movement */
4717 { "scrolldown", 0, 0, GDK_j },
4718 { "scrolldown", 0, 0, GDK_Down },
4719 { "scrollup", 0, 0, GDK_Up },
4720 { "scrollup", 0, 0, GDK_k },
4721 { "scrollbottom", 0, 0, GDK_G },
4722 { "scrollbottom", 0, 0, GDK_End },
4723 { "scrolltop", 0, 0, GDK_Home },
4724 { "scrolltop", 0, 0, GDK_g },
4725 { "scrollpagedown", 0, 0, GDK_space },
4726 { "scrollpagedown", CTRL, 0, GDK_f },
4727 { "scrollhalfdown", CTRL, 0, GDK_d },
4728 { "scrollpagedown", 0, 0, GDK_Page_Down },
4729 { "scrollpageup", 0, 0, GDK_Page_Up },
4730 { "scrollpageup", CTRL, 0, GDK_b },
4731 { "scrollhalfup", CTRL, 0, GDK_u },
4732 /* horizontal movement */
4733 { "scrollright", 0, 0, GDK_l },
4734 { "scrollright", 0, 0, GDK_Right },
4735 { "scrollleft", 0, 0, GDK_Left },
4736 { "scrollleft", 0, 0, GDK_h },
4737 { "scrollfarright", 0, 0, GDK_dollar },
4738 { "scrollfarleft", 0, 0, GDK_0 },
4740 /* tabs */
4741 { "tabnew", CTRL, 0, GDK_t },
4742 { "tabclose", CTRL, 1, GDK_w },
4743 { "tabundoclose", 0, 0, GDK_U },
4744 { "tabnext 1", CTRL, 0, GDK_1 },
4745 { "tabnext 2", CTRL, 0, GDK_2 },
4746 { "tabnext 3", CTRL, 0, GDK_3 },
4747 { "tabnext 4", CTRL, 0, GDK_4 },
4748 { "tabnext 5", CTRL, 0, GDK_5 },
4749 { "tabnext 6", CTRL, 0, GDK_6 },
4750 { "tabnext 7", CTRL, 0, GDK_7 },
4751 { "tabnext 8", CTRL, 0, GDK_8 },
4752 { "tabnext 9", CTRL, 0, GDK_9 },
4753 { "tabnext 10", CTRL, 0, GDK_0 },
4754 { "tabfirst", CTRL, 0, GDK_less },
4755 { "tablast", CTRL, 0, GDK_greater },
4756 { "tabprevious", CTRL, 0, GDK_Left },
4757 { "tabnext", CTRL, 0, GDK_Right },
4758 { "focusout", CTRL, 0, GDK_minus },
4759 { "focusin", CTRL, 0, GDK_plus },
4760 { "focusin", CTRL, 0, GDK_equal },
4762 /* command aliases (handy when -S flag is used) */
4763 { "promptopen", 0, 0, GDK_F9 },
4764 { "promptopencurrent", 0, 0, GDK_F10 },
4765 { "prompttabnew", 0, 0, GDK_F11 },
4766 { "prompttabnewcurrent",0, 0, GDK_F12 },
4768 TAILQ_HEAD(keybinding_list, key_binding);
4770 void
4771 walk_kb(struct settings *s,
4772 void (*cb)(struct settings *, char *, void *), void *cb_args)
4774 struct key_binding *k;
4775 char str[1024];
4777 if (s == NULL || cb == NULL) {
4778 show_oops_s("walk_kb invalid parameters");
4779 return;
4782 TAILQ_FOREACH(k, &kbl, entry) {
4783 if (k->cmd == NULL)
4784 continue;
4785 str[0] = '\0';
4787 /* sanity */
4788 if (gdk_keyval_name(k->key) == NULL)
4789 continue;
4791 strlcat(str, k->cmd, sizeof str);
4792 strlcat(str, ",", sizeof str);
4794 if (k->mask & GDK_SHIFT_MASK)
4795 strlcat(str, "S-", sizeof str);
4796 if (k->mask & GDK_CONTROL_MASK)
4797 strlcat(str, "C-", sizeof str);
4798 if (k->mask & GDK_MOD1_MASK)
4799 strlcat(str, "M1-", sizeof str);
4800 if (k->mask & GDK_MOD2_MASK)
4801 strlcat(str, "M2-", sizeof str);
4802 if (k->mask & GDK_MOD3_MASK)
4803 strlcat(str, "M3-", sizeof str);
4804 if (k->mask & GDK_MOD4_MASK)
4805 strlcat(str, "M4-", sizeof str);
4806 if (k->mask & GDK_MOD5_MASK)
4807 strlcat(str, "M5-", sizeof str);
4809 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4810 cb(s, str, cb_args);
4814 void
4815 init_keybindings(void)
4817 int i;
4818 struct key_binding *k;
4820 for (i = 0; i < LENGTH(keys); i++) {
4821 k = g_malloc0(sizeof *k);
4822 k->cmd = keys[i].cmd;
4823 k->mask = keys[i].mask;
4824 k->use_in_entry = keys[i].use_in_entry;
4825 k->key = keys[i].key;
4826 TAILQ_INSERT_HEAD(&kbl, k, entry);
4828 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4829 k->cmd ? k->cmd : "unnamed key");
4833 void
4834 keybinding_clearall(void)
4836 struct key_binding *k, *next;
4838 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4839 next = TAILQ_NEXT(k, entry);
4840 if (k->cmd == NULL)
4841 continue;
4843 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4844 k->cmd ? k->cmd : "unnamed key");
4845 TAILQ_REMOVE(&kbl, k, entry);
4846 g_free(k);
4851 keybinding_add(char *cmd, char *key, int use_in_entry)
4853 struct key_binding *k;
4854 guint keyval, mask = 0;
4855 int i;
4857 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4859 /* Keys which are to be used in entry have been prefixed with an
4860 * exclamation mark. */
4861 if (use_in_entry)
4862 key++;
4864 /* find modifier keys */
4865 if (strstr(key, "S-"))
4866 mask |= GDK_SHIFT_MASK;
4867 if (strstr(key, "C-"))
4868 mask |= GDK_CONTROL_MASK;
4869 if (strstr(key, "M1-"))
4870 mask |= GDK_MOD1_MASK;
4871 if (strstr(key, "M2-"))
4872 mask |= GDK_MOD2_MASK;
4873 if (strstr(key, "M3-"))
4874 mask |= GDK_MOD3_MASK;
4875 if (strstr(key, "M4-"))
4876 mask |= GDK_MOD4_MASK;
4877 if (strstr(key, "M5-"))
4878 mask |= GDK_MOD5_MASK;
4880 /* find keyname */
4881 for (i = strlen(key) - 1; i > 0; i--)
4882 if (key[i] == '-')
4883 key = &key[i + 1];
4885 /* validate keyname */
4886 keyval = gdk_keyval_from_name(key);
4887 if (keyval == GDK_VoidSymbol) {
4888 warnx("invalid keybinding name %s", key);
4889 return (1);
4891 /* must run this test too, gtk+ doesn't handle 10 for example */
4892 if (gdk_keyval_name(keyval) == NULL) {
4893 warnx("invalid keybinding name %s", key);
4894 return (1);
4897 /* Remove eventual dupes. */
4898 TAILQ_FOREACH(k, &kbl, entry)
4899 if (k->key == keyval && k->mask == mask) {
4900 TAILQ_REMOVE(&kbl, k, entry);
4901 g_free(k);
4902 break;
4905 /* add keyname */
4906 k = g_malloc0(sizeof *k);
4907 k->cmd = g_strdup(cmd);
4908 k->mask = mask;
4909 k->use_in_entry = use_in_entry;
4910 k->key = keyval;
4912 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4913 k->cmd,
4914 k->mask,
4915 k->use_in_entry,
4916 k->key);
4917 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4918 k->cmd, gdk_keyval_name(keyval));
4920 TAILQ_INSERT_HEAD(&kbl, k, entry);
4922 return (0);
4926 add_kb(struct settings *s, char *entry)
4928 char *kb, *key;
4930 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4932 /* clearall is special */
4933 if (!strcmp(entry, "clearall")) {
4934 keybinding_clearall();
4935 return (0);
4938 kb = strstr(entry, ",");
4939 if (kb == NULL)
4940 return (1);
4941 *kb = '\0';
4942 key = kb + 1;
4944 return (keybinding_add(entry, key, key[0] == '!'));
4947 struct cmd {
4948 char *cmd;
4949 int level;
4950 int (*func)(struct tab *, struct karg *);
4951 int arg;
4952 int type;
4953 } cmds[] = {
4954 { "command", 0, command, ':', 0 },
4955 { "search", 0, command, '/', 0 },
4956 { "searchb", 0, command, '?', 0 },
4957 { "togglesrc", 0, toggle_src, 0, 0 },
4959 /* yanking and pasting */
4960 { "yankuri", 0, yank_uri, 0, 0 },
4961 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4962 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
4963 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
4965 /* search */
4966 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
4967 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
4969 /* focus */
4970 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
4971 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
4973 /* hinting */
4974 { "hinting", 0, hint, 0, 0 },
4976 /* custom stylesheet */
4977 { "userstyle", 0, userstyle, 0, 0 },
4979 /* navigation */
4980 { "goback", 0, navaction, XT_NAV_BACK, 0 },
4981 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
4982 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
4983 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
4985 /* vertical movement */
4986 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
4987 { "scrollup", 0, move, XT_MOVE_UP, 0 },
4988 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
4989 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
4990 { "1", 0, move, XT_MOVE_TOP, 0 },
4991 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
4992 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
4993 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
4994 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
4995 /* horizontal movement */
4996 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
4997 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
4998 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
4999 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5002 { "favorites", 0, xtp_page_fl, 0, 0 },
5003 { "fav", 0, xtp_page_fl, 0, 0 },
5004 { "favadd", 0, add_favorite, 0, 0 },
5006 { "qall", 0, quit, 0, 0 },
5007 { "quitall", 0, quit, 0, 0 },
5008 { "w", 0, save_tabs, 0, 0 },
5009 { "wq", 0, save_tabs_and_quit, 0, 0 },
5010 { "help", 0, help, 0, 0 },
5011 { "about", 0, about, 0, 0 },
5012 { "stats", 0, stats, 0, 0 },
5013 { "version", 0, about, 0, 0 },
5015 /* js command */
5016 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5017 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5018 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5019 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5020 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5021 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5022 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5023 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5024 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5025 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5026 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5028 /* cookie command */
5029 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5030 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5031 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5032 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5033 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5034 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5035 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5036 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5037 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5038 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5039 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5041 /* cookie jar */
5042 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5044 /* cert command */
5045 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5046 { "save", 1, cert_cmd, XT_SAVE, 0 },
5047 { "show", 1, cert_cmd, XT_SHOW, 0 },
5049 { "ca", 0, ca_cmd, 0, 0 },
5050 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5051 { "dl", 0, xtp_page_dl, 0, 0 },
5052 { "h", 0, xtp_page_hl, 0, 0 },
5053 { "history", 0, xtp_page_hl, 0, 0 },
5054 { "home", 0, go_home, 0, 0 },
5055 { "restart", 0, restart, 0, 0 },
5056 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5057 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5058 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5059 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5061 { "print", 0, print_page, 0, 0 },
5063 /* tabs */
5064 { "focusin", 0, resizetab, 1, 0 },
5065 { "focusout", 0, resizetab, -1, 0 },
5066 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5067 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5068 { "open", 0, tabaction, XT_TAB_OPEN, XT_USERARG | XT_URLARG },
5069 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_USERARG },
5070 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_USERARG | XT_URLARG },
5071 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5072 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5073 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5074 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_USERARG | XT_URLARG },
5075 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_USERARG },
5076 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_USERARG },
5077 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5078 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5079 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5081 /* command aliases (handy when -S flag is used) */
5082 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5083 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5084 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5085 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5087 /* settings */
5088 { "set", 0, set, 0, 0 },
5089 { "fullscreen", 0, fullscreen, 0, 0 },
5090 { "f", 0, fullscreen, 0, 0 },
5092 /* sessions */
5093 { "session", 0, session_cmd, XT_SHOW, 0 },
5094 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5095 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5096 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5097 { "show", 1, session_cmd, XT_SHOW, 0 },
5100 struct {
5101 int index;
5102 int len;
5103 gchar *list[256];
5104 } cmd_status = {-1, 0};
5106 gboolean
5107 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5109 struct karg a;
5111 hide_oops(t);
5113 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5114 /* go backward */
5115 a.i = XT_NAV_BACK;
5116 navaction(t, &a);
5118 return (TRUE);
5119 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5120 /* go forward */
5121 a.i = XT_NAV_FORWARD;
5122 navaction(t, &a);
5124 return (TRUE);
5127 return (FALSE);
5130 gboolean
5131 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5133 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5135 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5136 delete_tab(t);
5138 return (FALSE);
5142 * cancel, remove, etc. downloads
5144 void
5145 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5147 struct download find, *d = NULL;
5149 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5151 /* some commands require a valid download id */
5152 if (cmd != XT_XTP_DL_LIST) {
5153 /* lookup download in question */
5154 find.id = id;
5155 d = RB_FIND(download_list, &downloads, &find);
5157 if (d == NULL) {
5158 show_oops(t, "%s: no such download", __func__);
5159 return;
5163 /* decide what to do */
5164 switch (cmd) {
5165 case XT_XTP_DL_CANCEL:
5166 webkit_download_cancel(d->download);
5167 break;
5168 case XT_XTP_DL_REMOVE:
5169 webkit_download_cancel(d->download); /* just incase */
5170 g_object_unref(d->download);
5171 RB_REMOVE(download_list, &downloads, d);
5172 break;
5173 case XT_XTP_DL_LIST:
5174 /* Nothing */
5175 break;
5176 default:
5177 show_oops(t, "%s: unknown command", __func__);
5178 break;
5180 xtp_page_dl(t, NULL);
5184 * Actions on history, only does one thing for now, but
5185 * we provide the function for future actions
5187 void
5188 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5190 struct history *h, *next;
5191 int i = 1;
5193 switch (cmd) {
5194 case XT_XTP_HL_REMOVE:
5195 /* walk backwards, as listed in reverse */
5196 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5197 next = RB_PREV(history_list, &hl, h);
5198 if (id == i) {
5199 RB_REMOVE(history_list, &hl, h);
5200 g_free((gpointer) h->title);
5201 g_free((gpointer) h->uri);
5202 g_free(h);
5203 break;
5205 i++;
5207 break;
5208 case XT_XTP_HL_LIST:
5209 /* Nothing - just xtp_page_hl() below */
5210 break;
5211 default:
5212 show_oops(t, "%s: unknown command", __func__);
5213 break;
5216 xtp_page_hl(t, NULL);
5219 /* remove a favorite */
5220 void
5221 remove_favorite(struct tab *t, int index)
5223 char file[PATH_MAX], *title, *uri = NULL;
5224 char *new_favs, *tmp;
5225 FILE *f;
5226 int i;
5227 size_t len, lineno;
5229 /* open favorites */
5230 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5232 if ((f = fopen(file, "r")) == NULL) {
5233 show_oops(t, "%s: can't open favorites: %s",
5234 __func__, strerror(errno));
5235 return;
5238 /* build a string which will become the new favroites file */
5239 new_favs = g_strdup("");
5241 for (i = 1;;) {
5242 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5243 if (feof(f) || ferror(f))
5244 break;
5245 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5246 if (len == 0) {
5247 free(title);
5248 title = NULL;
5249 continue;
5252 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5253 if (feof(f) || ferror(f)) {
5254 show_oops(t, "%s: can't parse favorites %s",
5255 __func__, strerror(errno));
5256 goto clean;
5260 /* as long as this isn't the one we are deleting add to file */
5261 if (i != index) {
5262 tmp = new_favs;
5263 new_favs = g_strdup_printf("%s%s\n%s\n",
5264 new_favs, title, uri);
5265 g_free(tmp);
5268 free(uri);
5269 uri = NULL;
5270 free(title);
5271 title = NULL;
5272 i++;
5274 fclose(f);
5276 /* write back new favorites file */
5277 if ((f = fopen(file, "w")) == NULL) {
5278 show_oops(t, "%s: can't open favorites: %s",
5279 __func__, strerror(errno));
5280 goto clean;
5283 fwrite(new_favs, strlen(new_favs), 1, f);
5284 fclose(f);
5286 clean:
5287 if (uri)
5288 free(uri);
5289 if (title)
5290 free(title);
5292 g_free(new_favs);
5295 void
5296 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5298 switch (cmd) {
5299 case XT_XTP_FL_LIST:
5300 /* nothing, just the below call to xtp_page_fl() */
5301 break;
5302 case XT_XTP_FL_REMOVE:
5303 remove_favorite(t, arg);
5304 break;
5305 default:
5306 show_oops(t, "%s: invalid favorites command", __func__);
5307 break;
5310 xtp_page_fl(t, NULL);
5313 void
5314 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5316 switch (cmd) {
5317 case XT_XTP_CL_LIST:
5318 /* nothing, just xtp_page_cl() */
5319 break;
5320 case XT_XTP_CL_REMOVE:
5321 remove_cookie(arg);
5322 break;
5323 default:
5324 show_oops(t, "%s: unknown cookie xtp command", __func__);
5325 break;
5328 xtp_page_cl(t, NULL);
5331 /* link an XTP class to it's session key and handler function */
5332 struct xtp_despatch {
5333 uint8_t xtp_class;
5334 char **session_key;
5335 void (*handle_func)(struct tab *, uint8_t, int);
5338 struct xtp_despatch xtp_despatches[] = {
5339 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5340 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5341 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5342 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5343 { XT_XTP_INVALID, NULL, NULL }
5347 * is the url xtp protocol? (xxxt://)
5348 * if so, parse and despatch correct bahvior
5351 parse_xtp_url(struct tab *t, const char *url)
5353 char *dup = NULL, *p, *last;
5354 uint8_t n_tokens = 0;
5355 char *tokens[4] = {NULL, NULL, NULL, ""};
5356 struct xtp_despatch *dsp, *dsp_match = NULL;
5357 uint8_t req_class;
5358 int ret = FALSE;
5361 * tokens array meaning:
5362 * tokens[0] = class
5363 * tokens[1] = session key
5364 * tokens[2] = action
5365 * tokens[3] = optional argument
5368 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5370 /*xtp tab meaning is normal unless proven special */
5371 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5373 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5374 goto clean;
5376 dup = g_strdup(url + strlen(XT_XTP_STR));
5378 /* split out the url */
5379 for ((p = strtok_r(dup, "/", &last)); p;
5380 (p = strtok_r(NULL, "/", &last))) {
5381 if (n_tokens < 4)
5382 tokens[n_tokens++] = p;
5385 /* should be atleast three fields 'class/seskey/command/arg' */
5386 if (n_tokens < 3)
5387 goto clean;
5389 dsp = xtp_despatches;
5390 req_class = atoi(tokens[0]);
5391 while (dsp->xtp_class) {
5392 if (dsp->xtp_class == req_class) {
5393 dsp_match = dsp;
5394 break;
5396 dsp++;
5399 /* did we find one atall? */
5400 if (dsp_match == NULL) {
5401 show_oops(t, "%s: no matching xtp despatch found", __func__);
5402 goto clean;
5405 /* check session key and call despatch function */
5406 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5407 ret = TRUE; /* all is well, this was a valid xtp request */
5408 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5411 clean:
5412 if (dup)
5413 g_free(dup);
5415 return (ret);
5420 void
5421 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5423 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5425 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5427 if (t == NULL) {
5428 show_oops_s("activate_uri_entry_cb invalid parameters");
5429 return;
5432 if (uri == NULL) {
5433 show_oops(t, "activate_uri_entry_cb no uri");
5434 return;
5437 uri += strspn(uri, "\t ");
5439 /* if xxxt:// treat specially */
5440 if (parse_xtp_url(t, uri))
5441 return;
5443 /* otherwise continue to load page normally */
5444 load_uri(t, (gchar *)uri);
5445 focus_webview(t);
5448 void
5449 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5451 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5452 char *newuri = NULL;
5453 gchar *enc_search;
5455 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5457 if (t == NULL) {
5458 show_oops_s("activate_search_entry_cb invalid parameters");
5459 return;
5462 if (search_string == NULL) {
5463 show_oops(t, "no search_string");
5464 return;
5467 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5468 newuri = g_strdup_printf(search_string, enc_search);
5469 g_free(enc_search);
5471 webkit_web_view_load_uri(t->wv, newuri);
5472 focus_webview(t);
5474 if (newuri)
5475 g_free(newuri);
5478 void
5479 check_and_set_js(const gchar *uri, struct tab *t)
5481 struct domain *d = NULL;
5482 int es = 0;
5484 if (uri == NULL || t == NULL)
5485 return;
5487 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5488 es = 0;
5489 else
5490 es = 1;
5492 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5493 es ? "enable" : "disable", uri);
5495 g_object_set(G_OBJECT(t->settings),
5496 "enable-scripts", es, (char *)NULL);
5497 g_object_set(G_OBJECT(t->settings),
5498 "javascript-can-open-windows-automatically", es, (char *)NULL);
5499 webkit_web_view_set_settings(t->wv, t->settings);
5501 button_set_stockid(t->js_toggle,
5502 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5505 void
5506 show_ca_status(struct tab *t, const char *uri)
5508 WebKitWebFrame *frame;
5509 WebKitWebDataSource *source;
5510 WebKitNetworkRequest *request;
5511 SoupMessage *message;
5512 GdkColor color;
5513 gchar *col_str = XT_COLOR_WHITE;
5514 int r;
5516 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5517 ssl_strict_certs, ssl_ca_file, uri);
5519 if (uri == NULL)
5520 goto done;
5521 if (ssl_ca_file == NULL) {
5522 if (g_str_has_prefix(uri, "http://"))
5523 goto done;
5524 if (g_str_has_prefix(uri, "https://")) {
5525 col_str = XT_COLOR_RED;
5526 goto done;
5528 return;
5530 if (g_str_has_prefix(uri, "http://") ||
5531 !g_str_has_prefix(uri, "https://"))
5532 goto done;
5534 frame = webkit_web_view_get_main_frame(t->wv);
5535 source = webkit_web_frame_get_data_source(frame);
5536 request = webkit_web_data_source_get_request(source);
5537 message = webkit_network_request_get_message(request);
5539 if (message && (soup_message_get_flags(message) &
5540 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5541 col_str = XT_COLOR_GREEN;
5542 goto done;
5543 } else {
5544 r = load_compare_cert(t, NULL);
5545 if (r == 0)
5546 col_str = XT_COLOR_BLUE;
5547 else if (r == 1)
5548 col_str = XT_COLOR_YELLOW;
5549 else
5550 col_str = XT_COLOR_RED;
5551 goto done;
5553 done:
5554 if (col_str) {
5555 gdk_color_parse(col_str, &color);
5556 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5558 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5559 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5560 &color);
5561 gdk_color_parse(XT_COLOR_BLACK, &color);
5562 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5563 &color);
5564 } else {
5565 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5566 &color);
5567 gdk_color_parse(XT_COLOR_BLACK, &color);
5568 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5569 &color);
5574 void
5575 free_favicon(struct tab *t)
5577 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5578 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5580 if (t->icon_request)
5581 g_object_unref(t->icon_request);
5582 if (t->icon_pixbuf)
5583 g_object_unref(t->icon_pixbuf);
5584 if (t->icon_dest_uri)
5585 g_free(t->icon_dest_uri);
5587 t->icon_pixbuf = NULL;
5588 t->icon_request = NULL;
5589 t->icon_dest_uri = NULL;
5592 void
5593 xt_icon_from_name(struct tab *t, gchar *name)
5595 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5596 GTK_ENTRY_ICON_PRIMARY, "text-html");
5597 if (show_url == 0)
5598 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5599 GTK_ENTRY_ICON_PRIMARY, "text-html");
5600 else
5601 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5602 GTK_ENTRY_ICON_PRIMARY, NULL);
5605 void
5606 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5608 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5609 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5610 if (show_url == 0)
5611 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5612 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5613 else
5614 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5615 GTK_ENTRY_ICON_PRIMARY, NULL);
5618 gboolean
5619 is_valid_icon(char *file)
5621 gboolean valid = 0;
5622 const char *mime_type;
5623 GFileInfo *fi;
5624 GFile *gf;
5626 gf = g_file_new_for_path(file);
5627 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5628 NULL, NULL);
5629 mime_type = g_file_info_get_content_type(fi);
5630 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5631 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5632 g_strcmp0(mime_type, "image/png") == 0 ||
5633 g_strcmp0(mime_type, "image/gif") == 0 ||
5634 g_strcmp0(mime_type, "application/octet-stream") == 0;
5635 g_object_unref(fi);
5636 g_object_unref(gf);
5638 return (valid);
5641 void
5642 set_favicon_from_file(struct tab *t, char *file)
5644 gint width, height;
5645 GdkPixbuf *pixbuf, *scaled;
5646 struct stat sb;
5648 if (t == NULL || file == NULL)
5649 return;
5650 if (t->icon_pixbuf) {
5651 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5652 return;
5655 if (g_str_has_prefix(file, "file://"))
5656 file += strlen("file://");
5657 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5659 if (!stat(file, &sb)) {
5660 if (sb.st_size == 0 || !is_valid_icon(file)) {
5661 /* corrupt icon so trash it */
5662 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5663 __func__, file);
5664 unlink(file);
5665 /* no need to set icon to default here */
5666 return;
5670 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5671 if (pixbuf == NULL) {
5672 xt_icon_from_name(t, "text-html");
5673 return;
5676 g_object_get(pixbuf, "width", &width, "height", &height,
5677 (char *)NULL);
5678 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5679 __func__, t->tab_id, width, height);
5681 if (width > 16 || height > 16) {
5682 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5683 GDK_INTERP_BILINEAR);
5684 g_object_unref(pixbuf);
5685 } else
5686 scaled = pixbuf;
5688 if (scaled == NULL) {
5689 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5690 GDK_INTERP_BILINEAR);
5691 return;
5694 t->icon_pixbuf = scaled;
5695 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5698 void
5699 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5700 WebKitWebView *wv)
5702 WebKitDownloadStatus status = webkit_download_get_status(download);
5703 struct tab *tt = NULL, *t = NULL;
5706 * find the webview instead of passing in the tab as it could have been
5707 * deleted from underneath us.
5709 TAILQ_FOREACH(tt, &tabs, entry) {
5710 if (tt->wv == wv) {
5711 t = tt;
5712 break;
5715 if (t == NULL)
5716 return;
5718 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5719 __func__, t->tab_id, status);
5721 switch (status) {
5722 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5723 /* -1 */
5724 t->icon_download = NULL;
5725 free_favicon(t);
5726 break;
5727 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5728 /* 0 */
5729 break;
5730 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5731 /* 1 */
5732 break;
5733 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5734 /* 2 */
5735 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5736 __func__, t->tab_id);
5737 t->icon_download = NULL;
5738 free_favicon(t);
5739 break;
5740 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5741 /* 3 */
5743 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5744 __func__, t->icon_dest_uri);
5745 set_favicon_from_file(t, t->icon_dest_uri);
5746 /* these will be freed post callback */
5747 t->icon_request = NULL;
5748 t->icon_download = NULL;
5749 break;
5750 default:
5751 break;
5755 void
5756 abort_favicon_download(struct tab *t)
5758 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5760 if (t->icon_download) {
5761 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5762 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5763 webkit_download_cancel(t->icon_download);
5764 t->icon_download = NULL;
5765 } else
5766 free_favicon(t);
5768 xt_icon_from_name(t, "text-html");
5771 void
5772 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5774 gchar *name_hash, file[PATH_MAX];
5775 struct stat sb;
5777 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5779 if (uri == NULL || t == NULL)
5780 return;
5782 if (t->icon_request) {
5783 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5784 return;
5787 /* check to see if we got the icon in cache */
5788 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5789 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5790 g_free(name_hash);
5792 if (!stat(file, &sb)) {
5793 if (sb.st_size > 0) {
5794 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5795 __func__, file);
5796 set_favicon_from_file(t, file);
5797 return;
5800 /* corrupt icon so trash it */
5801 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5802 __func__, file);
5803 unlink(file);
5806 /* create download for icon */
5807 t->icon_request = webkit_network_request_new(uri);
5808 if (t->icon_request == NULL) {
5809 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5810 __func__, uri);
5811 return;
5814 t->icon_download = webkit_download_new(t->icon_request);
5815 if (t->icon_download == NULL) {
5816 fprintf(stderr, "%s: icon_download", __func__);
5817 return;
5820 /* we have to free icon_dest_uri later */
5821 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5822 webkit_download_set_destination_uri(t->icon_download,
5823 t->icon_dest_uri);
5825 if (webkit_download_get_status(t->icon_download) ==
5826 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5827 fprintf(stderr, "%s: download failed to start", __func__);
5828 g_object_unref(t->icon_request);
5829 g_free(t->icon_dest_uri);
5830 t->icon_request = NULL;
5831 t->icon_dest_uri = NULL;
5832 return;
5835 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5836 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5838 webkit_download_start(t->icon_download);
5841 void
5842 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5844 const gchar *set = NULL, *uri = NULL, *title = NULL;
5845 struct history *h, find;
5846 const gchar *s_loading;
5847 struct karg a;
5849 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5850 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5852 if (t == NULL) {
5853 show_oops_s("notify_load_status_cb invalid paramters");
5854 return;
5857 switch (webkit_web_view_get_load_status(wview)) {
5858 case WEBKIT_LOAD_PROVISIONAL:
5859 /* 0 */
5860 abort_favicon_download(t);
5861 #if GTK_CHECK_VERSION(2, 20, 0)
5862 gtk_widget_show(t->spinner);
5863 gtk_spinner_start(GTK_SPINNER(t->spinner));
5864 #endif
5865 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5867 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5869 t->focus_wv = 1;
5871 break;
5873 case WEBKIT_LOAD_COMMITTED:
5874 /* 1 */
5875 if ((uri = get_uri(wview)) != NULL) {
5876 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5877 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5879 if (t->status) {
5880 g_free(t->status);
5881 t->status = NULL;
5883 set_status(t, (char *)uri, XT_STATUS_LOADING);
5886 /* check if js white listing is enabled */
5887 if (enable_js_whitelist) {
5888 uri = get_uri(wview);
5889 check_and_set_js(uri, t);
5892 if (t->styled)
5893 apply_style(t);
5895 show_ca_status(t, uri);
5897 /* we know enough to autosave the session */
5898 if (session_autosave) {
5899 a.s = NULL;
5900 save_tabs(t, &a);
5902 break;
5904 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5905 /* 3 */
5906 break;
5908 case WEBKIT_LOAD_FINISHED:
5909 /* 2 */
5910 uri = get_uri(wview);
5911 if (uri == NULL)
5912 return;
5914 if (!strncmp(uri, "http://", strlen("http://")) ||
5915 !strncmp(uri, "https://", strlen("https://")) ||
5916 !strncmp(uri, "file://", strlen("file://"))) {
5917 find.uri = uri;
5918 h = RB_FIND(history_list, &hl, &find);
5919 if (!h) {
5920 title = webkit_web_view_get_title(wview);
5921 set = title ? title: uri;
5922 h = g_malloc(sizeof *h);
5923 h->uri = g_strdup(uri);
5924 h->title = g_strdup(set);
5925 RB_INSERT(history_list, &hl, h);
5926 completion_add_uri(h->uri);
5927 update_history_tabs(NULL);
5931 set_status(t, (char *)uri, XT_STATUS_URI);
5932 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5933 case WEBKIT_LOAD_FAILED:
5934 /* 4 */
5935 #endif
5936 #if GTK_CHECK_VERSION(2, 20, 0)
5937 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5938 gtk_widget_hide(t->spinner);
5939 #endif
5940 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5941 if (s_loading && !strcmp(s_loading, "Loading"))
5942 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5943 default:
5944 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5945 break;
5948 if (t->item)
5949 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5950 else
5951 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5952 webkit_web_view_can_go_back(wview));
5954 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5955 webkit_web_view_can_go_forward(wview));
5957 /* take focus if we are visible */
5958 focus_webview(t);
5961 void
5962 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5964 const gchar *set = NULL, *title = NULL;
5966 title = webkit_web_view_get_title(wview);
5967 set = title ? title: get_uri(wview);
5968 if (set) {
5969 gtk_label_set_text(GTK_LABEL(t->label), set);
5970 gtk_window_set_title(GTK_WINDOW(main_window), set);
5971 } else {
5972 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5973 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5977 void
5978 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5980 run_script(t, JS_HINTING);
5983 void
5984 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5986 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5987 progress == 100 ? 0 : (double)progress / 100);
5988 if (show_url == 0) {
5989 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5990 progress == 100 ? 0 : (double)progress / 100);
5995 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5996 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5997 WebKitWebPolicyDecision *pd, struct tab *t)
5999 char *uri;
6000 WebKitWebNavigationReason reason;
6001 struct domain *d = NULL;
6003 if (t == NULL) {
6004 show_oops_s("webview_npd_cb invalid parameters");
6005 return (FALSE);
6008 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6009 t->ctrl_click,
6010 webkit_network_request_get_uri(request));
6012 uri = (char *)webkit_network_request_get_uri(request);
6014 /* if this is an xtp url, we don't load anything else */
6015 if (parse_xtp_url(t, uri))
6016 return (TRUE);
6018 if (t->ctrl_click) {
6019 t->ctrl_click = 0;
6020 create_new_tab(uri, NULL, ctrl_click_focus);
6021 webkit_web_policy_decision_ignore(pd);
6022 return (TRUE); /* we made the decission */
6026 * This is a little hairy but it comes down to this:
6027 * when we run in whitelist mode we have to assist the browser in
6028 * opening the URL that it would have opened in a new tab.
6030 reason = webkit_web_navigation_action_get_reason(na);
6031 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6032 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6033 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6034 load_uri(t, uri);
6036 webkit_web_policy_decision_use(pd);
6037 return (TRUE); /* we made the decission */
6040 return (FALSE);
6043 WebKitWebView *
6044 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6046 struct tab *tt;
6047 struct domain *d = NULL;
6048 const gchar *uri;
6049 WebKitWebView *webview = NULL;
6051 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6052 webkit_web_view_get_uri(wv));
6054 if (tabless) {
6055 /* open in current tab */
6056 webview = t->wv;
6057 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6058 uri = webkit_web_view_get_uri(wv);
6059 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6060 return (NULL);
6062 tt = create_new_tab(NULL, NULL, 1);
6063 webview = tt->wv;
6064 } else if (enable_scripts == 1) {
6065 tt = create_new_tab(NULL, NULL, 1);
6066 webview = tt->wv;
6069 return (webview);
6072 gboolean
6073 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6075 const gchar *uri;
6076 struct domain *d = NULL;
6078 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6080 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6081 uri = webkit_web_view_get_uri(wv);
6082 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6083 return (FALSE);
6085 delete_tab(t);
6086 } else if (enable_scripts == 1)
6087 delete_tab(t);
6089 return (TRUE);
6093 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6095 /* we can not eat the event without throwing gtk off so defer it */
6097 /* catch middle click */
6098 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6099 t->ctrl_click = 1;
6100 goto done;
6103 /* catch ctrl click */
6104 if (e->type == GDK_BUTTON_RELEASE &&
6105 CLEAN(e->state) == GDK_CONTROL_MASK)
6106 t->ctrl_click = 1;
6107 else
6108 t->ctrl_click = 0;
6109 done:
6110 return (XT_CB_PASSTHROUGH);
6114 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6116 struct mime_type *m;
6118 m = find_mime_type(mime_type);
6119 if (m == NULL)
6120 return (1);
6121 if (m->mt_download)
6122 return (1);
6124 switch (fork()) {
6125 case -1:
6126 show_oops(t, "can't fork mime handler");
6127 /* NOTREACHED */
6128 case 0:
6129 break;
6130 default:
6131 return (0);
6134 /* child */
6135 execlp(m->mt_action, m->mt_action,
6136 webkit_network_request_get_uri(request), (void *)NULL);
6138 _exit(0);
6140 /* NOTREACHED */
6141 return (0);
6144 const gchar *
6145 get_mime_type(char *file)
6147 const char *mime_type;
6148 GFileInfo *fi;
6149 GFile *gf;
6151 if (g_str_has_prefix(file, "file://"))
6152 file += strlen("file://");
6154 gf = g_file_new_for_path(file);
6155 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6156 NULL, NULL);
6157 mime_type = g_file_info_get_content_type(fi);
6158 g_object_unref(fi);
6159 g_object_unref(gf);
6161 return (mime_type);
6165 run_download_mimehandler(char *mime_type, char *file)
6167 struct mime_type *m;
6169 m = find_mime_type(mime_type);
6170 if (m == NULL)
6171 return (1);
6173 switch (fork()) {
6174 case -1:
6175 show_oops_s("can't fork download mime handler");
6176 /* NOTREACHED */
6177 case 0:
6178 break;
6179 default:
6180 return (0);
6183 /* child */
6184 if (g_str_has_prefix(file, "file://"))
6185 file += strlen("file://");
6186 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6188 _exit(0);
6190 /* NOTREACHED */
6191 return (0);
6194 void
6195 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6196 WebKitWebView *wv)
6198 WebKitDownloadStatus status;
6199 const gchar *file = NULL, *mime = NULL;
6201 if (download == NULL)
6202 return;
6203 status = webkit_download_get_status(download);
6204 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6205 return;
6207 file = webkit_download_get_destination_uri(download);
6208 if (file == NULL)
6209 return;
6210 mime = get_mime_type((char *)file);
6211 if (mime == NULL)
6212 return;
6214 run_download_mimehandler((char *)mime, (char *)file);
6218 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6219 WebKitNetworkRequest *request, char *mime_type,
6220 WebKitWebPolicyDecision *decision, struct tab *t)
6222 if (t == NULL) {
6223 show_oops_s("webview_mimetype_cb invalid parameters");
6224 return (FALSE);
6227 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6228 t->tab_id, mime_type);
6230 if (run_mimehandler(t, mime_type, request) == 0) {
6231 webkit_web_policy_decision_ignore(decision);
6232 focus_webview(t);
6233 return (TRUE);
6236 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6237 webkit_web_policy_decision_download(decision);
6238 return (TRUE);
6241 return (FALSE);
6245 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6246 struct tab *t)
6248 const gchar *filename;
6249 char *uri = NULL;
6250 struct download *download_entry;
6251 int ret = TRUE;
6253 if (wk_download == NULL || t == NULL) {
6254 show_oops_s("%s invalid parameters", __func__);
6255 return (FALSE);
6258 filename = webkit_download_get_suggested_filename(wk_download);
6259 if (filename == NULL)
6260 return (FALSE); /* abort download */
6262 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6264 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6265 "local %s\n", __func__, t->tab_id, filename, uri);
6267 webkit_download_set_destination_uri(wk_download, uri);
6269 if (webkit_download_get_status(wk_download) ==
6270 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6271 show_oops(t, "%s: download failed to start", __func__);
6272 ret = FALSE;
6273 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6274 } else {
6275 /* connect "download first" mime handler */
6276 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6277 G_CALLBACK(download_status_changed_cb), NULL);
6279 download_entry = g_malloc(sizeof(struct download));
6280 download_entry->download = wk_download;
6281 download_entry->tab = t;
6282 download_entry->id = next_download_id++;
6283 RB_INSERT(download_list, &downloads, download_entry);
6284 /* get from history */
6285 g_object_ref(wk_download);
6286 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6287 show_oops(t, "Download of '%s' started...",
6288 basename(webkit_download_get_destination_uri(wk_download)));
6291 if (uri)
6292 g_free(uri);
6294 /* sync other download manager tabs */
6295 update_download_tabs(NULL);
6298 * NOTE: never redirect/render the current tab before this
6299 * function returns. This will cause the download to never start.
6301 return (ret); /* start download */
6304 void
6305 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6307 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6309 if (t == NULL) {
6310 show_oops_s("webview_hover_cb");
6311 return;
6314 if (uri)
6315 set_status(t, uri, XT_STATUS_LINK);
6316 else {
6317 if (t->status)
6318 set_status(t, t->status, XT_STATUS_NOTHING);
6322 gboolean
6323 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6325 struct key_binding *k;
6327 TAILQ_FOREACH(k, &kbl, entry)
6328 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6329 if (k->mask == 0) {
6330 if ((e->state & (CTRL | MOD1)) == 0)
6331 return (cmd_execute(t, k->cmd));
6332 } else if ((e->state & k->mask) == k->mask) {
6333 return (cmd_execute(t, k->cmd));
6337 return (XT_CB_PASSTHROUGH);
6341 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6343 char s[2], buf[128];
6344 const char *errstr = NULL;
6345 long long link;
6347 /* don't use w directly; use t->whatever instead */
6349 if (t == NULL) {
6350 show_oops_s("wv_keypress_after_cb");
6351 return (XT_CB_PASSTHROUGH);
6354 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6355 e->keyval, e->state, t);
6357 if (t->hints_on) {
6358 /* ESC */
6359 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6360 disable_hints(t);
6361 return (XT_CB_HANDLED);
6364 /* RETURN */
6365 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6366 link = strtonum(t->hint_num, 1, 1000, &errstr);
6367 if (errstr) {
6368 /* we have a string */
6369 } else {
6370 /* we have a number */
6371 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6372 t->hint_num);
6373 run_script(t, buf);
6375 disable_hints(t);
6378 /* BACKSPACE */
6379 /* XXX unfuck this */
6380 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6381 if (t->hint_mode == XT_HINT_NUMERICAL) {
6382 /* last input was numerical */
6383 int l;
6384 l = strlen(t->hint_num);
6385 if (l > 0) {
6386 l--;
6387 if (l == 0) {
6388 disable_hints(t);
6389 enable_hints(t);
6390 } else {
6391 t->hint_num[l] = '\0';
6392 goto num;
6395 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6396 /* last input was alphanumerical */
6397 int l;
6398 l = strlen(t->hint_buf);
6399 if (l > 0) {
6400 l--;
6401 if (l == 0) {
6402 disable_hints(t);
6403 enable_hints(t);
6404 } else {
6405 t->hint_buf[l] = '\0';
6406 goto anum;
6409 } else {
6410 /* bogus */
6411 disable_hints(t);
6415 /* numerical input */
6416 if (CLEAN(e->state) == 0 &&
6417 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6418 snprintf(s, sizeof s, "%c", e->keyval);
6419 strlcat(t->hint_num, s, sizeof t->hint_num);
6420 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6421 t->hint_num);
6422 num:
6423 link = strtonum(t->hint_num, 1, 1000, &errstr);
6424 if (errstr) {
6425 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6426 disable_hints(t);
6427 } else {
6428 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6429 t->hint_num);
6430 t->hint_mode = XT_HINT_NUMERICAL;
6431 run_script(t, buf);
6434 /* empty the counter buffer */
6435 bzero(t->hint_buf, sizeof t->hint_buf);
6436 return (XT_CB_HANDLED);
6439 /* alphanumerical input */
6440 if (
6441 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6442 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6443 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6444 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6445 snprintf(s, sizeof s, "%c", e->keyval);
6446 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6447 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6448 t->hint_buf);
6449 anum:
6450 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6451 run_script(t, buf);
6453 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6454 t->hint_buf);
6455 t->hint_mode = XT_HINT_ALPHANUM;
6456 run_script(t, buf);
6458 /* empty the counter buffer */
6459 bzero(t->hint_num, sizeof t->hint_num);
6460 return (XT_CB_HANDLED);
6463 return (XT_CB_HANDLED);
6464 } else {
6465 /* prefix input*/
6466 snprintf(s, sizeof s, "%c", e->keyval);
6467 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6468 cmd_prefix = 10 * cmd_prefix + atoi(s);
6472 return (handle_keypress(t, e, 0));
6476 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6478 hide_oops(t);
6480 return (XT_CB_PASSTHROUGH);
6484 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6486 const gchar *c = gtk_entry_get_text(w);
6487 GdkColor color;
6488 int forward = TRUE;
6490 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6491 e->keyval, e->state, t);
6493 if (t == NULL) {
6494 show_oops_s("cmd_keyrelease_cb invalid parameters");
6495 return (XT_CB_PASSTHROUGH);
6498 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6499 e->keyval, e->state, t);
6501 if (c[0] == ':')
6502 goto done;
6503 if (strlen(c) == 1) {
6504 webkit_web_view_unmark_text_matches(t->wv);
6505 goto done;
6508 if (c[0] == '/')
6509 forward = TRUE;
6510 else if (c[0] == '?')
6511 forward = FALSE;
6512 else
6513 goto done;
6515 /* search */
6516 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6517 FALSE) {
6518 /* not found, mark red */
6519 gdk_color_parse(XT_COLOR_RED, &color);
6520 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6521 /* unmark and remove selection */
6522 webkit_web_view_unmark_text_matches(t->wv);
6523 /* my kingdom for a way to unselect text in webview */
6524 } else {
6525 /* found, highlight all */
6526 webkit_web_view_unmark_text_matches(t->wv);
6527 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6528 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6529 gdk_color_parse(XT_COLOR_WHITE, &color);
6530 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6532 done:
6533 return (XT_CB_PASSTHROUGH);
6536 gboolean
6537 match_uri(const gchar *uri, const gchar *key) {
6538 gchar *voffset;
6539 size_t len;
6540 gboolean match = FALSE;
6542 len = strlen(key);
6544 if (!strncmp(key, uri, len))
6545 match = TRUE;
6546 else {
6547 voffset = strstr(uri, "/") + 2;
6548 if (!strncmp(key, voffset, len))
6549 match = TRUE;
6550 else if (g_str_has_prefix(voffset, "www.")) {
6551 voffset = voffset + strlen("www.");
6552 if (!strncmp(key, voffset, len))
6553 match = TRUE;
6557 return (match);
6560 void
6561 cmd_getlist(int id, char *key)
6563 int i, dep, c = 0;
6564 struct history *h;
6566 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6567 RB_FOREACH_REVERSE(h, history_list, &hl)
6568 if (match_uri(h->uri, key)) {
6569 cmd_status.list[c] = (char *)h->uri;
6570 if (++c > 255)
6571 break;
6574 cmd_status.len = c;
6575 return;
6578 dep = (id == -1) ? 0 : cmds[id].level + 1;
6580 for (i = id + 1; i < LENGTH(cmds); i++) {
6581 if(cmds[i].level < dep)
6582 break;
6583 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6584 cmd_status.list[c++] = cmds[i].cmd;
6588 cmd_status.len = c;
6591 char *
6592 cmd_getnext(int dir)
6594 cmd_status.index += dir;
6596 if (cmd_status.index < 0)
6597 cmd_status.index = cmd_status.len - 1;
6598 else if (cmd_status.index >= cmd_status.len)
6599 cmd_status.index = 0;
6601 return cmd_status.list[cmd_status.index];
6605 cmd_tokenize(char *s, char *tokens[])
6607 int i = 0;
6608 char *tok, *last;
6609 size_t len = strlen(s);
6610 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6612 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6613 tokens[i] = tok;
6615 if (blank && i < 3)
6616 tokens[i++] = "";
6618 return (i);
6621 void
6622 cmd_complete(struct tab *t, char *str, int dir)
6624 GtkEntry *w = GTK_ENTRY(t->cmd);
6625 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6626 char *tok, *match, *s = g_strdup(str);
6627 char *tokens[3];
6628 char res[XT_MAX_URL_LENGTH + 32] = ":";
6629 char *sc = s;
6631 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6633 /* copy prefix*/
6634 for (i = 0; isdigit(s[i]); i++)
6635 res[i + 1] = s[i];
6637 for (; isspace(s[i]); i++)
6638 res[i + 1] = s[i];
6640 s += i;
6642 levels = cmd_tokenize(s, tokens);
6644 for (i = 0; i < levels - 1; i++) {
6645 tok = tokens[i];
6646 matchcount = 0;
6647 for (j = c; j < LENGTH(cmds); j++) {
6648 if (cmds[j].level < dep)
6649 break;
6650 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6651 matchcount++;
6652 c = j + 1;
6653 if (strlen(tok) == strlen(cmds[j].cmd)) {
6654 matchcount = 1;
6655 break;
6660 if (matchcount == 1) {
6661 strlcat(res, tok, sizeof res);
6662 strlcat(res, " ", sizeof res);
6663 dep++;
6664 } else {
6665 g_free(sc);
6666 return;
6669 parent = c - 1;
6672 if (cmd_status.index == -1)
6673 cmd_getlist(parent, tokens[i]);
6675 if (cmd_status.len > 0) {
6676 match = cmd_getnext(dir);
6677 strlcat(res, match, sizeof res);
6678 gtk_entry_set_text(w, res);
6679 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6682 g_free(sc);
6685 gboolean
6686 cmd_execute(struct tab *t, char *str)
6688 struct cmd *cmd = NULL;
6689 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6690 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6691 struct karg arg = {0, NULL, -1};
6692 int rv = XT_CB_PASSTHROUGH;
6694 sc = s;
6696 /* copy prefix*/
6697 for (j = 0; j<3 && isdigit(s[j]); j++)
6698 prefixstr[j]=s[j];
6700 prefixstr[j]='\0';
6702 s += j;
6703 while (isspace(s[0]))
6704 s++;
6706 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6707 prefix = atoi(prefixstr);
6708 else
6709 s = sc;
6711 for (tok = strtok_r(s, " ", &last); tok;
6712 tok = strtok_r(NULL, " ", &last)) {
6713 matchcount = 0;
6714 for (j = c; j < LENGTH(cmds); j++) {
6715 if (cmds[j].level < dep)
6716 break;
6717 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6718 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6719 matchcount++;
6720 c = j + 1;
6721 cmd = &cmds[j];
6722 if (len == strlen(cmds[j].cmd)) {
6723 matchcount = 1;
6724 break;
6728 if (matchcount == 1) {
6729 if (cmd->type & XT_USERARG)
6730 goto execute_cmd;
6731 dep++;
6732 } else {
6733 show_oops(t, "Invalid command: %s", str);
6734 goto done;
6737 execute_cmd:
6738 arg.i = cmd->arg;
6740 if (prefix != -1)
6741 arg.p = prefix;
6742 else if (cmd_prefix > 0)
6743 arg.p = cmd_prefix;
6745 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6746 show_oops(t, "No Prefix allowed: %s", str);
6747 goto done;
6750 if (cmd->type & XT_USERARG)
6751 arg.s = last ? g_strdup(last) : g_strdup("");
6753 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6755 cmd->func(t, &arg);
6757 rv = XT_CB_HANDLED;
6758 done:
6759 if (j > 0)
6760 cmd_prefix = 0;
6761 g_free(sc);
6762 if (arg.s)
6763 g_free(arg.s);
6765 return (rv);
6769 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6771 if (t == NULL) {
6772 show_oops_s("entry_key_cb invalid parameters");
6773 return (XT_CB_PASSTHROUGH);
6776 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6777 e->keyval, e->state, t);
6779 hide_oops(t);
6781 if (e->keyval == GDK_Escape) {
6782 /* don't use focus_webview(t) because we want to type :cmds */
6783 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6786 return (handle_keypress(t, e, 1));
6790 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6792 int rv = XT_CB_HANDLED;
6793 const gchar *c = gtk_entry_get_text(w);
6795 if (t == NULL) {
6796 show_oops_s("cmd_keypress_cb parameters");
6797 return (XT_CB_PASSTHROUGH);
6800 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6801 e->keyval, e->state, t);
6803 /* sanity */
6804 if (c == NULL)
6805 e->keyval = GDK_Escape;
6806 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6807 e->keyval = GDK_Escape;
6809 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6810 cmd_status.index = -1;
6812 switch (e->keyval) {
6813 case GDK_Tab:
6814 if (c[0] == ':')
6815 cmd_complete(t, (char *)&c[1], 1);
6816 goto done;
6817 case GDK_ISO_Left_Tab:
6818 if (c[0] == ':')
6819 cmd_complete(t, (char *)&c[1], -1);
6821 goto done;
6822 case GDK_BackSpace:
6823 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6824 break;
6825 /* FALLTHROUGH */
6826 case GDK_Escape:
6827 hide_cmd(t);
6828 focus_webview(t);
6830 /* cancel search */
6831 if (c[0] == '/' || c[0] == '?')
6832 webkit_web_view_unmark_text_matches(t->wv);
6833 goto done;
6836 rv = XT_CB_PASSTHROUGH;
6837 done:
6838 return (rv);
6842 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6844 if (t == NULL) {
6845 show_oops_s("cmd_focusout_cb invalid parameters");
6846 return (XT_CB_PASSTHROUGH);
6848 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6850 hide_cmd(t);
6851 hide_oops(t);
6853 if (show_url == 0 || t->focus_wv)
6854 focus_webview(t);
6855 else
6856 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6858 return (XT_CB_PASSTHROUGH);
6861 void
6862 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6864 char *s;
6865 const gchar *c = gtk_entry_get_text(entry);
6867 if (t == NULL) {
6868 show_oops_s("cmd_activate_cb invalid parameters");
6869 return;
6872 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6874 hide_cmd(t);
6876 /* sanity */
6877 if (c == NULL)
6878 goto done;
6879 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6880 goto done;
6881 if (strlen(c) < 2)
6882 goto done;
6883 s = (char *)&c[1];
6885 if (c[0] == '/' || c[0] == '?') {
6886 if (t->search_text) {
6887 g_free(t->search_text);
6888 t->search_text = NULL;
6891 t->search_text = g_strdup(s);
6892 if (global_search)
6893 g_free(global_search);
6894 global_search = g_strdup(s);
6895 t->search_forward = c[0] == '/';
6897 goto done;
6900 cmd_execute(t, s);
6902 done:
6903 return;
6906 void
6907 backward_cb(GtkWidget *w, struct tab *t)
6909 struct karg a;
6911 if (t == NULL) {
6912 show_oops_s("backward_cb invalid parameters");
6913 return;
6916 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6918 a.i = XT_NAV_BACK;
6919 navaction(t, &a);
6922 void
6923 forward_cb(GtkWidget *w, struct tab *t)
6925 struct karg a;
6927 if (t == NULL) {
6928 show_oops_s("forward_cb invalid parameters");
6929 return;
6932 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6934 a.i = XT_NAV_FORWARD;
6935 navaction(t, &a);
6938 void
6939 home_cb(GtkWidget *w, struct tab *t)
6941 if (t == NULL) {
6942 show_oops_s("home_cb invalid parameters");
6943 return;
6946 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6948 load_uri(t, home);
6951 void
6952 stop_cb(GtkWidget *w, struct tab *t)
6954 WebKitWebFrame *frame;
6956 if (t == NULL) {
6957 show_oops_s("stop_cb invalid parameters");
6958 return;
6961 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6963 frame = webkit_web_view_get_main_frame(t->wv);
6964 if (frame == NULL) {
6965 show_oops(t, "stop_cb: no frame");
6966 return;
6969 webkit_web_frame_stop_loading(frame);
6970 abort_favicon_download(t);
6973 void
6974 setup_webkit(struct tab *t)
6976 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6977 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6978 FALSE, (char *)NULL);
6979 else
6980 warnx("webkit does not have \"enable-dns-prefetching\" property");
6981 g_object_set(G_OBJECT(t->settings),
6982 "user-agent", t->user_agent, (char *)NULL);
6983 g_object_set(G_OBJECT(t->settings),
6984 "enable-scripts", enable_scripts, (char *)NULL);
6985 g_object_set(G_OBJECT(t->settings),
6986 "enable-plugins", enable_plugins, (char *)NULL);
6987 g_object_set(G_OBJECT(t->settings),
6988 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6989 g_object_set(G_OBJECT(t->wv),
6990 "full-content-zoom", TRUE, (char *)NULL);
6991 adjustfont_webkit(t, XT_FONT_SET);
6993 webkit_web_view_set_settings(t->wv, t->settings);
6996 GtkWidget *
6997 create_browser(struct tab *t)
6999 GtkWidget *w;
7000 gchar *strval;
7002 if (t == NULL) {
7003 show_oops_s("create_browser invalid parameters");
7004 return (NULL);
7007 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7008 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7009 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7010 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7012 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7013 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7014 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7016 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7017 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7019 /* set defaults */
7020 t->settings = webkit_web_settings_new();
7022 if (user_agent == NULL) {
7023 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7024 (char *)NULL);
7025 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7026 g_free(strval);
7027 } else
7028 t->user_agent = g_strdup(user_agent);
7030 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7032 setup_webkit(t);
7034 return (w);
7037 GtkWidget *
7038 create_window(void)
7040 GtkWidget *w;
7042 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7043 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7044 gtk_widget_set_name(w, "xxxterm");
7045 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7046 g_signal_connect(G_OBJECT(w), "delete_event",
7047 G_CALLBACK (gtk_main_quit), NULL);
7049 return (w);
7052 GtkWidget *
7053 create_kiosk_toolbar(struct tab *t)
7055 GtkWidget *toolbar = NULL, *b;
7057 b = gtk_hbox_new(FALSE, 0);
7058 toolbar = b;
7059 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7061 /* backward button */
7062 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7063 gtk_widget_set_sensitive(t->backward, FALSE);
7064 g_signal_connect(G_OBJECT(t->backward), "clicked",
7065 G_CALLBACK(backward_cb), t);
7066 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7068 /* forward button */
7069 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7070 gtk_widget_set_sensitive(t->forward, FALSE);
7071 g_signal_connect(G_OBJECT(t->forward), "clicked",
7072 G_CALLBACK(forward_cb), t);
7073 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7075 /* home button */
7076 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7077 gtk_widget_set_sensitive(t->gohome, true);
7078 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7079 G_CALLBACK(home_cb), t);
7080 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7082 /* create widgets but don't use them */
7083 t->uri_entry = gtk_entry_new();
7084 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7085 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7086 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7088 return (toolbar);
7091 GtkWidget *
7092 create_toolbar(struct tab *t)
7094 GtkWidget *toolbar = NULL, *b, *eb1;
7096 b = gtk_hbox_new(FALSE, 0);
7097 toolbar = b;
7098 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7100 if (fancy_bar) {
7101 /* backward button */
7102 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7103 gtk_widget_set_sensitive(t->backward, FALSE);
7104 g_signal_connect(G_OBJECT(t->backward), "clicked",
7105 G_CALLBACK(backward_cb), t);
7106 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7108 /* forward button */
7109 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7110 gtk_widget_set_sensitive(t->forward, FALSE);
7111 g_signal_connect(G_OBJECT(t->forward), "clicked",
7112 G_CALLBACK(forward_cb), t);
7113 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7114 FALSE, 0);
7116 /* stop button */
7117 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7118 gtk_widget_set_sensitive(t->stop, FALSE);
7119 g_signal_connect(G_OBJECT(t->stop), "clicked",
7120 G_CALLBACK(stop_cb), t);
7121 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7122 FALSE, 0);
7124 /* JS button */
7125 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7126 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7127 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7128 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7129 G_CALLBACK(js_toggle_cb), t);
7130 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7133 t->uri_entry = gtk_entry_new();
7134 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7135 G_CALLBACK(activate_uri_entry_cb), t);
7136 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7137 G_CALLBACK(entry_key_cb), t);
7138 completion_add(t);
7139 eb1 = gtk_hbox_new(FALSE, 0);
7140 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7141 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7142 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7144 /* search entry */
7145 if (fancy_bar && search_string) {
7146 GtkWidget *eb2;
7147 t->search_entry = gtk_entry_new();
7148 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7149 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7150 G_CALLBACK(activate_search_entry_cb), t);
7151 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7152 G_CALLBACK(entry_key_cb), t);
7153 gtk_widget_set_size_request(t->search_entry, -1, -1);
7154 eb2 = gtk_hbox_new(FALSE, 0);
7155 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7156 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7158 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7160 return (toolbar);
7163 void
7164 recalc_tabs(void)
7166 struct tab *t;
7168 TAILQ_FOREACH(t, &tabs, entry)
7169 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7173 undo_close_tab_save(struct tab *t)
7175 int m, n;
7176 const gchar *uri;
7177 struct undo *u1, *u2;
7178 GList *items;
7179 WebKitWebHistoryItem *item;
7181 if ((uri = get_uri(t->wv)) == NULL)
7182 return (1);
7184 u1 = g_malloc0(sizeof(struct undo));
7185 u1->uri = g_strdup(uri);
7187 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7189 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7190 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7191 u1->back = n;
7193 /* forward history */
7194 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7196 while (items) {
7197 item = items->data;
7198 u1->history = g_list_prepend(u1->history,
7199 webkit_web_history_item_copy(item));
7200 items = g_list_next(items);
7203 /* current item */
7204 if (m) {
7205 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7206 u1->history = g_list_prepend(u1->history,
7207 webkit_web_history_item_copy(item));
7210 /* back history */
7211 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7213 while (items) {
7214 item = items->data;
7215 u1->history = g_list_prepend(u1->history,
7216 webkit_web_history_item_copy(item));
7217 items = g_list_next(items);
7220 TAILQ_INSERT_HEAD(&undos, u1, entry);
7222 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7223 u2 = TAILQ_LAST(&undos, undo_tailq);
7224 TAILQ_REMOVE(&undos, u2, entry);
7225 g_free(u2->uri);
7226 g_list_free(u2->history);
7227 g_free(u2);
7228 } else
7229 undo_count++;
7231 return (0);
7234 void
7235 delete_tab(struct tab *t)
7237 struct karg a;
7239 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7241 if (t == NULL)
7242 return;
7244 TAILQ_REMOVE(&tabs, t, entry);
7246 /* halt all webkit activity */
7247 abort_favicon_download(t);
7248 webkit_web_view_stop_loading(t->wv);
7249 undo_close_tab_save(t);
7251 if (browser_mode == XT_BM_KIOSK) {
7252 gtk_widget_destroy(t->uri_entry);
7253 gtk_widget_destroy(t->stop);
7254 gtk_widget_destroy(t->js_toggle);
7257 gtk_widget_destroy(t->vbox);
7258 g_free(t->user_agent);
7259 g_free(t->stylesheet);
7260 g_free(t);
7262 if (TAILQ_EMPTY(&tabs)) {
7263 if (browser_mode == XT_BM_KIOSK)
7264 create_new_tab(home, NULL, 1);
7265 else
7266 create_new_tab(NULL, NULL, 1);
7269 /* recreate session */
7270 if (session_autosave) {
7271 a.s = NULL;
7272 save_tabs(t, &a);
7276 void
7277 adjustfont_webkit(struct tab *t, int adjust)
7279 gfloat zoom;
7281 if (t == NULL) {
7282 show_oops_s("adjustfont_webkit invalid parameters");
7283 return;
7286 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7287 if (adjust == XT_FONT_SET) {
7288 t->font_size = default_font_size;
7289 zoom = default_zoom_level;
7290 t->font_size += adjust;
7291 g_object_set(G_OBJECT(t->settings), "default-font-size",
7292 t->font_size, (char *)NULL);
7293 g_object_get(G_OBJECT(t->settings), "default-font-size",
7294 &t->font_size, (char *)NULL);
7295 } else {
7296 t->font_size += adjust;
7297 zoom += adjust/25.0;
7298 if (zoom < 0.0) {
7299 zoom = 0.04;
7302 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7303 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7306 void
7307 append_tab(struct tab *t)
7309 if (t == NULL)
7310 return;
7312 TAILQ_INSERT_TAIL(&tabs, t, entry);
7313 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7316 struct tab *
7317 create_new_tab(char *title, struct undo *u, int focus)
7319 struct tab *t, *tt;
7320 int load = 1, id, notfound;
7321 GtkWidget *b, *bb;
7322 WebKitWebHistoryItem *item;
7323 GList *items;
7324 GdkColor color;
7326 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7328 if (tabless && !TAILQ_EMPTY(&tabs)) {
7329 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7330 return (NULL);
7333 t = g_malloc0(sizeof *t);
7335 if (title == NULL) {
7336 title = "(untitled)";
7337 load = 0;
7340 t->vbox = gtk_vbox_new(FALSE, 0);
7342 /* label + button for tab */
7343 b = gtk_hbox_new(FALSE, 0);
7344 t->tab_content = b;
7346 #if GTK_CHECK_VERSION(2, 20, 0)
7347 t->spinner = gtk_spinner_new ();
7348 #endif
7349 t->label = gtk_label_new(title);
7350 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7351 gtk_widget_set_size_request(t->label, 100, 0);
7352 gtk_widget_set_size_request(b, 130, 0);
7354 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7355 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7356 #if GTK_CHECK_VERSION(2, 20, 0)
7357 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7358 #endif
7360 /* toolbar */
7361 if (browser_mode == XT_BM_KIOSK)
7362 t->toolbar = create_kiosk_toolbar(t);
7363 else
7364 t->toolbar = create_toolbar(t);
7366 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7368 /* browser */
7369 t->browser_win = create_browser(t);
7370 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7372 /* oops message for user feedback */
7373 t->oops = gtk_entry_new();
7374 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7375 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7376 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7377 gdk_color_parse(XT_COLOR_RED, &color);
7378 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7379 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7381 /* command entry */
7382 t->cmd = gtk_entry_new();
7383 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7384 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7385 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7387 /* status bar */
7388 t->statusbar = gtk_entry_new();
7389 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7390 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7391 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7392 gdk_color_parse(XT_COLOR_BLACK, &color);
7393 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7394 gdk_color_parse(XT_COLOR_WHITE, &color);
7395 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7396 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7398 /* xtp meaning is normal by default */
7399 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7401 /* set empty favicon */
7402 xt_icon_from_name(t, "text-html");
7404 /* and show it all */
7405 gtk_widget_show_all(b);
7406 gtk_widget_show_all(t->vbox);
7408 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7409 append_tab(t);
7410 else {
7411 notfound = 1;
7412 id = gtk_notebook_get_current_page(notebook);
7413 TAILQ_FOREACH(tt, &tabs, entry) {
7414 if (tt->tab_id == id) {
7415 notfound = 0;
7416 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7417 gtk_notebook_insert_page(notebook, t->vbox, b,
7418 id + 1);
7419 recalc_tabs();
7420 break;
7423 if (notfound)
7424 append_tab(t);
7427 #if GTK_CHECK_VERSION(2, 20, 0)
7428 /* turn spinner off if we are a new tab without uri */
7429 if (!load) {
7430 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7431 gtk_widget_hide(t->spinner);
7433 #endif
7434 /* make notebook tabs reorderable */
7435 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7437 g_object_connect(G_OBJECT(t->cmd),
7438 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7439 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7440 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7441 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7442 (char *)NULL);
7444 /* reuse wv_button_cb to hide oops */
7445 g_object_connect(G_OBJECT(t->oops),
7446 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7447 (char *)NULL);
7449 g_object_connect(G_OBJECT(t->wv),
7450 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7451 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7452 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7453 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7454 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7455 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7456 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7457 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7458 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7459 "signal::event", G_CALLBACK(webview_event_cb), t,
7460 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7461 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7462 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7463 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7464 #endif
7465 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7466 (char *)NULL);
7467 g_signal_connect(t->wv,
7468 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7469 g_signal_connect(t->wv,
7470 "notify::title", G_CALLBACK(notify_title_cb), t);
7472 /* hijack the unused keys as if we were the browser */
7473 g_object_connect(G_OBJECT(t->toolbar),
7474 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7475 (char *)NULL);
7477 g_signal_connect(G_OBJECT(bb), "button_press_event",
7478 G_CALLBACK(tab_close_cb), t);
7480 /* hide stuff */
7481 hide_cmd(t);
7482 hide_oops(t);
7483 url_set_visibility();
7484 statusbar_set_visibility();
7486 if (focus) {
7487 gtk_notebook_set_current_page(notebook, t->tab_id);
7488 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7489 t->tab_id);
7492 if (load) {
7493 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7494 load_uri(t, title);
7495 } else {
7496 if (show_url == 1)
7497 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7498 else
7499 focus_webview(t);
7502 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7503 /* restore the tab's history */
7504 if (u && u->history) {
7505 items = u->history;
7506 while (items) {
7507 item = items->data;
7508 webkit_web_back_forward_list_add_item(t->bfl, item);
7509 items = g_list_next(items);
7512 item = g_list_nth_data(u->history, u->back);
7513 if (item)
7514 webkit_web_view_go_to_back_forward_item(t->wv, item);
7516 g_list_free(items);
7517 g_list_free(u->history);
7518 } else
7519 webkit_web_back_forward_list_clear(t->bfl);
7521 return (t);
7524 void
7525 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7526 gpointer *udata)
7528 struct tab *t;
7529 const gchar *uri;
7531 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7533 if (gtk_notebook_get_current_page(notebook) == -1)
7534 recalc_tabs();
7536 TAILQ_FOREACH(t, &tabs, entry) {
7537 if (t->tab_id == pn) {
7538 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7539 "%d\n", pn);
7541 uri = webkit_web_view_get_title(t->wv);
7542 if (uri == NULL)
7543 uri = XT_NAME;
7544 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7546 hide_cmd(t);
7547 hide_oops(t);
7549 if (t->focus_wv)
7550 focus_webview(t);
7555 void
7556 notebook_pagereordered_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7557 gpointer *udata)
7559 recalc_tabs();
7562 void
7563 menuitem_response(struct tab *t)
7565 gtk_notebook_set_current_page(notebook, t->tab_id);
7568 gboolean
7569 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7571 GtkWidget *menu, *menu_items;
7572 GdkEventButton *bevent;
7573 const gchar *uri;
7574 struct tab *ti;
7576 if (event->type == GDK_BUTTON_PRESS) {
7577 bevent = (GdkEventButton *) event;
7578 menu = gtk_menu_new();
7580 TAILQ_FOREACH(ti, &tabs, entry) {
7581 if ((uri = get_uri(ti->wv)) == NULL)
7582 /* XXX make sure there is something to print */
7583 /* XXX add gui pages in here to look purdy */
7584 uri = "(untitled)";
7585 menu_items = gtk_menu_item_new_with_label(uri);
7586 gtk_menu_append(GTK_MENU (menu), menu_items);
7587 gtk_widget_show(menu_items);
7589 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7590 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7591 (gpointer)ti);
7594 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7595 bevent->button, bevent->time);
7597 /* unref object so it'll free itself when popped down */
7598 g_object_ref_sink(menu);
7599 g_object_unref(menu);
7601 return (TRUE /* eat event */);
7604 return (FALSE /* propagate */);
7608 icon_size_map(int icon_size)
7610 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7611 icon_size > GTK_ICON_SIZE_DIALOG)
7612 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7614 return (icon_size);
7617 GtkWidget *
7618 create_button(char *name, char *stockid, int size)
7620 GtkWidget *button, *image;
7621 gchar *rcstring;
7622 int gtk_icon_size;
7624 rcstring = g_strdup_printf(
7625 "style \"%s-style\"\n"
7626 "{\n"
7627 " GtkWidget::focus-padding = 0\n"
7628 " GtkWidget::focus-line-width = 0\n"
7629 " xthickness = 0\n"
7630 " ythickness = 0\n"
7631 "}\n"
7632 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7633 gtk_rc_parse_string(rcstring);
7634 g_free(rcstring);
7635 button = gtk_button_new();
7636 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7637 gtk_icon_size = icon_size_map(size ? size : icon_size);
7639 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7640 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7641 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7642 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7643 gtk_widget_set_name(button, name);
7644 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7646 return (button);
7649 void
7650 button_set_stockid(GtkWidget *button, char *stockid)
7652 GtkWidget *image;
7654 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7655 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7656 gtk_button_set_image(GTK_BUTTON(button), image);
7659 void
7660 create_canvas(void)
7662 GtkWidget *vbox;
7663 GList *l = NULL;
7664 GdkPixbuf *pb;
7665 char file[PATH_MAX];
7666 int i;
7668 vbox = gtk_vbox_new(FALSE, 0);
7669 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7670 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7671 gtk_notebook_set_tab_hborder(notebook, 0);
7672 gtk_notebook_set_tab_vborder(notebook, 0);
7673 gtk_notebook_set_scrollable(notebook, TRUE);
7674 notebook_tab_set_visibility(notebook);
7675 gtk_notebook_set_show_border(notebook, FALSE);
7676 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7678 abtn = gtk_button_new();
7679 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7680 gtk_widget_set_size_request(arrow, -1, -1);
7681 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7682 gtk_widget_set_size_request(abtn, -1, 20);
7684 #if GTK_CHECK_VERSION(2, 20, 0)
7685 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7686 #endif
7687 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7688 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7689 gtk_widget_set_size_request(vbox, -1, -1);
7691 g_object_connect(G_OBJECT(notebook),
7692 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7693 (char *)NULL);
7694 g_object_connect(G_OBJECT(notebook),
7695 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
7696 (char *)NULL);
7697 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7698 G_CALLBACK(arrow_cb), NULL);
7700 main_window = create_window();
7701 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7702 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7704 /* icons */
7705 for (i = 0; i < LENGTH(icons); i++) {
7706 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7707 pb = gdk_pixbuf_new_from_file(file, NULL);
7708 l = g_list_append(l, pb);
7710 gtk_window_set_default_icon_list(l);
7712 gtk_widget_show_all(abtn);
7713 gtk_widget_show_all(main_window);
7716 void
7717 set_hook(void **hook, char *name)
7719 if (hook == NULL)
7720 errx(1, "set_hook");
7722 if (*hook == NULL) {
7723 *hook = dlsym(RTLD_NEXT, name);
7724 if (*hook == NULL)
7725 errx(1, "can't hook %s", name);
7729 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7730 gboolean
7731 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7733 g_return_val_if_fail(cookie1, FALSE);
7734 g_return_val_if_fail(cookie2, FALSE);
7736 return (!strcmp (cookie1->name, cookie2->name) &&
7737 !strcmp (cookie1->value, cookie2->value) &&
7738 !strcmp (cookie1->path, cookie2->path) &&
7739 !strcmp (cookie1->domain, cookie2->domain));
7742 void
7743 transfer_cookies(void)
7745 GSList *cf;
7746 SoupCookie *sc, *pc;
7748 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7750 for (;cf; cf = cf->next) {
7751 pc = cf->data;
7752 sc = soup_cookie_copy(pc);
7753 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7756 soup_cookies_free(cf);
7759 void
7760 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7762 GSList *cf;
7763 SoupCookie *ci;
7765 print_cookie("soup_cookie_jar_delete_cookie", c);
7767 if (cookies_enabled == 0)
7768 return;
7770 if (jar == NULL || c == NULL)
7771 return;
7773 /* find and remove from persistent jar */
7774 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7776 for (;cf; cf = cf->next) {
7777 ci = cf->data;
7778 if (soup_cookie_equal(ci, c)) {
7779 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7780 break;
7784 soup_cookies_free(cf);
7786 /* delete from session jar */
7787 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7790 void
7791 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7793 struct domain *d = NULL;
7794 SoupCookie *c;
7795 FILE *r_cookie_f;
7797 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7798 jar, p_cookiejar, s_cookiejar);
7800 if (cookies_enabled == 0)
7801 return;
7803 /* see if we are up and running */
7804 if (p_cookiejar == NULL) {
7805 _soup_cookie_jar_add_cookie(jar, cookie);
7806 return;
7808 /* disallow p_cookiejar adds, shouldn't happen */
7809 if (jar == p_cookiejar)
7810 return;
7812 /* sanity */
7813 if (jar == NULL || cookie == NULL)
7814 return;
7816 if (enable_cookie_whitelist &&
7817 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7818 blocked_cookies++;
7819 DNPRINTF(XT_D_COOKIE,
7820 "soup_cookie_jar_add_cookie: reject %s\n",
7821 cookie->domain);
7822 if (save_rejected_cookies) {
7823 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7824 show_oops_s("can't open reject cookie file");
7825 return;
7827 fseek(r_cookie_f, 0, SEEK_END);
7828 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7829 cookie->http_only ? "#HttpOnly_" : "",
7830 cookie->domain,
7831 *cookie->domain == '.' ? "TRUE" : "FALSE",
7832 cookie->path,
7833 cookie->secure ? "TRUE" : "FALSE",
7834 cookie->expires ?
7835 (gulong)soup_date_to_time_t(cookie->expires) :
7837 cookie->name,
7838 cookie->value);
7839 fflush(r_cookie_f);
7840 fclose(r_cookie_f);
7842 if (!allow_volatile_cookies)
7843 return;
7846 if (cookie->expires == NULL && session_timeout) {
7847 soup_cookie_set_expires(cookie,
7848 soup_date_new_from_now(session_timeout));
7849 print_cookie("modified add cookie", cookie);
7852 /* see if we are white listed for persistence */
7853 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7854 /* add to persistent jar */
7855 c = soup_cookie_copy(cookie);
7856 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7857 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7860 /* add to session jar */
7861 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7862 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7865 void
7866 setup_cookies(void)
7868 char file[PATH_MAX];
7870 set_hook((void *)&_soup_cookie_jar_add_cookie,
7871 "soup_cookie_jar_add_cookie");
7872 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7873 "soup_cookie_jar_delete_cookie");
7875 if (cookies_enabled == 0)
7876 return;
7879 * the following code is intricate due to overriding several libsoup
7880 * functions.
7881 * do not alter order of these operations.
7884 /* rejected cookies */
7885 if (save_rejected_cookies)
7886 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
7888 /* persistent cookies */
7889 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
7890 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7892 /* session cookies */
7893 s_cookiejar = soup_cookie_jar_new();
7894 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7895 cookie_policy, (void *)NULL);
7896 transfer_cookies();
7898 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7901 void
7902 setup_proxy(char *uri)
7904 if (proxy_uri) {
7905 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7906 soup_uri_free(proxy_uri);
7907 proxy_uri = NULL;
7909 if (http_proxy) {
7910 if (http_proxy != uri) {
7911 g_free(http_proxy);
7912 http_proxy = NULL;
7916 if (uri) {
7917 http_proxy = g_strdup(uri);
7918 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7919 proxy_uri = soup_uri_new(http_proxy);
7920 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7925 send_cmd_to_socket(char *cmd)
7927 int s, len, rv = 1;
7928 struct sockaddr_un sa;
7930 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7931 warnx("%s: socket", __func__);
7932 return (rv);
7935 sa.sun_family = AF_UNIX;
7936 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7937 work_dir, XT_SOCKET_FILE);
7938 len = SUN_LEN(&sa);
7940 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7941 warnx("%s: connect", __func__);
7942 goto done;
7945 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7946 warnx("%s: send", __func__);
7947 goto done;
7950 rv = 0;
7951 done:
7952 close(s);
7953 return (rv);
7956 void
7957 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7959 int s, n;
7960 char str[XT_MAX_URL_LENGTH];
7961 socklen_t t = sizeof(struct sockaddr_un);
7962 struct sockaddr_un sa;
7963 struct passwd *p;
7964 uid_t uid;
7965 gid_t gid;
7966 struct tab *tt;
7968 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7969 warn("accept");
7970 return;
7973 if (getpeereid(s, &uid, &gid) == -1) {
7974 warn("getpeereid");
7975 return;
7977 if (uid != getuid() || gid != getgid()) {
7978 warnx("unauthorized user");
7979 return;
7982 p = getpwuid(uid);
7983 if (p == NULL) {
7984 warnx("not a valid user");
7985 return;
7988 n = recv(s, str, sizeof(str), 0);
7989 if (n <= 0)
7990 return;
7992 tt = TAILQ_LAST(&tabs, tab_list);
7993 cmd_execute(tt, str);
7997 is_running(void)
7999 int s, len, rv = 1;
8000 struct sockaddr_un sa;
8002 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8003 warn("is_running: socket");
8004 return (-1);
8007 sa.sun_family = AF_UNIX;
8008 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8009 work_dir, XT_SOCKET_FILE);
8010 len = SUN_LEN(&sa);
8012 /* connect to see if there is a listener */
8013 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8014 rv = 0; /* not running */
8015 else
8016 rv = 1; /* already running */
8018 close(s);
8020 return (rv);
8024 build_socket(void)
8026 int s, len;
8027 struct sockaddr_un sa;
8029 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8030 warn("build_socket: socket");
8031 return (-1);
8034 sa.sun_family = AF_UNIX;
8035 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8036 work_dir, XT_SOCKET_FILE);
8037 len = SUN_LEN(&sa);
8039 /* connect to see if there is a listener */
8040 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8041 /* no listener so we will */
8042 unlink(sa.sun_path);
8044 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8045 warn("build_socket: bind");
8046 goto done;
8049 if (listen(s, 1) == -1) {
8050 warn("build_socket: listen");
8051 goto done;
8054 return (s);
8057 done:
8058 close(s);
8059 return (-1);
8062 gboolean
8063 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8064 GtkTreeIter *iter, struct tab *t)
8066 gchar *value;
8068 gtk_tree_model_get(model, iter, 0, &value, -1);
8069 load_uri(t, value);
8070 g_free(value);
8072 return (FALSE);
8075 gboolean
8076 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8077 GtkTreeIter *iter, struct tab *t)
8079 gchar *value;
8081 gtk_tree_model_get(model, iter, 0, &value, -1);
8082 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8083 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8084 g_free(value);
8086 return (TRUE);
8089 void
8090 completion_add_uri(const gchar *uri)
8092 GtkTreeIter iter;
8094 /* add uri to list_store */
8095 gtk_list_store_append(completion_model, &iter);
8096 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8099 gboolean
8100 completion_match(GtkEntryCompletion *completion, const gchar *key,
8101 GtkTreeIter *iter, gpointer user_data)
8103 gchar *value;
8104 gboolean match = FALSE;
8106 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8107 -1);
8109 if (value == NULL)
8110 return FALSE;
8112 match = match_uri(value, key);
8114 g_free(value);
8115 return (match);
8118 void
8119 completion_add(struct tab *t)
8121 /* enable completion for tab */
8122 t->completion = gtk_entry_completion_new();
8123 gtk_entry_completion_set_text_column(t->completion, 0);
8124 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8125 gtk_entry_completion_set_model(t->completion,
8126 GTK_TREE_MODEL(completion_model));
8127 gtk_entry_completion_set_match_func(t->completion, completion_match,
8128 NULL, NULL);
8129 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8130 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8131 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8132 G_CALLBACK(completion_select_cb), t);
8133 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8134 G_CALLBACK(completion_hover_cb), t);
8137 void
8138 usage(void)
8140 fprintf(stderr,
8141 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8142 exit(0);
8146 main(int argc, char *argv[])
8148 struct stat sb;
8149 int c, s, optn = 0, opte = 0, focus = 1;
8150 char conf[PATH_MAX] = { '\0' };
8151 char file[PATH_MAX];
8152 char *env_proxy = NULL;
8153 FILE *f = NULL;
8154 struct karg a;
8155 struct sigaction sact;
8156 gchar *priority = g_strdup("NORMAL");
8158 start_argv = argv;
8160 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8162 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8163 switch (c) {
8164 case 'S':
8165 show_url = 0;
8166 break;
8167 case 'T':
8168 show_tabs = 0;
8169 break;
8170 case 'V':
8171 errx(0 , "Version: %s", version);
8172 break;
8173 case 'f':
8174 strlcpy(conf, optarg, sizeof(conf));
8175 break;
8176 case 's':
8177 strlcpy(named_session, optarg, sizeof(named_session));
8178 break;
8179 case 't':
8180 tabless = 1;
8181 break;
8182 case 'n':
8183 optn = 1;
8184 break;
8185 case 'e':
8186 opte = 1;
8187 break;
8188 default:
8189 usage();
8190 /* NOTREACHED */
8193 argc -= optind;
8194 argv += optind;
8196 RB_INIT(&hl);
8197 RB_INIT(&js_wl);
8198 RB_INIT(&downloads);
8200 TAILQ_INIT(&tabs);
8201 TAILQ_INIT(&mtl);
8202 TAILQ_INIT(&aliases);
8203 TAILQ_INIT(&undos);
8204 TAILQ_INIT(&kbl);
8206 init_keybindings();
8208 gnutls_global_init();
8210 /* generate session keys for xtp pages */
8211 generate_xtp_session_key(&dl_session_key);
8212 generate_xtp_session_key(&hl_session_key);
8213 generate_xtp_session_key(&cl_session_key);
8214 generate_xtp_session_key(&fl_session_key);
8216 /* prepare gtk */
8217 gtk_init(&argc, &argv);
8218 if (!g_thread_supported())
8219 g_thread_init(NULL);
8221 /* signals */
8222 bzero(&sact, sizeof(sact));
8223 sigemptyset(&sact.sa_mask);
8224 sact.sa_handler = sigchild;
8225 sact.sa_flags = SA_NOCLDSTOP;
8226 sigaction(SIGCHLD, &sact, NULL);
8228 /* set download dir */
8229 pwd = getpwuid(getuid());
8230 if (pwd == NULL)
8231 errx(1, "invalid user %d", getuid());
8232 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8234 /* set default string settings */
8235 home = g_strdup("https://www.cyphertite.com");
8236 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8237 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8238 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8240 /* read config file */
8241 if (strlen(conf) == 0)
8242 snprintf(conf, sizeof conf, "%s/.%s",
8243 pwd->pw_dir, XT_CONF_FILE);
8244 config_parse(conf, 0);
8246 /* working directory */
8247 if (strlen(work_dir) == 0)
8248 snprintf(work_dir, sizeof work_dir, "%s/%s",
8249 pwd->pw_dir, XT_DIR);
8250 if (stat(work_dir, &sb)) {
8251 if (mkdir(work_dir, S_IRWXU) == -1)
8252 err(1, "mkdir work_dir");
8253 if (stat(work_dir, &sb))
8254 err(1, "stat work_dir");
8256 if (S_ISDIR(sb.st_mode) == 0)
8257 errx(1, "%s not a dir", work_dir);
8258 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8259 warnx("fixing invalid permissions on %s", work_dir);
8260 if (chmod(work_dir, S_IRWXU) == -1)
8261 err(1, "chmod");
8264 /* icon cache dir */
8265 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8266 if (stat(cache_dir, &sb)) {
8267 if (mkdir(cache_dir, S_IRWXU) == -1)
8268 err(1, "mkdir cache_dir");
8269 if (stat(cache_dir, &sb))
8270 err(1, "stat cache_dir");
8272 if (S_ISDIR(sb.st_mode) == 0)
8273 errx(1, "%s not a dir", cache_dir);
8274 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8275 warnx("fixing invalid permissions on %s", cache_dir);
8276 if (chmod(cache_dir, S_IRWXU) == -1)
8277 err(1, "chmod");
8280 /* certs dir */
8281 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8282 if (stat(certs_dir, &sb)) {
8283 if (mkdir(certs_dir, S_IRWXU) == -1)
8284 err(1, "mkdir certs_dir");
8285 if (stat(certs_dir, &sb))
8286 err(1, "stat certs_dir");
8288 if (S_ISDIR(sb.st_mode) == 0)
8289 errx(1, "%s not a dir", certs_dir);
8290 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8291 warnx("fixing invalid permissions on %s", certs_dir);
8292 if (chmod(certs_dir, S_IRWXU) == -1)
8293 err(1, "chmod");
8296 /* sessions dir */
8297 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8298 work_dir, XT_SESSIONS_DIR);
8299 if (stat(sessions_dir, &sb)) {
8300 if (mkdir(sessions_dir, S_IRWXU) == -1)
8301 err(1, "mkdir sessions_dir");
8302 if (stat(sessions_dir, &sb))
8303 err(1, "stat sessions_dir");
8305 if (S_ISDIR(sb.st_mode) == 0)
8306 errx(1, "%s not a dir", sessions_dir);
8307 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8308 warnx("fixing invalid permissions on %s", sessions_dir);
8309 if (chmod(sessions_dir, S_IRWXU) == -1)
8310 err(1, "chmod");
8312 /* runtime settings that can override config file */
8313 if (runtime_settings[0] != '\0')
8314 config_parse(runtime_settings, 1);
8316 /* download dir */
8317 if (!strcmp(download_dir, pwd->pw_dir))
8318 strlcat(download_dir, "/downloads", sizeof download_dir);
8319 if (stat(download_dir, &sb)) {
8320 if (mkdir(download_dir, S_IRWXU) == -1)
8321 err(1, "mkdir download_dir");
8322 if (stat(download_dir, &sb))
8323 err(1, "stat download_dir");
8325 if (S_ISDIR(sb.st_mode) == 0)
8326 errx(1, "%s not a dir", download_dir);
8327 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8328 warnx("fixing invalid permissions on %s", download_dir);
8329 if (chmod(download_dir, S_IRWXU) == -1)
8330 err(1, "chmod");
8333 /* favorites file */
8334 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8335 if (stat(file, &sb)) {
8336 warnx("favorites file doesn't exist, creating it");
8337 if ((f = fopen(file, "w")) == NULL)
8338 err(1, "favorites");
8339 fclose(f);
8342 /* cookies */
8343 session = webkit_get_default_session();
8344 /* XXX ssl-priority property not quite available yet */
8345 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8346 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8347 (char *)NULL);
8348 else
8349 warnx("session does not have \"ssl-priority\" property");
8350 setup_cookies();
8352 /* certs */
8353 if (ssl_ca_file) {
8354 if (stat(ssl_ca_file, &sb)) {
8355 warn("no CA file: %s", ssl_ca_file);
8356 g_free(ssl_ca_file);
8357 ssl_ca_file = NULL;
8358 } else
8359 g_object_set(session,
8360 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8361 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8362 (void *)NULL);
8365 /* proxy */
8366 env_proxy = getenv("http_proxy");
8367 if (env_proxy)
8368 setup_proxy(env_proxy);
8369 else
8370 setup_proxy(http_proxy);
8372 if (opte) {
8373 send_cmd_to_socket(argv[0]);
8374 exit(0);
8377 /* set some connection parameters */
8378 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8379 g_object_set(session, "max-conns-per-host", max_host_connections,
8380 (char *)NULL);
8382 /* see if there is already an xxxterm running */
8383 if (single_instance && is_running()) {
8384 optn = 1;
8385 warnx("already running");
8388 char *cmd = NULL;
8389 if (optn) {
8390 while (argc) {
8391 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8392 send_cmd_to_socket(cmd);
8393 if (cmd)
8394 g_free(cmd);
8396 argc--;
8397 argv++;
8399 exit(0);
8402 /* uri completion */
8403 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8405 /* go graphical */
8406 create_canvas();
8408 if (save_global_history)
8409 restore_global_history();
8411 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8412 restore_saved_tabs();
8413 else {
8414 a.s = named_session;
8415 a.i = XT_SES_DONOTHING;
8416 open_tabs(NULL, &a);
8419 while (argc) {
8420 create_new_tab(argv[0], NULL, focus);
8421 focus = 0;
8423 argc--;
8424 argv++;
8427 if (TAILQ_EMPTY(&tabs))
8428 create_new_tab(home, NULL, 1);
8430 if (enable_socket)
8431 if ((s = build_socket()) != -1)
8432 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8434 gtk_main();
8436 gnutls_global_deinit();
8438 return (0);