Clipboards on x are stupid. Even more so when combined with gtk *and*
[xxxterm.git] / xxxterm.c
blobf59def3bddb711f4916bd73537b3f9387ff1160e
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <err.h>
34 #include <pwd.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pthread.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <libgen.h>
42 #include <ctype.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/stat.h>
59 #include <sys/socket.h>
60 #include <sys/un.h>
62 #include <gtk/gtk.h>
63 #include <gdk/gdkkeysyms.h>
65 #if GTK_CHECK_VERSION(3,0,0)
66 /* we still use GDK_* instead of GDK_KEY_* */
67 #include <gdk/gdkkeysyms-compat.h>
68 #endif
70 #include <webkit/webkit.h>
71 #include <libsoup/soup.h>
72 #include <gnutls/gnutls.h>
73 #include <JavaScriptCore/JavaScript.h>
74 #include <gnutls/x509.h>
76 #include "javascript.h"
79 javascript.h borrowed from vimprobable2 under the following license:
81 Copyright (c) 2009 Leon Winter
82 Copyright (c) 2009 Hannes Schueller
83 Copyright (c) 2009 Matto Fransen
85 Permission is hereby granted, free of charge, to any person obtaining a copy
86 of this software and associated documentation files (the "Software"), to deal
87 in the Software without restriction, including without limitation the rights
88 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
89 copies of the Software, and to permit persons to whom the Software is
90 furnished to do so, subject to the following conditions:
92 The above copyright notice and this permission notice shall be included in
93 all copies or substantial portions of the Software.
95 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
96 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
97 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
98 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
99 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
100 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
101 THE SOFTWARE.
104 static char *version = "$xxxterm$";
106 /* hooked functions */
107 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
108 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
109 SoupCookie *);
111 /*#define XT_DEBUG*/
112 #ifdef XT_DEBUG
113 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
114 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
115 #define XT_D_MOVE 0x0001
116 #define XT_D_KEY 0x0002
117 #define XT_D_TAB 0x0004
118 #define XT_D_URL 0x0008
119 #define XT_D_CMD 0x0010
120 #define XT_D_NAV 0x0020
121 #define XT_D_DOWNLOAD 0x0040
122 #define XT_D_CONFIG 0x0080
123 #define XT_D_JS 0x0100
124 #define XT_D_FAVORITE 0x0200
125 #define XT_D_PRINTING 0x0400
126 #define XT_D_COOKIE 0x0800
127 #define XT_D_KEYBINDING 0x1000
128 #define XT_D_CLIP 0x2000
129 u_int32_t swm_debug = 0
130 | XT_D_MOVE
131 | XT_D_KEY
132 | XT_D_TAB
133 | XT_D_URL
134 | XT_D_CMD
135 | XT_D_NAV
136 | XT_D_DOWNLOAD
137 | XT_D_CONFIG
138 | XT_D_JS
139 | XT_D_FAVORITE
140 | XT_D_PRINTING
141 | XT_D_COOKIE
142 | XT_D_KEYBINDING
143 | XT_D_CLIP
145 #else
146 #define DPRINTF(x...)
147 #define DNPRINTF(n,x...)
148 #endif
150 #define LENGTH(x) (sizeof x / sizeof x[0])
151 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
152 ~(GDK_BUTTON1_MASK) & \
153 ~(GDK_BUTTON2_MASK) & \
154 ~(GDK_BUTTON3_MASK) & \
155 ~(GDK_BUTTON4_MASK) & \
156 ~(GDK_BUTTON5_MASK))
158 char *icons[] = {
159 "xxxtermicon16.png",
160 "xxxtermicon32.png",
161 "xxxtermicon48.png",
162 "xxxtermicon64.png",
163 "xxxtermicon128.png"
166 struct tab {
167 TAILQ_ENTRY(tab) entry;
168 GtkWidget *vbox;
169 GtkWidget *tab_content;
170 GtkWidget *label;
171 GtkWidget *spinner;
172 GtkWidget *uri_entry;
173 GtkWidget *search_entry;
174 GtkWidget *toolbar;
175 GtkWidget *browser_win;
176 GtkWidget *statusbar;
177 GtkWidget *cmd;
178 GtkWidget *oops;
179 GtkWidget *backward;
180 GtkWidget *forward;
181 GtkWidget *stop;
182 GtkWidget *gohome;
183 GtkWidget *js_toggle;
184 GtkEntryCompletion *completion;
185 guint tab_id;
186 WebKitWebView *wv;
188 WebKitWebHistoryItem *item;
189 WebKitWebBackForwardList *bfl;
191 /* favicon */
192 WebKitNetworkRequest *icon_request;
193 WebKitDownload *icon_download;
194 GdkPixbuf *icon_pixbuf;
195 gchar *icon_dest_uri;
197 /* adjustments for browser */
198 GtkScrollbar *sb_h;
199 GtkScrollbar *sb_v;
200 GtkAdjustment *adjust_h;
201 GtkAdjustment *adjust_v;
203 /* flags */
204 int focus_wv;
205 int ctrl_click;
206 gchar *status;
207 int xtp_meaning; /* identifies dls/favorites */
209 /* hints */
210 int hints_on;
211 int hint_mode;
212 #define XT_HINT_NONE (0)
213 #define XT_HINT_NUMERICAL (1)
214 #define XT_HINT_ALPHANUM (2)
215 char hint_buf[128];
216 char hint_num[128];
218 /* custom stylesheet */
219 int styled;
220 char *stylesheet;
222 /* search */
223 char *search_text;
224 int search_forward;
226 /* settings */
227 WebKitWebSettings *settings;
228 int font_size;
229 gchar *user_agent;
231 TAILQ_HEAD(tab_list, tab);
233 struct history {
234 RB_ENTRY(history) entry;
235 const gchar *uri;
236 const gchar *title;
238 RB_HEAD(history_list, history);
240 struct download {
241 RB_ENTRY(download) entry;
242 int id;
243 WebKitDownload *download;
244 struct tab *tab;
246 RB_HEAD(download_list, download);
248 struct domain {
249 RB_ENTRY(domain) entry;
250 gchar *d;
251 int handy; /* app use */
253 RB_HEAD(domain_list, domain);
255 struct undo {
256 TAILQ_ENTRY(undo) entry;
257 gchar *uri;
258 GList *history;
259 int back; /* Keeps track of how many back
260 * history items there are. */
262 TAILQ_HEAD(undo_tailq, undo);
264 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
265 int next_download_id = 1;
267 struct karg {
268 int i;
269 char *s;
270 int p;
273 /* defines */
274 #define XT_NAME ("XXXTerm")
275 #define XT_DIR (".xxxterm")
276 #define XT_CACHE_DIR ("cache")
277 #define XT_CERT_DIR ("certs/")
278 #define XT_SESSIONS_DIR ("sessions/")
279 #define XT_CONF_FILE ("xxxterm.conf")
280 #define XT_FAVS_FILE ("favorites")
281 #define XT_SAVED_TABS_FILE ("main_session")
282 #define XT_RESTART_TABS_FILE ("restart_tabs")
283 #define XT_SOCKET_FILE ("socket")
284 #define XT_HISTORY_FILE ("history")
285 #define XT_REJECT_FILE ("rejected.txt")
286 #define XT_COOKIE_FILE ("cookies.txt")
287 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
288 #define XT_CB_HANDLED (TRUE)
289 #define XT_CB_PASSTHROUGH (FALSE)
290 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
291 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
292 #define XT_DLMAN_REFRESH "10"
293 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
294 "td{overflow: hidden;" \
295 " padding: 2px 2px 2px 2px;" \
296 " border: 1px solid black;" \
297 " vertical-align:top;" \
298 " word-wrap: break-word}\n" \
299 "tr:hover{background: #ffff99}\n" \
300 "th{background-color: #cccccc;" \
301 " border: 1px solid black}\n" \
302 "table{width: 100%%;" \
303 " border: 1px black solid;" \
304 " border-collapse:collapse}\n" \
305 ".progress-outer{" \
306 "border: 1px solid black;" \
307 " height: 8px;" \
308 " width: 90%%}\n" \
309 ".progress-inner{float: left;" \
310 " height: 8px;" \
311 " background: green}\n" \
312 ".dlstatus{font-size: small;" \
313 " text-align: center}\n" \
314 "</style>\n"
315 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
316 #define XT_MAX_UNDO_CLOSE_TAB (32)
317 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
318 #define XT_PRINT_EXTRA_MARGIN 10
320 /* colors */
321 #define XT_COLOR_RED "#cc0000"
322 #define XT_COLOR_YELLOW "#ffff66"
323 #define XT_COLOR_BLUE "lightblue"
324 #define XT_COLOR_GREEN "#99ff66"
325 #define XT_COLOR_WHITE "white"
326 #define XT_COLOR_BLACK "black"
329 * xxxterm "protocol" (xtp)
330 * We use this for managing stuff like downloads and favorites. They
331 * make magical HTML pages in memory which have xxxt:// links in order
332 * to communicate with xxxterm's internals. These links take the format:
333 * xxxt://class/session_key/action/arg
335 * Don't begin xtp class/actions as 0. atoi returns that on error.
337 * Typically we have not put addition of items in this framework, as
338 * adding items is either done via an ex-command or via a keybinding instead.
341 #define XT_XTP_STR "xxxt://"
343 /* XTP classes (xxxt://<class>) */
344 #define XT_XTP_INVALID 0 /* invalid */
345 #define XT_XTP_DL 1 /* downloads */
346 #define XT_XTP_HL 2 /* history */
347 #define XT_XTP_CL 3 /* cookies */
348 #define XT_XTP_FL 4 /* favorites */
350 /* XTP download actions */
351 #define XT_XTP_DL_LIST 1
352 #define XT_XTP_DL_CANCEL 2
353 #define XT_XTP_DL_REMOVE 3
355 /* XTP history actions */
356 #define XT_XTP_HL_LIST 1
357 #define XT_XTP_HL_REMOVE 2
359 /* XTP cookie actions */
360 #define XT_XTP_CL_LIST 1
361 #define XT_XTP_CL_REMOVE 2
363 /* XTP cookie actions */
364 #define XT_XTP_FL_LIST 1
365 #define XT_XTP_FL_REMOVE 2
367 /* xtp tab meanings - identifies which tabs have xtp pages in */
368 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
369 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
370 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
371 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
372 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
374 /* actions */
375 #define XT_MOVE_INVALID (0)
376 #define XT_MOVE_DOWN (1)
377 #define XT_MOVE_UP (2)
378 #define XT_MOVE_BOTTOM (3)
379 #define XT_MOVE_TOP (4)
380 #define XT_MOVE_PAGEDOWN (5)
381 #define XT_MOVE_PAGEUP (6)
382 #define XT_MOVE_HALFDOWN (7)
383 #define XT_MOVE_HALFUP (8)
384 #define XT_MOVE_LEFT (9)
385 #define XT_MOVE_FARLEFT (10)
386 #define XT_MOVE_RIGHT (11)
387 #define XT_MOVE_FARRIGHT (12)
389 #define XT_TAB_LAST (-4)
390 #define XT_TAB_FIRST (-3)
391 #define XT_TAB_PREV (-2)
392 #define XT_TAB_NEXT (-1)
393 #define XT_TAB_INVALID (0)
394 #define XT_TAB_NEW (1)
395 #define XT_TAB_DELETE (2)
396 #define XT_TAB_DELQUIT (3)
397 #define XT_TAB_OPEN (4)
398 #define XT_TAB_UNDO_CLOSE (5)
399 #define XT_TAB_SHOW (6)
400 #define XT_TAB_HIDE (7)
402 #define XT_NAV_INVALID (0)
403 #define XT_NAV_BACK (1)
404 #define XT_NAV_FORWARD (2)
405 #define XT_NAV_RELOAD (3)
406 #define XT_NAV_RELOAD_CACHE (4)
408 #define XT_FOCUS_INVALID (0)
409 #define XT_FOCUS_URI (1)
410 #define XT_FOCUS_SEARCH (2)
412 #define XT_SEARCH_INVALID (0)
413 #define XT_SEARCH_NEXT (1)
414 #define XT_SEARCH_PREV (2)
416 #define XT_PASTE_CURRENT_TAB (0)
417 #define XT_PASTE_NEW_TAB (1)
419 #define XT_FONT_SET (0)
421 #define XT_URL_SHOW (1)
422 #define XT_URL_HIDE (2)
424 #define XT_STATUSBAR_SHOW (1)
425 #define XT_STATUSBAR_HIDE (2)
427 #define XT_WL_TOGGLE (1<<0)
428 #define XT_WL_ENABLE (1<<1)
429 #define XT_WL_DISABLE (1<<2)
430 #define XT_WL_FQDN (1<<3) /* default */
431 #define XT_WL_TOPLEVEL (1<<4)
432 #define XT_WL_PERSISTENT (1<<5)
433 #define XT_WL_SESSION (1<<6)
435 #define XT_SHOW (1<<7)
436 #define XT_DELETE (1<<8)
437 #define XT_SAVE (1<<9)
438 #define XT_OPEN (1<<10)
440 #define XT_CMD_OPEN (0)
441 #define XT_CMD_OPEN_CURRENT (1)
442 #define XT_CMD_TABNEW (2)
443 #define XT_CMD_TABNEW_CURRENT (3)
445 #define XT_STATUS_NOTHING (0)
446 #define XT_STATUS_LINK (1)
447 #define XT_STATUS_URI (2)
448 #define XT_STATUS_LOADING (3)
450 #define XT_SES_DONOTHING (0)
451 #define XT_SES_CLOSETABS (1)
453 #define XT_BM_NORMAL (0)
454 #define XT_BM_WHITELIST (1)
455 #define XT_BM_KIOSK (2)
457 #define XT_PREFIX (1<<0)
458 #define XT_USERARG (1<<1)
459 #define XT_URLARG (1<<2)
460 #define XT_INTARG (1<<3)
462 /* mime types */
463 struct mime_type {
464 char *mt_type;
465 char *mt_action;
466 int mt_default;
467 int mt_download;
468 TAILQ_ENTRY(mime_type) entry;
470 TAILQ_HEAD(mime_type_list, mime_type);
472 /* uri aliases */
473 struct alias {
474 char *a_name;
475 char *a_uri;
476 TAILQ_ENTRY(alias) entry;
478 TAILQ_HEAD(alias_list, alias);
480 /* settings that require restart */
481 int tabless = 0; /* allow only 1 tab */
482 int enable_socket = 0;
483 int single_instance = 0; /* only allow one xxxterm to run */
484 int fancy_bar = 1; /* fancy toolbar */
485 int browser_mode = XT_BM_NORMAL;
487 /* runtime settings */
488 int show_tabs = 1; /* show tabs on notebook */
489 int show_url = 1; /* show url toolbar on notebook */
490 int show_statusbar = 0; /* vimperator style status bar */
491 int ctrl_click_focus = 0; /* ctrl click gets focus */
492 int cookies_enabled = 1; /* enable cookies */
493 int read_only_cookies = 0; /* enable to not write cookies */
494 int enable_scripts = 1;
495 int enable_plugins = 0;
496 int default_font_size = 12;
497 gfloat default_zoom_level = 1.0;
498 int window_height = 768;
499 int window_width = 1024;
500 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
501 unsigned refresh_interval = 10; /* download refresh interval */
502 int enable_cookie_whitelist = 0;
503 int enable_js_whitelist = 0;
504 time_t session_timeout = 3600; /* cookie session timeout */
505 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
506 char *ssl_ca_file = NULL;
507 char *resource_dir = NULL;
508 gboolean ssl_strict_certs = FALSE;
509 int append_next = 1; /* append tab after current tab */
510 char *home = NULL;
511 char *search_string = NULL;
512 char *http_proxy = NULL;
513 char download_dir[PATH_MAX];
514 char runtime_settings[PATH_MAX]; /* override of settings */
515 int allow_volatile_cookies = 0;
516 int save_global_history = 0; /* save global history to disk */
517 char *user_agent = NULL;
518 int save_rejected_cookies = 0;
519 time_t session_autosave = 0;
520 int guess_search = 0;
521 int dns_prefetch = FALSE;
522 gint max_connections = 25;
523 gint max_host_connections = 5;
524 gint enable_spell_checking = 0;
525 char *spell_check_languages = NULL;
527 struct settings;
528 struct key_binding;
529 int set_download_dir(struct settings *, char *);
530 int set_work_dir(struct settings *, char *);
531 int set_runtime_dir(struct settings *, char *);
532 int set_browser_mode(struct settings *, char *);
533 int set_cookie_policy(struct settings *, char *);
534 int add_alias(struct settings *, char *);
535 int add_mime_type(struct settings *, char *);
536 int add_cookie_wl(struct settings *, char *);
537 int add_js_wl(struct settings *, char *);
538 int add_kb(struct settings *, char *);
539 void button_set_stockid(GtkWidget *, char *);
540 GtkWidget * create_button(char *, char *, int);
542 char *get_browser_mode(struct settings *);
543 char *get_cookie_policy(struct settings *);
545 char *get_download_dir(struct settings *);
546 char *get_work_dir(struct settings *);
547 char *get_runtime_dir(struct settings *);
549 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
550 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
551 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
552 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
553 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
555 void recalc_tabs(void);
557 struct special {
558 int (*set)(struct settings *, char *);
559 char *(*get)(struct settings *);
560 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
563 struct special s_browser_mode = {
564 set_browser_mode,
565 get_browser_mode,
566 NULL
569 struct special s_cookie = {
570 set_cookie_policy,
571 get_cookie_policy,
572 NULL
575 struct special s_alias = {
576 add_alias,
577 NULL,
578 walk_alias
581 struct special s_mime = {
582 add_mime_type,
583 NULL,
584 walk_mime_type
587 struct special s_js = {
588 add_js_wl,
589 NULL,
590 walk_js_wl
593 struct special s_kb = {
594 add_kb,
595 NULL,
596 walk_kb
599 struct special s_cookie_wl = {
600 add_cookie_wl,
601 NULL,
602 walk_cookie_wl
605 struct special s_download_dir = {
606 set_download_dir,
607 get_download_dir,
608 NULL
611 struct special s_work_dir = {
612 set_work_dir,
613 get_work_dir,
614 NULL
617 struct settings {
618 char *name;
619 int type;
620 #define XT_S_INVALID (0)
621 #define XT_S_INT (1)
622 #define XT_S_STR (2)
623 #define XT_S_FLOAT (3)
624 uint32_t flags;
625 #define XT_SF_RESTART (1<<0)
626 #define XT_SF_RUNTIME (1<<1)
627 int *ival;
628 char **sval;
629 struct special *s;
630 gfloat *fval;
631 } rs[] = {
632 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
633 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
634 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
635 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
636 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
637 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
638 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
639 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
640 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
641 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
642 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
643 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
644 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
645 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
646 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
647 { "home", XT_S_STR, 0, NULL, &home, NULL },
648 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
649 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
650 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
651 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
652 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
653 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
654 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
655 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
656 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
657 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
658 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
659 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
660 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
661 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
662 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
663 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
664 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
665 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
666 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
667 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
668 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
669 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
670 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
671 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
672 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
674 /* runtime settings */
675 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
676 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
677 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
678 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
679 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
682 int about(struct tab *, struct karg *);
683 int blank(struct tab *, struct karg *);
684 int ca_cmd(struct tab *, struct karg *);
685 int cookie_show_wl(struct tab *, struct karg *);
686 int js_show_wl(struct tab *, struct karg *);
687 int help(struct tab *, struct karg *);
688 int set(struct tab *, struct karg *);
689 int stats(struct tab *, struct karg *);
690 int marco(struct tab *, struct karg *);
691 const char * marco_message(int *);
692 int xtp_page_cl(struct tab *, struct karg *);
693 int xtp_page_dl(struct tab *, struct karg *);
694 int xtp_page_fl(struct tab *, struct karg *);
695 int xtp_page_hl(struct tab *, struct karg *);
697 #define XT_URI_ABOUT ("about:")
698 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
699 #define XT_URI_ABOUT_ABOUT ("about")
700 #define XT_URI_ABOUT_BLANK ("blank")
701 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
702 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
703 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
704 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
705 #define XT_URI_ABOUT_FAVORITES ("favorites")
706 #define XT_URI_ABOUT_HELP ("help")
707 #define XT_URI_ABOUT_HISTORY ("history")
708 #define XT_URI_ABOUT_JSWL ("jswl")
709 #define XT_URI_ABOUT_SET ("set")
710 #define XT_URI_ABOUT_STATS ("stats")
711 #define XT_URI_ABOUT_MARCO ("marco")
713 struct about_type {
714 char *name;
715 int (*func)(struct tab *, struct karg *);
716 } about_list[] = {
717 { XT_URI_ABOUT_ABOUT, about },
718 { XT_URI_ABOUT_BLANK, blank },
719 { XT_URI_ABOUT_CERTS, ca_cmd },
720 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
721 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
722 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
723 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
724 { XT_URI_ABOUT_HELP, help },
725 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
726 { XT_URI_ABOUT_JSWL, js_show_wl },
727 { XT_URI_ABOUT_SET, set },
728 { XT_URI_ABOUT_STATS, stats },
729 { XT_URI_ABOUT_MARCO, marco },
732 /* globals */
733 extern char *__progname;
734 char **start_argv;
735 struct passwd *pwd;
736 GtkWidget *main_window;
737 GtkNotebook *notebook;
738 GtkWidget *arrow, *abtn;
739 struct tab_list tabs;
740 struct history_list hl;
741 struct download_list downloads;
742 struct domain_list c_wl;
743 struct domain_list js_wl;
744 struct undo_tailq undos;
745 struct keybinding_list kbl;
746 int undo_count;
747 int updating_dl_tabs = 0;
748 int updating_hl_tabs = 0;
749 int updating_cl_tabs = 0;
750 int updating_fl_tabs = 0;
751 char *global_search;
752 uint64_t blocked_cookies = 0;
753 char named_session[PATH_MAX];
754 void update_favicon(struct tab *);
755 int icon_size_map(int);
757 GtkListStore *completion_model;
758 void completion_add(struct tab *);
759 void completion_add_uri(const gchar *);
760 void xxx_dir(char *);
762 void
763 sigchild(int sig)
765 int saved_errno, status;
766 pid_t pid;
768 saved_errno = errno;
770 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
771 if (pid == -1) {
772 if (errno == EINTR)
773 continue;
774 if (errno != ECHILD) {
776 clog_warn("sigchild: waitpid:");
779 break;
782 if (WIFEXITED(status)) {
783 if (WEXITSTATUS(status) != 0) {
785 clog_warnx("sigchild: child exit status: %d",
786 WEXITSTATUS(status));
789 } else {
791 clog_warnx("sigchild: child is terminated abnormally");
796 errno = saved_errno;
800 is_g_object_setting(GObject *o, char *str)
802 guint n_props = 0, i;
803 GParamSpec **proplist;
805 if (! G_IS_OBJECT(o))
806 return (0);
808 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
809 &n_props);
811 for (i=0; i < n_props; i++) {
812 if (! strcmp(proplist[i]->name, str))
813 return (1);
815 return (0);
818 gchar *
819 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
821 gchar *r;
823 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
824 "<head>\n"
825 "<title>%s</title>\n"
826 "%s"
827 "%s"
828 "</head>\n"
829 "<body>\n"
830 "<h1>%s</h1>\n"
831 "%s\n</body>\n"
832 "</html>",
833 title,
834 addstyles ? XT_PAGE_STYLE : "",
835 head,
836 title,
837 body);
839 return r;
843 * Display a web page from a HTML string in memory, rather than from a URL
845 void
846 load_webkit_string(struct tab *t, const char *str, gchar *title)
848 gchar *uri;
849 char file[PATH_MAX];
850 GdkPixbuf *pb;
852 /* we set this to indicate we want to manually do navaction */
853 if (t->bfl)
854 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
856 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
857 #if GTK_CHECK_VERSION(2, 20, 0)
858 gtk_spinner_stop(GTK_SPINNER(t->spinner));
859 gtk_widget_hide(t->spinner);
860 #endif
862 if (title) {
863 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
864 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
865 g_free(uri);
867 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
868 pb = gdk_pixbuf_new_from_file(file, NULL);
869 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
870 GTK_ENTRY_ICON_PRIMARY, pb);
871 gdk_pixbuf_unref(pb);
875 void
876 set_status(struct tab *t, gchar *s, int status)
878 gchar *type = NULL;
880 if (s == NULL)
881 return;
883 switch (status) {
884 case XT_STATUS_LOADING:
885 type = g_strdup_printf("Loading: %s", s);
886 s = type;
887 break;
888 case XT_STATUS_LINK:
889 type = g_strdup_printf("Link: %s", s);
890 if (!t->status)
891 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
892 s = type;
893 break;
894 case XT_STATUS_URI:
895 type = g_strdup_printf("%s", s);
896 if (!t->status) {
897 t->status = g_strdup(type);
899 s = type;
900 if (!t->status)
901 t->status = g_strdup(s);
902 break;
903 case XT_STATUS_NOTHING:
904 /* FALL THROUGH */
905 default:
906 break;
908 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
909 if (type)
910 g_free(type);
913 void
914 hide_oops(struct tab *t)
916 gtk_widget_hide(t->oops);
919 void
920 hide_cmd(struct tab *t)
922 gtk_widget_hide(t->cmd);
925 void
926 show_cmd(struct tab *t)
928 gtk_widget_hide(t->oops);
929 gtk_widget_show(t->cmd);
932 void
933 show_oops(struct tab *t, const char *fmt, ...)
935 va_list ap;
936 char *msg;
938 if (fmt == NULL)
939 return;
941 va_start(ap, fmt);
942 if (vasprintf(&msg, fmt, ap) == -1)
943 errx(1, "show_oops failed");
944 va_end(ap);
946 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
947 gtk_widget_hide(t->cmd);
948 gtk_widget_show(t->oops);
951 /* XXX collapse with show_oops */
952 void
953 show_oops_s(const char *fmt, ...)
955 va_list ap;
956 char *msg;
957 struct tab *ti, *t = NULL;
959 if (fmt == NULL)
960 return;
962 TAILQ_FOREACH(ti, &tabs, entry)
963 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
964 t = ti;
965 break;
967 if (t == NULL)
968 return;
970 va_start(ap, fmt);
971 if (vasprintf(&msg, fmt, ap) == -1)
972 errx(1, "show_oops_s failed");
973 va_end(ap);
975 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
976 gtk_widget_hide(t->cmd);
977 gtk_widget_show(t->oops);
980 char *
981 get_as_string(struct settings *s)
983 char *r = NULL;
985 if (s == NULL)
986 return (NULL);
988 if (s->s) {
989 if (s->s->get)
990 r = s->s->get(s);
991 else
992 warnx("get_as_string skip %s\n", s->name);
993 } else if (s->type == XT_S_INT)
994 r = g_strdup_printf("%d", *s->ival);
995 else if (s->type == XT_S_STR)
996 r = g_strdup(*s->sval);
997 else if (s->type == XT_S_FLOAT)
998 r = g_strdup_printf("%f", *s->fval);
999 else
1000 r = g_strdup_printf("INVALID TYPE");
1002 return (r);
1005 void
1006 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1008 int i;
1009 char *s;
1011 for (i = 0; i < LENGTH(rs); i++) {
1012 if (rs[i].s && rs[i].s->walk)
1013 rs[i].s->walk(&rs[i], cb, cb_args);
1014 else {
1015 s = get_as_string(&rs[i]);
1016 cb(&rs[i], s, cb_args);
1017 g_free(s);
1023 set_browser_mode(struct settings *s, char *val)
1025 if (!strcmp(val, "whitelist")) {
1026 browser_mode = XT_BM_WHITELIST;
1027 allow_volatile_cookies = 0;
1028 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1029 cookies_enabled = 1;
1030 enable_cookie_whitelist = 1;
1031 read_only_cookies = 0;
1032 save_rejected_cookies = 0;
1033 session_timeout = 3600;
1034 enable_scripts = 0;
1035 enable_js_whitelist = 1;
1036 } else if (!strcmp(val, "normal")) {
1037 browser_mode = XT_BM_NORMAL;
1038 allow_volatile_cookies = 0;
1039 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1040 cookies_enabled = 1;
1041 enable_cookie_whitelist = 0;
1042 read_only_cookies = 0;
1043 save_rejected_cookies = 0;
1044 session_timeout = 3600;
1045 enable_scripts = 1;
1046 enable_js_whitelist = 0;
1047 } else if (!strcmp(val, "kiosk")) {
1048 browser_mode = XT_BM_KIOSK;
1049 allow_volatile_cookies = 0;
1050 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1051 cookies_enabled = 1;
1052 enable_cookie_whitelist = 0;
1053 read_only_cookies = 0;
1054 save_rejected_cookies = 0;
1055 session_timeout = 3600;
1056 enable_scripts = 1;
1057 enable_js_whitelist = 0;
1058 show_tabs = 0;
1059 tabless = 1;
1060 } else
1061 return (1);
1063 return (0);
1066 char *
1067 get_browser_mode(struct settings *s)
1069 char *r = NULL;
1071 if (browser_mode == XT_BM_WHITELIST)
1072 r = g_strdup("whitelist");
1073 else if (browser_mode == XT_BM_NORMAL)
1074 r = g_strdup("normal");
1075 else if (browser_mode == XT_BM_KIOSK)
1076 r = g_strdup("kiosk");
1077 else
1078 return (NULL);
1080 return (r);
1084 set_cookie_policy(struct settings *s, char *val)
1086 if (!strcmp(val, "no3rdparty"))
1087 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1088 else if (!strcmp(val, "accept"))
1089 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1090 else if (!strcmp(val, "reject"))
1091 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1092 else
1093 return (1);
1095 return (0);
1098 char *
1099 get_cookie_policy(struct settings *s)
1101 char *r = NULL;
1103 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1104 r = g_strdup("no3rdparty");
1105 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1106 r = g_strdup("accept");
1107 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1108 r = g_strdup("reject");
1109 else
1110 return (NULL);
1112 return (r);
1115 char *
1116 get_download_dir(struct settings *s)
1118 if (download_dir[0] == '\0')
1119 return (0);
1120 return (g_strdup(download_dir));
1124 set_download_dir(struct settings *s, char *val)
1126 if (val[0] == '~')
1127 snprintf(download_dir, sizeof download_dir, "%s/%s",
1128 pwd->pw_dir, &val[1]);
1129 else
1130 strlcpy(download_dir, val, sizeof download_dir);
1132 return (0);
1136 * Session IDs.
1137 * We use these to prevent people putting xxxt:// URLs on
1138 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1140 #define XT_XTP_SES_KEY_SZ 8
1141 #define XT_XTP_SES_KEY_HEX_FMT \
1142 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1143 char *dl_session_key; /* downloads */
1144 char *hl_session_key; /* history list */
1145 char *cl_session_key; /* cookie list */
1146 char *fl_session_key; /* favorites list */
1148 char work_dir[PATH_MAX];
1149 char certs_dir[PATH_MAX];
1150 char cache_dir[PATH_MAX];
1151 char sessions_dir[PATH_MAX];
1152 char cookie_file[PATH_MAX];
1153 SoupURI *proxy_uri = NULL;
1154 SoupSession *session;
1155 SoupCookieJar *s_cookiejar;
1156 SoupCookieJar *p_cookiejar;
1157 char rc_fname[PATH_MAX];
1159 struct mime_type_list mtl;
1160 struct alias_list aliases;
1162 /* protos */
1163 struct tab *create_new_tab(char *, struct undo *, int, int);
1164 void delete_tab(struct tab *);
1165 void adjustfont_webkit(struct tab *, int);
1166 int run_script(struct tab *, char *);
1167 int download_rb_cmp(struct download *, struct download *);
1168 gboolean cmd_execute(struct tab *t, char *str);
1171 history_rb_cmp(struct history *h1, struct history *h2)
1173 return (strcmp(h1->uri, h2->uri));
1175 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1178 domain_rb_cmp(struct domain *d1, struct domain *d2)
1180 return (strcmp(d1->d, d2->d));
1182 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1184 char *
1185 get_work_dir(struct settings *s)
1187 if (work_dir[0] == '\0')
1188 return (0);
1189 return (g_strdup(work_dir));
1193 set_work_dir(struct settings *s, char *val)
1195 if (val[0] == '~')
1196 snprintf(work_dir, sizeof work_dir, "%s/%s",
1197 pwd->pw_dir, &val[1]);
1198 else
1199 strlcpy(work_dir, val, sizeof work_dir);
1201 return (0);
1205 * generate a session key to secure xtp commands.
1206 * pass in a ptr to the key in question and it will
1207 * be modified in place.
1209 void
1210 generate_xtp_session_key(char **key)
1212 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1214 /* free old key */
1215 if (*key)
1216 g_free(*key);
1218 /* make a new one */
1219 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1220 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1221 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1222 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1224 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1228 * validate a xtp session key.
1229 * return 1 if OK
1232 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1234 if (strcmp(trusted, untrusted) != 0) {
1235 show_oops(t, "%s: xtp session key mismatch possible spoof",
1236 __func__);
1237 return (0);
1240 return (1);
1244 download_rb_cmp(struct download *e1, struct download *e2)
1246 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1248 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1250 struct valid_url_types {
1251 char *type;
1252 } vut[] = {
1253 { "http://" },
1254 { "https://" },
1255 { "ftp://" },
1256 { "file://" },
1257 { XT_XTP_STR },
1261 valid_url_type(char *url)
1263 int i;
1265 for (i = 0; i < LENGTH(vut); i++)
1266 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1267 return (0);
1269 return (1);
1272 void
1273 print_cookie(char *msg, SoupCookie *c)
1275 if (c == NULL)
1276 return;
1278 if (msg)
1279 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1280 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1281 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1282 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1283 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1284 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1285 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1286 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1287 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1288 DNPRINTF(XT_D_COOKIE, "====================================\n");
1291 void
1292 walk_alias(struct settings *s,
1293 void (*cb)(struct settings *, char *, void *), void *cb_args)
1295 struct alias *a;
1296 char *str;
1298 if (s == NULL || cb == NULL) {
1299 show_oops_s("walk_alias invalid parameters");
1300 return;
1303 TAILQ_FOREACH(a, &aliases, entry) {
1304 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1305 cb(s, str, cb_args);
1306 g_free(str);
1310 char *
1311 match_alias(char *url_in)
1313 struct alias *a;
1314 char *arg;
1315 char *url_out = NULL, *search, *enc_arg;
1317 search = g_strdup(url_in);
1318 arg = search;
1319 if (strsep(&arg, " \t") == NULL) {
1320 show_oops_s("match_alias: NULL URL");
1321 goto done;
1324 TAILQ_FOREACH(a, &aliases, entry) {
1325 if (!strcmp(search, a->a_name))
1326 break;
1329 if (a != NULL) {
1330 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1331 a->a_name);
1332 if (arg != NULL) {
1333 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1334 url_out = g_strdup_printf(a->a_uri, enc_arg);
1335 g_free(enc_arg);
1336 } else
1337 url_out = g_strdup(a->a_uri);
1339 done:
1340 g_free(search);
1341 return (url_out);
1344 char *
1345 guess_url_type(char *url_in)
1347 struct stat sb;
1348 char *url_out = NULL, *enc_search = NULL;
1350 url_out = match_alias(url_in);
1351 if (url_out != NULL)
1352 return (url_out);
1354 if (guess_search) {
1356 * If there is no dot nor slash in the string and it isn't a
1357 * path to a local file and doesn't resolves to an IP, assume
1358 * that the user wants to search for the string.
1361 if (strchr(url_in, '.') == NULL &&
1362 strchr(url_in, '/') == NULL &&
1363 stat(url_in, &sb) != 0 &&
1364 gethostbyname(url_in) == NULL) {
1366 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1367 url_out = g_strdup_printf(search_string, enc_search);
1368 g_free(enc_search);
1369 return (url_out);
1373 /* XXX not sure about this heuristic */
1374 if (stat(url_in, &sb) == 0)
1375 url_out = g_strdup_printf("file://%s", url_in);
1376 else
1377 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1379 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1381 return (url_out);
1384 void
1385 load_uri(struct tab *t, gchar *uri)
1387 struct karg args;
1388 gchar *newuri = NULL;
1389 int i;
1391 if (uri == NULL)
1392 return;
1394 /* Strip leading spaces. */
1395 while(*uri && isspace(*uri))
1396 uri++;
1398 if (strlen(uri) == 0) {
1399 blank(t, NULL);
1400 return;
1403 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1404 for (i = 0; i < LENGTH(about_list); i++)
1405 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1406 bzero(&args, sizeof args);
1407 about_list[i].func(t, &args);
1408 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1409 FALSE);
1410 return;
1412 show_oops(t, "invalid about page");
1413 return;
1416 if (valid_url_type(uri)) {
1417 newuri = guess_url_type(uri);
1418 uri = newuri;
1421 set_status(t, (char *)uri, XT_STATUS_LOADING);
1422 webkit_web_view_load_uri(t->wv, uri);
1424 if (newuri)
1425 g_free(newuri);
1428 const gchar *
1429 get_uri(WebKitWebView *wv)
1431 const gchar *uri;
1433 uri = webkit_web_view_get_uri(wv);
1435 if (uri && strlen(uri) > 0)
1436 return (uri);
1437 else
1438 return (NULL);
1442 add_alias(struct settings *s, char *line)
1444 char *l, *alias;
1445 struct alias *a = NULL;
1447 if (s == NULL || line == NULL) {
1448 show_oops_s("add_alias invalid parameters");
1449 return (1);
1452 l = line;
1453 a = g_malloc(sizeof(*a));
1455 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1456 show_oops_s("add_alias: incomplete alias definition");
1457 goto bad;
1459 if (strlen(alias) == 0 || strlen(l) == 0) {
1460 show_oops_s("add_alias: invalid alias definition");
1461 goto bad;
1464 a->a_name = g_strdup(alias);
1465 a->a_uri = g_strdup(l);
1467 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1469 TAILQ_INSERT_TAIL(&aliases, a, entry);
1471 return (0);
1472 bad:
1473 if (a)
1474 g_free(a);
1475 return (1);
1479 add_mime_type(struct settings *s, char *line)
1481 char *mime_type;
1482 char *l;
1483 struct mime_type *m = NULL;
1484 int downloadfirst = 0;
1486 /* XXX this could be smarter */
1488 if (line == NULL && strlen(line) == 0) {
1489 show_oops_s("add_mime_type invalid parameters");
1490 return (1);
1493 l = line;
1494 if (*l == '@') {
1495 downloadfirst = 1;
1496 l++;
1498 m = g_malloc(sizeof(*m));
1500 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1501 show_oops_s("add_mime_type: invalid mime_type");
1502 goto bad;
1504 if (mime_type[strlen(mime_type) - 1] == '*') {
1505 mime_type[strlen(mime_type) - 1] = '\0';
1506 m->mt_default = 1;
1507 } else
1508 m->mt_default = 0;
1510 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1511 show_oops_s("add_mime_type: invalid mime_type");
1512 goto bad;
1515 m->mt_type = g_strdup(mime_type);
1516 m->mt_action = g_strdup(l);
1517 m->mt_download = downloadfirst;
1519 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1520 m->mt_type, m->mt_action, m->mt_default);
1522 TAILQ_INSERT_TAIL(&mtl, m, entry);
1524 return (0);
1525 bad:
1526 if (m)
1527 g_free(m);
1528 return (1);
1531 struct mime_type *
1532 find_mime_type(char *mime_type)
1534 struct mime_type *m, *def = NULL, *rv = NULL;
1536 TAILQ_FOREACH(m, &mtl, entry) {
1537 if (m->mt_default &&
1538 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1539 def = m;
1541 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1542 rv = m;
1543 break;
1547 if (rv == NULL)
1548 rv = def;
1550 return (rv);
1553 void
1554 walk_mime_type(struct settings *s,
1555 void (*cb)(struct settings *, char *, void *), void *cb_args)
1557 struct mime_type *m;
1558 char *str;
1560 if (s == NULL || cb == NULL)
1561 show_oops_s("walk_mime_type invalid parameters");
1563 TAILQ_FOREACH(m, &mtl, entry) {
1564 str = g_strdup_printf("%s%s --> %s",
1565 m->mt_type,
1566 m->mt_default ? "*" : "",
1567 m->mt_action);
1568 cb(s, str, cb_args);
1569 g_free(str);
1573 void
1574 wl_add(char *str, struct domain_list *wl, int handy)
1576 struct domain *d;
1577 int add_dot = 0;
1579 if (str == NULL || wl == NULL || strlen(str) < 2)
1580 return;
1582 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1584 /* treat *.moo.com the same as .moo.com */
1585 if (str[0] == '*' && str[1] == '.')
1586 str = &str[1];
1587 else if (str[0] == '.')
1588 str = &str[0];
1589 else
1590 add_dot = 1;
1592 d = g_malloc(sizeof *d);
1593 if (add_dot)
1594 d->d = g_strdup_printf(".%s", str);
1595 else
1596 d->d = g_strdup(str);
1597 d->handy = handy;
1599 if (RB_INSERT(domain_list, wl, d))
1600 goto unwind;
1602 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1603 return;
1604 unwind:
1605 if (d) {
1606 if (d->d)
1607 g_free(d->d);
1608 g_free(d);
1613 add_cookie_wl(struct settings *s, char *entry)
1615 wl_add(entry, &c_wl, 1);
1616 return (0);
1619 void
1620 walk_cookie_wl(struct settings *s,
1621 void (*cb)(struct settings *, char *, void *), void *cb_args)
1623 struct domain *d;
1625 if (s == NULL || cb == NULL) {
1626 show_oops_s("walk_cookie_wl invalid parameters");
1627 return;
1630 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1631 cb(s, d->d, cb_args);
1634 void
1635 walk_js_wl(struct settings *s,
1636 void (*cb)(struct settings *, char *, void *), void *cb_args)
1638 struct domain *d;
1640 if (s == NULL || cb == NULL) {
1641 show_oops_s("walk_js_wl invalid parameters");
1642 return;
1645 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1646 cb(s, d->d, cb_args);
1650 add_js_wl(struct settings *s, char *entry)
1652 wl_add(entry, &js_wl, 1 /* persistent */);
1653 return (0);
1656 struct domain *
1657 wl_find(const gchar *search, struct domain_list *wl)
1659 int i;
1660 struct domain *d = NULL, dfind;
1661 gchar *s = NULL;
1663 if (search == NULL || wl == NULL)
1664 return (NULL);
1665 if (strlen(search) < 2)
1666 return (NULL);
1668 if (search[0] != '.')
1669 s = g_strdup_printf(".%s", search);
1670 else
1671 s = g_strdup(search);
1673 for (i = strlen(s) - 1; i >= 0; i--) {
1674 if (s[i] == '.') {
1675 dfind.d = &s[i];
1676 d = RB_FIND(domain_list, wl, &dfind);
1677 if (d)
1678 goto done;
1682 done:
1683 if (s)
1684 g_free(s);
1686 return (d);
1689 struct domain *
1690 wl_find_uri(const gchar *s, struct domain_list *wl)
1692 int i;
1693 char *ss;
1694 struct domain *r;
1696 if (s == NULL || wl == NULL)
1697 return (NULL);
1699 if (!strncmp(s, "http://", strlen("http://")))
1700 s = &s[strlen("http://")];
1701 else if (!strncmp(s, "https://", strlen("https://")))
1702 s = &s[strlen("https://")];
1704 if (strlen(s) < 2)
1705 return (NULL);
1707 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1708 /* chop string at first slash */
1709 if (s[i] == '/' || s[i] == '\0') {
1710 ss = g_strdup(s);
1711 ss[i] = '\0';
1712 r = wl_find(ss, wl);
1713 g_free(ss);
1714 return (r);
1717 return (NULL);
1720 char *
1721 get_toplevel_domain(char *domain)
1723 char *s;
1724 int found = 0;
1726 if (domain == NULL)
1727 return (NULL);
1728 if (strlen(domain) < 2)
1729 return (NULL);
1731 s = &domain[strlen(domain) - 1];
1732 while (s != domain) {
1733 if (*s == '.') {
1734 found++;
1735 if (found == 2)
1736 return (s);
1738 s--;
1741 if (found)
1742 return (domain);
1744 return (NULL);
1748 settings_add(char *var, char *val)
1750 int i, rv, *p;
1751 gfloat *f;
1752 char **s;
1754 /* get settings */
1755 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1756 if (strcmp(var, rs[i].name))
1757 continue;
1759 if (rs[i].s) {
1760 if (rs[i].s->set(&rs[i], val))
1761 errx(1, "invalid value for %s: %s", var, val);
1762 rv = 1;
1763 break;
1764 } else
1765 switch (rs[i].type) {
1766 case XT_S_INT:
1767 p = rs[i].ival;
1768 *p = atoi(val);
1769 rv = 1;
1770 break;
1771 case XT_S_STR:
1772 s = rs[i].sval;
1773 if (s == NULL)
1774 errx(1, "invalid sval for %s",
1775 rs[i].name);
1776 if (*s)
1777 g_free(*s);
1778 *s = g_strdup(val);
1779 rv = 1;
1780 break;
1781 case XT_S_FLOAT:
1782 f = rs[i].fval;
1783 *f = atof(val);
1784 rv = 1;
1785 break;
1786 case XT_S_INVALID:
1787 default:
1788 errx(1, "invalid type for %s", var);
1790 break;
1792 return (rv);
1795 #define WS "\n= \t"
1796 void
1797 config_parse(char *filename, int runtime)
1799 FILE *config, *f;
1800 char *line, *cp, *var, *val;
1801 size_t len, lineno = 0;
1802 int handled;
1803 char file[PATH_MAX];
1804 struct stat sb;
1806 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1808 if (filename == NULL)
1809 return;
1811 if (runtime && runtime_settings[0] != '\0') {
1812 snprintf(file, sizeof file, "%s/%s",
1813 work_dir, runtime_settings);
1814 if (stat(file, &sb)) {
1815 warnx("runtime file doesn't exist, creating it");
1816 if ((f = fopen(file, "w")) == NULL)
1817 err(1, "runtime");
1818 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1819 fclose(f);
1821 } else
1822 strlcpy(file, filename, sizeof file);
1824 if ((config = fopen(file, "r")) == NULL) {
1825 warn("config_parse: cannot open %s", filename);
1826 return;
1829 for (;;) {
1830 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1831 if (feof(config) || ferror(config))
1832 break;
1834 cp = line;
1835 cp += (long)strspn(cp, WS);
1836 if (cp[0] == '\0') {
1837 /* empty line */
1838 free(line);
1839 continue;
1842 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1843 errx(1, "invalid config file entry: %s", line);
1845 cp += (long)strspn(cp, WS);
1847 if ((val = strsep(&cp, "\0")) == NULL)
1848 break;
1850 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1851 handled = settings_add(var, val);
1852 if (handled == 0)
1853 errx(1, "invalid conf file entry: %s=%s", var, val);
1855 free(line);
1858 fclose(config);
1861 char *
1862 js_ref_to_string(JSContextRef context, JSValueRef ref)
1864 char *s = NULL;
1865 size_t l;
1866 JSStringRef jsref;
1868 jsref = JSValueToStringCopy(context, ref, NULL);
1869 if (jsref == NULL)
1870 return (NULL);
1872 l = JSStringGetMaximumUTF8CStringSize(jsref);
1873 s = g_malloc(l);
1874 if (s)
1875 JSStringGetUTF8CString(jsref, s, l);
1876 JSStringRelease(jsref);
1878 return (s);
1881 void
1882 disable_hints(struct tab *t)
1884 bzero(t->hint_buf, sizeof t->hint_buf);
1885 bzero(t->hint_num, sizeof t->hint_num);
1886 run_script(t, "vimprobable_clear()");
1887 t->hints_on = 0;
1888 t->hint_mode = XT_HINT_NONE;
1891 void
1892 enable_hints(struct tab *t)
1894 bzero(t->hint_buf, sizeof t->hint_buf);
1895 run_script(t, "vimprobable_show_hints()");
1896 t->hints_on = 1;
1897 t->hint_mode = XT_HINT_NONE;
1900 #define XT_JS_OPEN ("open;")
1901 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1902 #define XT_JS_FIRE ("fire;")
1903 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1904 #define XT_JS_FOUND ("found;")
1905 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1908 run_script(struct tab *t, char *s)
1910 JSGlobalContextRef ctx;
1911 WebKitWebFrame *frame;
1912 JSStringRef str;
1913 JSValueRef val, exception;
1914 char *es, buf[128];
1916 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1917 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1919 frame = webkit_web_view_get_main_frame(t->wv);
1920 ctx = webkit_web_frame_get_global_context(frame);
1922 str = JSStringCreateWithUTF8CString(s);
1923 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1924 NULL, 0, &exception);
1925 JSStringRelease(str);
1927 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1928 if (val == NULL) {
1929 es = js_ref_to_string(ctx, exception);
1930 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1931 g_free(es);
1932 return (1);
1933 } else {
1934 es = js_ref_to_string(ctx, val);
1935 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1937 /* handle return value right here */
1938 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1939 disable_hints(t);
1940 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1943 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1944 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1945 &es[XT_JS_FIRE_LEN]);
1946 run_script(t, buf);
1947 disable_hints(t);
1950 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1951 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1952 disable_hints(t);
1955 g_free(es);
1958 return (0);
1962 hint(struct tab *t, struct karg *args)
1965 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1967 if (t->hints_on == 0)
1968 enable_hints(t);
1969 else
1970 disable_hints(t);
1972 return (0);
1975 void
1976 apply_style(struct tab *t)
1978 g_object_set(G_OBJECT(t->settings),
1979 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1983 userstyle(struct tab *t, struct karg *args)
1985 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1987 if (t->styled) {
1988 t->styled = 0;
1989 g_object_set(G_OBJECT(t->settings),
1990 "user-stylesheet-uri", NULL, (char *)NULL);
1991 } else {
1992 t->styled = 1;
1993 apply_style(t);
1995 return (0);
1999 * Doesn't work fully, due to the following bug:
2000 * https://bugs.webkit.org/show_bug.cgi?id=51747
2003 restore_global_history(void)
2005 char file[PATH_MAX];
2006 FILE *f;
2007 struct history *h;
2008 gchar *uri;
2009 gchar *title;
2010 const char delim[3] = {'\\', '\\', '\0'};
2012 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2014 if ((f = fopen(file, "r")) == NULL) {
2015 warnx("%s: fopen", __func__);
2016 return (1);
2019 for (;;) {
2020 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2021 if (feof(f) || ferror(f))
2022 break;
2024 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2025 if (feof(f) || ferror(f)) {
2026 free(uri);
2027 warnx("%s: broken history file\n", __func__);
2028 return (1);
2031 if (uri && strlen(uri) && title && strlen(title)) {
2032 webkit_web_history_item_new_with_data(uri, title);
2033 h = g_malloc(sizeof(struct history));
2034 h->uri = g_strdup(uri);
2035 h->title = g_strdup(title);
2036 RB_INSERT(history_list, &hl, h);
2037 completion_add_uri(h->uri);
2038 } else {
2039 warnx("%s: failed to restore history\n", __func__);
2040 free(uri);
2041 free(title);
2042 return (1);
2045 free(uri);
2046 free(title);
2047 uri = NULL;
2048 title = NULL;
2051 return (0);
2055 save_global_history_to_disk(struct tab *t)
2057 char file[PATH_MAX];
2058 FILE *f;
2059 struct history *h;
2061 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2063 if ((f = fopen(file, "w")) == NULL) {
2064 show_oops(t, "%s: global history file: %s",
2065 __func__, strerror(errno));
2066 return (1);
2069 RB_FOREACH_REVERSE(h, history_list, &hl) {
2070 if (h->uri && h->title)
2071 fprintf(f, "%s\n%s\n", h->uri, h->title);
2074 fclose(f);
2076 return (0);
2080 quit(struct tab *t, struct karg *args)
2082 if (save_global_history)
2083 save_global_history_to_disk(t);
2085 gtk_main_quit();
2087 return (1);
2091 open_tabs(struct tab *t, struct karg *a)
2093 char file[PATH_MAX];
2094 FILE *f = NULL;
2095 char *uri = NULL;
2096 int rv = 1;
2097 struct tab *ti, *tt;
2099 if (a == NULL)
2100 goto done;
2102 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2103 if ((f = fopen(file, "r")) == NULL)
2104 goto done;
2106 ti = TAILQ_LAST(&tabs, tab_list);
2108 for (;;) {
2109 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2110 if (feof(f) || ferror(f))
2111 break;
2113 /* retrieve session name */
2114 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2115 strlcpy(named_session,
2116 &uri[strlen(XT_SAVE_SESSION_ID)],
2117 sizeof named_session);
2118 continue;
2121 if (uri && strlen(uri))
2122 create_new_tab(uri, NULL, 1, -1);
2124 free(uri);
2125 uri = NULL;
2128 /* close open tabs */
2129 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2130 for (;;) {
2131 tt = TAILQ_FIRST(&tabs);
2132 if (tt != ti) {
2133 delete_tab(tt);
2134 continue;
2136 delete_tab(tt);
2137 break;
2141 rv = 0;
2142 done:
2143 if (f)
2144 fclose(f);
2146 return (rv);
2150 restore_saved_tabs(void)
2152 char file[PATH_MAX];
2153 int unlink_file = 0;
2154 struct stat sb;
2155 struct karg a;
2156 int rv = 0;
2158 snprintf(file, sizeof file, "%s/%s",
2159 sessions_dir, XT_RESTART_TABS_FILE);
2160 if (stat(file, &sb) == -1)
2161 a.s = XT_SAVED_TABS_FILE;
2162 else {
2163 unlink_file = 1;
2164 a.s = XT_RESTART_TABS_FILE;
2167 a.i = XT_SES_DONOTHING;
2168 rv = open_tabs(NULL, &a);
2170 if (unlink_file)
2171 unlink(file);
2173 return (rv);
2177 save_tabs(struct tab *t, struct karg *a)
2179 char file[PATH_MAX];
2180 FILE *f;
2181 struct tab *ti;
2182 const gchar *uri;
2183 int len = 0, i;
2184 const gchar **arr = NULL;
2186 if (a == NULL)
2187 return (1);
2188 if (a->s == NULL)
2189 snprintf(file, sizeof file, "%s/%s",
2190 sessions_dir, named_session);
2191 else
2192 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2194 if ((f = fopen(file, "w")) == NULL) {
2195 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2196 return (1);
2199 /* save session name */
2200 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2202 /* save tabs, in the order they are arranged in the notebook */
2203 TAILQ_FOREACH(ti, &tabs, entry)
2204 len++;
2206 arr = g_malloc0(len * sizeof(gchar *));
2208 TAILQ_FOREACH(ti, &tabs, entry) {
2209 if ((uri = get_uri(ti->wv)) != NULL)
2210 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2213 for (i = 0; i < len; i++)
2214 if (arr[i])
2215 fprintf(f, "%s\n", arr[i]);
2217 g_free(arr);
2218 /* try and make sure this gets to disk NOW. XXX Backup first? */
2219 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2220 show_oops(t, "May not have managed to save session: %s",
2221 strerror(errno));
2224 fclose(f);
2226 return (0);
2230 save_tabs_and_quit(struct tab *t, struct karg *args)
2232 struct karg a;
2234 a.s = NULL;
2235 save_tabs(t, &a);
2236 quit(t, NULL);
2238 return (1);
2242 yank_uri(struct tab *t, struct karg *args)
2244 const gchar *uri;
2245 GtkClipboard *clipboard;
2247 if ((uri = get_uri(t->wv)) == NULL)
2248 return (1);
2250 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2251 gtk_clipboard_set_text(clipboard, uri, -1);
2253 return (0);
2257 paste_uri(struct tab *t, struct karg *args)
2259 GtkClipboard *clipboard;
2260 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2261 gint len;
2262 gchar *p = NULL, *uri;
2264 /* try primary clipboard first */
2265 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2266 p = gtk_clipboard_wait_for_text(clipboard);
2268 /* if it failed get whatever text is in cut_buffer0 */
2269 if (p == NULL)
2270 if (gdk_property_get(gdk_get_default_root_window(),
2271 atom,
2272 gdk_atom_intern("STRING", FALSE),
2274 65536 /* picked out of my butt */,
2275 FALSE,
2276 NULL,
2277 NULL,
2278 &len,
2279 (guchar **)&p)) {
2280 /* yes sir, we need to NUL the string */
2281 p[len] = '\0';
2284 if (p) {
2285 uri = p;
2286 while(*uri && isspace(*uri))
2287 uri++;
2288 if (strlen(uri) == 0) {
2289 show_oops(t, "empty paste buffer");
2290 goto done;
2292 if (valid_url_type(uri)) {
2293 /* we can be clever and paste this in search box */
2294 show_oops(t, "not a valid URL");
2295 goto done;
2298 if (args->i == XT_PASTE_CURRENT_TAB)
2299 load_uri(t, uri);
2300 else if (args->i == XT_PASTE_NEW_TAB)
2301 create_new_tab(uri, NULL, 1, -1);
2304 done:
2305 if (p)
2306 g_free(p);
2308 return (0);
2311 char *
2312 find_domain(const gchar *s, int add_dot)
2314 int i;
2315 char *r = NULL, *ss = NULL;
2317 if (s == NULL)
2318 return (NULL);
2320 if (!strncmp(s, "http://", strlen("http://")))
2321 s = &s[strlen("http://")];
2322 else if (!strncmp(s, "https://", strlen("https://")))
2323 s = &s[strlen("https://")];
2325 if (strlen(s) < 2)
2326 return (NULL);
2328 ss = g_strdup(s);
2329 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2330 /* chop string at first slash */
2331 if (ss[i] == '/' || ss[i] == '\0') {
2332 ss[i] = '\0';
2333 if (add_dot)
2334 r = g_strdup_printf(".%s", ss);
2335 else
2336 r = g_strdup(ss);
2337 break;
2339 g_free(ss);
2341 return (r);
2345 toggle_cwl(struct tab *t, struct karg *args)
2347 struct domain *d;
2348 const gchar *uri;
2349 char *dom = NULL, *dom_toggle = NULL;
2350 int es;
2352 if (args == NULL)
2353 return (1);
2355 uri = get_uri(t->wv);
2356 dom = find_domain(uri, 1);
2357 d = wl_find(dom, &c_wl);
2359 if (d == NULL)
2360 es = 0;
2361 else
2362 es = 1;
2364 if (args->i & XT_WL_TOGGLE)
2365 es = !es;
2366 else if ((args->i & XT_WL_ENABLE) && es != 1)
2367 es = 1;
2368 else if ((args->i & XT_WL_DISABLE) && es != 0)
2369 es = 0;
2371 if (args->i & XT_WL_TOPLEVEL)
2372 dom_toggle = get_toplevel_domain(dom);
2373 else
2374 dom_toggle = dom;
2376 if (es)
2377 /* enable cookies for domain */
2378 wl_add(dom_toggle, &c_wl, 0);
2379 else
2380 /* disable cookies for domain */
2381 RB_REMOVE(domain_list, &c_wl, d);
2383 webkit_web_view_reload(t->wv);
2385 g_free(dom);
2386 return (0);
2390 toggle_js(struct tab *t, struct karg *args)
2392 int es;
2393 const gchar *uri;
2394 struct domain *d;
2395 char *dom = NULL, *dom_toggle = NULL;
2397 if (args == NULL)
2398 return (1);
2400 g_object_get(G_OBJECT(t->settings),
2401 "enable-scripts", &es, (char *)NULL);
2402 if (args->i & XT_WL_TOGGLE)
2403 es = !es;
2404 else if ((args->i & XT_WL_ENABLE) && es != 1)
2405 es = 1;
2406 else if ((args->i & XT_WL_DISABLE) && es != 0)
2407 es = 0;
2408 else
2409 return (1);
2411 uri = get_uri(t->wv);
2412 dom = find_domain(uri, 1);
2414 if (uri == NULL || dom == NULL) {
2415 show_oops(t, "Can't toggle domain in JavaScript white list");
2416 goto done;
2419 if (args->i & XT_WL_TOPLEVEL)
2420 dom_toggle = get_toplevel_domain(dom);
2421 else
2422 dom_toggle = dom;
2424 if (es) {
2425 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2426 wl_add(dom_toggle, &js_wl, 0 /* session */);
2427 } else {
2428 d = wl_find(dom_toggle, &js_wl);
2429 if (d)
2430 RB_REMOVE(domain_list, &js_wl, d);
2431 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2433 g_object_set(G_OBJECT(t->settings),
2434 "enable-scripts", es, (char *)NULL);
2435 g_object_set(G_OBJECT(t->settings),
2436 "javascript-can-open-windows-automatically", es, (char *)NULL);
2437 webkit_web_view_set_settings(t->wv, t->settings);
2438 webkit_web_view_reload(t->wv);
2439 done:
2440 if (dom)
2441 g_free(dom);
2442 return (0);
2445 void
2446 js_toggle_cb(GtkWidget *w, struct tab *t)
2448 struct karg a;
2450 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2451 toggle_js(t, &a);
2455 toggle_src(struct tab *t, struct karg *args)
2457 gboolean mode;
2459 if (t == NULL)
2460 return (0);
2462 mode = webkit_web_view_get_view_source_mode(t->wv);
2463 webkit_web_view_set_view_source_mode(t->wv, !mode);
2464 webkit_web_view_reload(t->wv);
2466 return (0);
2469 void
2470 focus_webview(struct tab *t)
2472 if (t == NULL)
2473 return;
2475 /* only grab focus if we are visible */
2476 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2477 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2481 focus(struct tab *t, struct karg *args)
2483 if (t == NULL || args == NULL)
2484 return (1);
2486 if (show_url == 0)
2487 return (0);
2489 if (args->i == XT_FOCUS_URI)
2490 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2491 else if (args->i == XT_FOCUS_SEARCH)
2492 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2494 return (0);
2498 stats(struct tab *t, struct karg *args)
2500 char *page, *body, *s, line[64 * 1024];
2501 uint64_t line_count = 0;
2502 FILE *r_cookie_f;
2504 if (t == NULL)
2505 show_oops_s("stats invalid parameters");
2507 line[0] = '\0';
2508 if (save_rejected_cookies) {
2509 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2510 for (;;) {
2511 s = fgets(line, sizeof line, r_cookie_f);
2512 if (s == NULL || feof(r_cookie_f) ||
2513 ferror(r_cookie_f))
2514 break;
2515 line_count++;
2517 fclose(r_cookie_f);
2518 snprintf(line, sizeof line,
2519 "<br/>Cookies blocked(*) total: %llu", line_count);
2520 } else
2521 show_oops(t, "Can't open blocked cookies file: %s",
2522 strerror(errno));
2525 body = g_strdup_printf(
2526 "Cookies blocked(*) this session: %llu"
2527 "%s"
2528 "<p><small><b>*</b> results vary based on settings</small></p>",
2529 blocked_cookies,
2530 line);
2532 page = get_html_page("Statistics", body, "", 0);
2533 g_free(body);
2535 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2536 g_free(page);
2538 return (0);
2542 marco(struct tab *t, struct karg *args)
2544 char *page, line[64 * 1024];
2545 int len;
2547 if (t == NULL)
2548 show_oops_s("marco invalid parameters");
2550 line[0] = '\0';
2551 snprintf(line, sizeof line, "%s", marco_message(&len));
2553 page = get_html_page("Marco Sez...", line, "", 0);
2555 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2556 g_free(page);
2558 return (0);
2562 blank(struct tab *t, struct karg *args)
2564 if (t == NULL)
2565 show_oops_s("blank invalid parameters");
2567 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2569 return (0);
2572 about(struct tab *t, struct karg *args)
2574 char *page, *body;
2576 if (t == NULL)
2577 show_oops_s("about invalid parameters");
2579 body = g_strdup_printf("<b>Version: %s</b><p>"
2580 "Authors:"
2581 "<ul>"
2582 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2583 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2584 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2585 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2586 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2587 "</ul>"
2588 "Copyrights and licenses can be found on the XXXterm "
2589 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2590 version
2593 page = get_html_page("About", body, "", 0);
2594 g_free(body);
2596 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2597 g_free(page);
2599 return (0);
2603 help(struct tab *t, struct karg *args)
2605 char *page, *head, *body;
2607 if (t == NULL)
2608 show_oops_s("help invalid parameters");
2610 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2611 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2612 "</head>\n";
2613 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2614 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2615 "cgi-bin/man-cgi?xxxterm</a>";
2617 page = get_html_page("XXXterm", body, head, FALSE);
2619 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2620 g_free(page);
2622 return (0);
2626 * update all favorite tabs apart from one. Pass NULL if
2627 * you want to update all.
2629 void
2630 update_favorite_tabs(struct tab *apart_from)
2632 struct tab *t;
2633 if (!updating_fl_tabs) {
2634 updating_fl_tabs = 1; /* stop infinite recursion */
2635 TAILQ_FOREACH(t, &tabs, entry)
2636 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2637 && (t != apart_from))
2638 xtp_page_fl(t, NULL);
2639 updating_fl_tabs = 0;
2643 /* show a list of favorites (bookmarks) */
2645 xtp_page_fl(struct tab *t, struct karg *args)
2647 char file[PATH_MAX];
2648 FILE *f;
2649 char *uri = NULL, *title = NULL;
2650 size_t len, lineno = 0;
2651 int i, failed = 0;
2652 char *body, *tmp, *page = NULL;
2653 const char delim[3] = {'\\', '\\', '\0'};
2655 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2657 if (t == NULL)
2658 warn("%s: bad param", __func__);
2660 /* mark tab as favorite list */
2661 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2663 /* new session key */
2664 if (!updating_fl_tabs)
2665 generate_xtp_session_key(&fl_session_key);
2667 /* open favorites */
2668 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2669 if ((f = fopen(file, "r")) == NULL) {
2670 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2671 return (1);
2674 /* body */
2675 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2676 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2677 "<th style='width: 40px'>Rm</th></tr>\n");
2679 for (i = 1;;) {
2680 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2681 if (feof(f) || ferror(f))
2682 break;
2683 if (len == 0) {
2684 free(title);
2685 title = NULL;
2686 continue;
2689 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2690 if (feof(f) || ferror(f)) {
2691 show_oops(t, "favorites file corrupt");
2692 failed = 1;
2693 break;
2696 tmp = body;
2697 body = g_strdup_printf("%s<tr>"
2698 "<td>%d</td>"
2699 "<td><a href='%s'>%s</a></td>"
2700 "<td style='text-align: center'>"
2701 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2702 "</tr>\n",
2703 body, i, uri, title,
2704 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2706 g_free(tmp);
2708 free(uri);
2709 uri = NULL;
2710 free(title);
2711 title = NULL;
2712 i++;
2714 fclose(f);
2716 /* if none, say so */
2717 if (i == 1) {
2718 tmp = body;
2719 body = g_strdup_printf("%s<tr>"
2720 "<td colspan='3' style='text-align: center'>"
2721 "No favorites - To add one use the 'favadd' command."
2722 "</td></tr>", body);
2723 g_free(tmp);
2726 tmp = body;
2727 body = g_strdup_printf("%s</table>", body);
2728 g_free(tmp);
2730 if (uri)
2731 free(uri);
2732 if (title)
2733 free(title);
2735 /* render */
2736 if (!failed) {
2737 page = get_html_page("Favorites", body, "", 1);
2738 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2739 g_free(page);
2742 update_favorite_tabs(t);
2744 if (body)
2745 g_free(body);
2747 return (failed);
2750 void
2751 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2752 size_t cert_count, char *title)
2754 gnutls_datum_t cinfo;
2755 char *tmp, *body;
2756 int i;
2758 body = g_strdup("");
2760 for (i = 0; i < cert_count; i++) {
2761 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2762 &cinfo))
2763 return;
2765 tmp = body;
2766 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2767 body, i, cinfo.data);
2768 gnutls_free(cinfo.data);
2769 g_free(tmp);
2772 tmp = get_html_page(title, body, "", 0);
2773 g_free(body);
2775 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2776 g_free(tmp);
2780 ca_cmd(struct tab *t, struct karg *args)
2782 FILE *f = NULL;
2783 int rv = 1, certs = 0, certs_read;
2784 struct stat sb;
2785 gnutls_datum dt;
2786 gnutls_x509_crt_t *c = NULL;
2787 char *certs_buf = NULL, *s;
2789 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2790 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2791 return (1);
2794 if (fstat(fileno(f), &sb) == -1) {
2795 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2796 goto done;
2799 certs_buf = g_malloc(sb.st_size + 1);
2800 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2801 show_oops(t, "Can't read CA file: %s", strerror(errno));
2802 goto done;
2804 certs_buf[sb.st_size] = '\0';
2806 s = certs_buf;
2807 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2808 certs++;
2809 s += strlen("BEGIN CERTIFICATE");
2812 bzero(&dt, sizeof dt);
2813 dt.data = certs_buf;
2814 dt.size = sb.st_size;
2815 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2816 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2817 GNUTLS_X509_FMT_PEM, 0);
2818 if (certs_read <= 0) {
2819 show_oops(t, "No cert(s) available");
2820 goto done;
2822 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2823 done:
2824 if (c)
2825 g_free(c);
2826 if (certs_buf)
2827 g_free(certs_buf);
2828 if (f)
2829 fclose(f);
2831 return (rv);
2835 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2837 SoupURI *su = NULL;
2838 struct addrinfo hints, *res = NULL, *ai;
2839 int s = -1, on;
2840 char port[8];
2842 if (uri && !g_str_has_prefix(uri, "https://"))
2843 goto done;
2845 su = soup_uri_new(uri);
2846 if (su == NULL)
2847 goto done;
2848 if (!SOUP_URI_VALID_FOR_HTTP(su))
2849 goto done;
2851 snprintf(port, sizeof port, "%d", su->port);
2852 bzero(&hints, sizeof(struct addrinfo));
2853 hints.ai_flags = AI_CANONNAME;
2854 hints.ai_family = AF_UNSPEC;
2855 hints.ai_socktype = SOCK_STREAM;
2857 if (getaddrinfo(su->host, port, &hints, &res))
2858 goto done;
2860 for (ai = res; ai; ai = ai->ai_next) {
2861 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2862 continue;
2864 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2865 if (s < 0)
2866 goto done;
2867 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2868 sizeof(on)) == -1)
2869 goto done;
2871 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2872 goto done;
2875 if (domain)
2876 strlcpy(domain, su->host, domain_sz);
2877 done:
2878 if (su)
2879 soup_uri_free(su);
2880 if (res)
2881 freeaddrinfo(res);
2883 return (s);
2887 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2889 if (gsession)
2890 gnutls_deinit(gsession);
2891 if (xcred)
2892 gnutls_certificate_free_credentials(xcred);
2894 return (0);
2898 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2899 gnutls_certificate_credentials_t *xc)
2901 gnutls_certificate_credentials_t xcred;
2902 gnutls_session_t gsession;
2903 int rv = 1;
2905 if (gs == NULL || xc == NULL)
2906 goto done;
2908 *gs = NULL;
2909 *xc = NULL;
2911 gnutls_certificate_allocate_credentials(&xcred);
2912 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2913 GNUTLS_X509_FMT_PEM);
2914 gnutls_init(&gsession, GNUTLS_CLIENT);
2915 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2916 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2917 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2918 if ((rv = gnutls_handshake(gsession)) < 0) {
2919 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2921 gnutls_error_is_fatal(rv),
2922 gnutls_strerror_name(rv));
2923 stop_tls(gsession, xcred);
2924 goto done;
2927 gnutls_credentials_type_t cred;
2928 cred = gnutls_auth_get_type(gsession);
2929 if (cred != GNUTLS_CRD_CERTIFICATE) {
2930 stop_tls(gsession, xcred);
2931 goto done;
2934 *gs = gsession;
2935 *xc = xcred;
2936 rv = 0;
2937 done:
2938 return (rv);
2942 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2943 size_t *cert_count)
2945 unsigned int len;
2946 const gnutls_datum_t *cl;
2947 gnutls_x509_crt_t *all_certs;
2948 int i, rv = 1;
2950 if (certs == NULL || cert_count == NULL)
2951 goto done;
2952 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2953 goto done;
2954 cl = gnutls_certificate_get_peers(gsession, &len);
2955 if (len == 0)
2956 goto done;
2958 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2959 for (i = 0; i < len; i++) {
2960 gnutls_x509_crt_init(&all_certs[i]);
2961 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2962 GNUTLS_X509_FMT_PEM < 0)) {
2963 g_free(all_certs);
2964 goto done;
2968 *certs = all_certs;
2969 *cert_count = len;
2970 rv = 0;
2971 done:
2972 return (rv);
2975 void
2976 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2978 int i;
2980 for (i = 0; i < cert_count; i++)
2981 gnutls_x509_crt_deinit(certs[i]);
2982 g_free(certs);
2985 void
2986 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2987 size_t cert_count, char *domain)
2989 size_t cert_buf_sz;
2990 char cert_buf[64 * 1024], file[PATH_MAX];
2991 int i;
2992 FILE *f;
2993 GdkColor color;
2995 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2996 return;
2998 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2999 if ((f = fopen(file, "w")) == NULL) {
3000 show_oops(t, "Can't create cert file %s %s",
3001 file, strerror(errno));
3002 return;
3005 for (i = 0; i < cert_count; i++) {
3006 cert_buf_sz = sizeof cert_buf;
3007 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3008 cert_buf, &cert_buf_sz)) {
3009 show_oops(t, "gnutls_x509_crt_export failed");
3010 goto done;
3012 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3013 show_oops(t, "Can't write certs: %s", strerror(errno));
3014 goto done;
3018 /* not the best spot but oh well */
3019 gdk_color_parse("lightblue", &color);
3020 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3021 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3022 gdk_color_parse(XT_COLOR_BLACK, &color);
3023 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3024 done:
3025 fclose(f);
3029 load_compare_cert(struct tab *t, struct karg *args)
3031 const gchar *uri;
3032 char domain[8182], file[PATH_MAX];
3033 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3034 int s = -1, rv = 1, i;
3035 size_t cert_count;
3036 FILE *f = NULL;
3037 size_t cert_buf_sz;
3038 gnutls_session_t gsession;
3039 gnutls_x509_crt_t *certs;
3040 gnutls_certificate_credentials_t xcred;
3042 if (t == NULL)
3043 return (1);
3045 if ((uri = get_uri(t->wv)) == NULL)
3046 return (1);
3048 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3049 return (1);
3051 /* go ssl/tls */
3052 if (start_tls(t, s, &gsession, &xcred)) {
3053 show_oops(t, "Start TLS failed");
3054 goto done;
3057 /* get certs */
3058 if (get_connection_certs(gsession, &certs, &cert_count)) {
3059 show_oops(t, "Can't get connection certificates");
3060 goto done;
3063 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3064 if ((f = fopen(file, "r")) == NULL)
3065 goto freeit;
3067 for (i = 0; i < cert_count; i++) {
3068 cert_buf_sz = sizeof cert_buf;
3069 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3070 cert_buf, &cert_buf_sz)) {
3071 goto freeit;
3073 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3074 rv = -1; /* critical */
3075 goto freeit;
3077 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3078 rv = -1; /* critical */
3079 goto freeit;
3083 rv = 0;
3084 freeit:
3085 if (f)
3086 fclose(f);
3087 free_connection_certs(certs, cert_count);
3088 done:
3089 /* we close the socket first for speed */
3090 if (s != -1)
3091 close(s);
3092 stop_tls(gsession, xcred);
3094 return (rv);
3098 cert_cmd(struct tab *t, struct karg *args)
3100 const gchar *uri;
3101 char domain[8182];
3102 int s = -1;
3103 size_t cert_count;
3104 gnutls_session_t gsession;
3105 gnutls_x509_crt_t *certs;
3106 gnutls_certificate_credentials_t xcred;
3108 if (t == NULL)
3109 return (1);
3111 if (ssl_ca_file == NULL) {
3112 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3113 return (1);
3116 if ((uri = get_uri(t->wv)) == NULL) {
3117 show_oops(t, "Invalid URI");
3118 return (1);
3121 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3122 show_oops(t, "Invalid certificate URI: %s", uri);
3123 return (1);
3126 /* go ssl/tls */
3127 if (start_tls(t, s, &gsession, &xcred)) {
3128 show_oops(t, "Start TLS failed");
3129 goto done;
3132 /* get certs */
3133 if (get_connection_certs(gsession, &certs, &cert_count)) {
3134 show_oops(t, "get_connection_certs failed");
3135 goto done;
3138 if (args->i & XT_SHOW)
3139 show_certs(t, certs, cert_count, "Certificate Chain");
3140 else if (args->i & XT_SAVE)
3141 save_certs(t, certs, cert_count, domain);
3143 free_connection_certs(certs, cert_count);
3144 done:
3145 /* we close the socket first for speed */
3146 if (s != -1)
3147 close(s);
3148 stop_tls(gsession, xcred);
3150 return (0);
3154 remove_cookie(int index)
3156 int i, rv = 1;
3157 GSList *cf;
3158 SoupCookie *c;
3160 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3162 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3164 for (i = 1; cf; cf = cf->next, i++) {
3165 if (i != index)
3166 continue;
3167 c = cf->data;
3168 print_cookie("remove cookie", c);
3169 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3170 rv = 0;
3171 break;
3174 soup_cookies_free(cf);
3176 return (rv);
3180 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3182 struct domain *d;
3183 char *tmp, *body;
3185 body = g_strdup("");
3187 /* p list */
3188 if (args->i & XT_WL_PERSISTENT) {
3189 tmp = body;
3190 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3191 g_free(tmp);
3192 RB_FOREACH(d, domain_list, wl) {
3193 if (d->handy == 0)
3194 continue;
3195 tmp = body;
3196 body = g_strdup_printf("%s%s<br/>", body, d->d);
3197 g_free(tmp);
3201 /* s list */
3202 if (args->i & XT_WL_SESSION) {
3203 tmp = body;
3204 body = g_strdup_printf("%s<h2>Session</h2>", body);
3205 g_free(tmp);
3206 RB_FOREACH(d, domain_list, wl) {
3207 if (d->handy == 1)
3208 continue;
3209 tmp = body;
3210 body = g_strdup_printf("%s%s<br/>", body, d->d);
3211 g_free(tmp);
3215 tmp = get_html_page(title, body, "", 0);
3216 g_free(body);
3217 if (wl == &js_wl)
3218 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3219 else
3220 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3221 g_free(tmp);
3222 return (0);
3226 wl_save(struct tab *t, struct karg *args, int js)
3228 char file[PATH_MAX];
3229 FILE *f;
3230 char *line = NULL, *lt = NULL;
3231 size_t linelen;
3232 const gchar *uri;
3233 char *dom = NULL, *dom_save = NULL;
3234 struct karg a;
3235 struct domain *d;
3236 GSList *cf;
3237 SoupCookie *ci, *c;
3239 if (t == NULL || args == NULL)
3240 return (1);
3242 if (runtime_settings[0] == '\0')
3243 return (1);
3245 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3246 if ((f = fopen(file, "r+")) == NULL)
3247 return (1);
3249 uri = get_uri(t->wv);
3250 dom = find_domain(uri, 1);
3251 if (uri == NULL || dom == NULL) {
3252 show_oops(t, "Can't add domain to %s white list",
3253 js ? "JavaScript" : "cookie");
3254 goto done;
3257 if (args->i & XT_WL_TOPLEVEL) {
3258 /* save domain */
3259 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3260 show_oops(t, "invalid domain: %s", dom);
3261 goto done;
3263 } else if (args->i & XT_WL_FQDN) {
3264 /* save fqdn */
3265 dom_save = dom;
3266 } else
3267 goto done;
3269 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3271 while (!feof(f)) {
3272 line = fparseln(f, &linelen, NULL, NULL, 0);
3273 if (line == NULL)
3274 continue;
3275 if (!strcmp(line, lt))
3276 goto done;
3277 free(line);
3278 line = NULL;
3281 fprintf(f, "%s\n", lt);
3283 a.i = XT_WL_ENABLE;
3284 a.i |= args->i;
3285 if (js) {
3286 d = wl_find(dom_save, &js_wl);
3287 if (!d) {
3288 settings_add("js_wl", dom_save);
3289 d = wl_find(dom_save, &js_wl);
3291 toggle_js(t, &a);
3292 } else {
3293 d = wl_find(dom_save, &c_wl);
3294 if (!d) {
3295 settings_add("cookie_wl", dom_save);
3296 d = wl_find(dom_save, &c_wl);
3298 toggle_cwl(t, &a);
3300 /* find and add to persistent jar */
3301 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3302 for (;cf; cf = cf->next) {
3303 ci = cf->data;
3304 if (!strcmp(dom_save, ci->domain) ||
3305 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3306 c = soup_cookie_copy(ci);
3307 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3310 soup_cookies_free(cf);
3312 if (d)
3313 d->handy = 1;
3315 done:
3316 if (line)
3317 free(line);
3318 if (dom)
3319 g_free(dom);
3320 if (lt)
3321 g_free(lt);
3322 fclose(f);
3324 return (0);
3328 js_show_wl(struct tab *t, struct karg *args)
3330 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3331 wl_show(t, args, "JavaScript White List", &js_wl);
3333 return (0);
3337 cookie_show_wl(struct tab *t, struct karg *args)
3339 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3340 wl_show(t, args, "Cookie White List", &c_wl);
3342 return (0);
3346 cookie_cmd(struct tab *t, struct karg *args)
3348 if (args->i & XT_SHOW)
3349 wl_show(t, args, "Cookie White List", &c_wl);
3350 else if (args->i & XT_WL_TOGGLE)
3351 toggle_cwl(t, args);
3352 else if (args->i & XT_SAVE)
3353 wl_save(t, args, 0);
3354 else if (args->i & XT_DELETE)
3355 show_oops(t, "'cookie delete' currently unimplemented");
3357 return (0);
3361 js_cmd(struct tab *t, struct karg *args)
3363 if (args->i & XT_SHOW)
3364 wl_show(t, args, "JavaScript White List", &js_wl);
3365 else if (args->i & XT_SAVE)
3366 wl_save(t, args, 1);
3367 else if (args->i & XT_WL_TOGGLE)
3368 toggle_js(t, args);
3369 else if (args->i & XT_DELETE)
3370 show_oops(t, "'js delete' currently unimplemented");
3372 return (0);
3376 add_favorite(struct tab *t, struct karg *args)
3378 char file[PATH_MAX];
3379 FILE *f;
3380 char *line = NULL;
3381 size_t urilen, linelen;
3382 const gchar *uri, *title;
3384 if (t == NULL)
3385 return (1);
3387 /* don't allow adding of xtp pages to favorites */
3388 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3389 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3390 return (1);
3393 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3394 if ((f = fopen(file, "r+")) == NULL) {
3395 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3396 return (1);
3399 title = webkit_web_view_get_title(t->wv);
3400 uri = get_uri(t->wv);
3402 if (title == NULL)
3403 title = uri;
3405 if (title == NULL || uri == NULL) {
3406 show_oops(t, "can't add page to favorites");
3407 goto done;
3410 urilen = strlen(uri);
3412 for (;;) {
3413 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3414 if (feof(f) || ferror(f))
3415 break;
3417 if (linelen == urilen && !strcmp(line, uri))
3418 goto done;
3420 free(line);
3421 line = NULL;
3424 fprintf(f, "\n%s\n%s", title, uri);
3425 done:
3426 if (line)
3427 free(line);
3428 fclose(f);
3430 update_favorite_tabs(NULL);
3432 return (0);
3436 navaction(struct tab *t, struct karg *args)
3438 WebKitWebHistoryItem *item;
3440 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3441 t->tab_id, args->i);
3443 if (t->item) {
3444 if (args->i == XT_NAV_BACK)
3445 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3446 else
3447 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3448 if (item == NULL)
3449 return (XT_CB_PASSTHROUGH);
3450 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3451 t->item = NULL;
3452 return (XT_CB_PASSTHROUGH);
3455 switch (args->i) {
3456 case XT_NAV_BACK:
3457 webkit_web_view_go_back(t->wv);
3458 break;
3459 case XT_NAV_FORWARD:
3460 webkit_web_view_go_forward(t->wv);
3461 break;
3462 case XT_NAV_RELOAD:
3463 webkit_web_view_reload(t->wv);
3464 break;
3465 case XT_NAV_RELOAD_CACHE:
3466 webkit_web_view_reload_bypass_cache(t->wv);
3467 break;
3469 return (XT_CB_PASSTHROUGH);
3473 move(struct tab *t, struct karg *args)
3475 GtkAdjustment *adjust;
3476 double pi, si, pos, ps, upper, lower, max;
3478 switch (args->i) {
3479 case XT_MOVE_DOWN:
3480 case XT_MOVE_UP:
3481 case XT_MOVE_BOTTOM:
3482 case XT_MOVE_TOP:
3483 case XT_MOVE_PAGEDOWN:
3484 case XT_MOVE_PAGEUP:
3485 case XT_MOVE_HALFDOWN:
3486 case XT_MOVE_HALFUP:
3487 adjust = t->adjust_v;
3488 break;
3489 default:
3490 adjust = t->adjust_h;
3491 break;
3494 pos = gtk_adjustment_get_value(adjust);
3495 ps = gtk_adjustment_get_page_size(adjust);
3496 upper = gtk_adjustment_get_upper(adjust);
3497 lower = gtk_adjustment_get_lower(adjust);
3498 si = gtk_adjustment_get_step_increment(adjust);
3499 pi = gtk_adjustment_get_page_increment(adjust);
3500 max = upper - ps;
3502 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3503 "max %f si %f pi %f\n",
3504 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3505 pos, ps, upper, lower, max, si, pi);
3507 switch (args->i) {
3508 case XT_MOVE_DOWN:
3509 case XT_MOVE_RIGHT:
3510 pos += si;
3511 gtk_adjustment_set_value(adjust, MIN(pos, max));
3512 break;
3513 case XT_MOVE_UP:
3514 case XT_MOVE_LEFT:
3515 pos -= si;
3516 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3517 break;
3518 case XT_MOVE_BOTTOM:
3519 case XT_MOVE_FARRIGHT:
3520 gtk_adjustment_set_value(adjust, max);
3521 break;
3522 case XT_MOVE_TOP:
3523 case XT_MOVE_FARLEFT:
3524 gtk_adjustment_set_value(adjust, lower);
3525 break;
3526 case XT_MOVE_PAGEDOWN:
3527 pos += pi;
3528 gtk_adjustment_set_value(adjust, MIN(pos, max));
3529 break;
3530 case XT_MOVE_PAGEUP:
3531 pos -= pi;
3532 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3533 break;
3534 case XT_MOVE_HALFDOWN:
3535 pos += pi / 2;
3536 gtk_adjustment_set_value(adjust, MIN(pos, max));
3537 break;
3538 case XT_MOVE_HALFUP:
3539 pos -= pi / 2;
3540 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3541 break;
3542 default:
3543 return (XT_CB_PASSTHROUGH);
3546 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3548 return (XT_CB_HANDLED);
3551 void
3552 url_set_visibility(void)
3554 struct tab *t;
3556 TAILQ_FOREACH(t, &tabs, entry) {
3557 if (show_url == 0) {
3558 gtk_widget_hide(t->toolbar);
3559 focus_webview(t);
3560 } else
3561 gtk_widget_show(t->toolbar);
3565 void
3566 notebook_tab_set_visibility(GtkNotebook *notebook)
3568 if (show_tabs == 0)
3569 gtk_notebook_set_show_tabs(notebook, FALSE);
3570 else
3571 gtk_notebook_set_show_tabs(notebook, TRUE);
3574 void
3575 statusbar_set_visibility(void)
3577 struct tab *t;
3579 TAILQ_FOREACH(t, &tabs, entry) {
3580 if (show_statusbar == 0) {
3581 gtk_widget_hide(t->statusbar);
3582 focus_webview(t);
3583 } else
3584 gtk_widget_show(t->statusbar);
3588 void
3589 url_set(struct tab *t, int enable_url_entry)
3591 GdkPixbuf *pixbuf;
3592 int progress;
3594 show_url = enable_url_entry;
3596 if (enable_url_entry) {
3597 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3598 GTK_ENTRY_ICON_PRIMARY, NULL);
3599 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3600 } else {
3601 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3602 GTK_ENTRY_ICON_PRIMARY);
3603 progress =
3604 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3605 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3606 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3607 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3608 progress);
3613 fullscreen(struct tab *t, struct karg *args)
3615 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3617 if (t == NULL)
3618 return (XT_CB_PASSTHROUGH);
3620 if (show_url == 0) {
3621 url_set(t, 1);
3622 show_tabs = 1;
3623 } else {
3624 url_set(t, 0);
3625 show_tabs = 0;
3628 url_set_visibility();
3629 notebook_tab_set_visibility(notebook);
3631 return (XT_CB_HANDLED);
3635 statusaction(struct tab *t, struct karg *args)
3637 int rv = XT_CB_HANDLED;
3639 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3641 if (t == NULL)
3642 return (XT_CB_PASSTHROUGH);
3644 switch (args->i) {
3645 case XT_STATUSBAR_SHOW:
3646 if (show_statusbar == 0) {
3647 show_statusbar = 1;
3648 statusbar_set_visibility();
3650 break;
3651 case XT_STATUSBAR_HIDE:
3652 if (show_statusbar == 1) {
3653 show_statusbar = 0;
3654 statusbar_set_visibility();
3656 break;
3658 return (rv);
3662 urlaction(struct tab *t, struct karg *args)
3664 int rv = XT_CB_HANDLED;
3666 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3668 if (t == NULL)
3669 return (XT_CB_PASSTHROUGH);
3671 switch (args->i) {
3672 case XT_URL_SHOW:
3673 if (show_url == 0) {
3674 url_set(t, 1);
3675 url_set_visibility();
3677 break;
3678 case XT_URL_HIDE:
3679 if (show_url == 1) {
3680 url_set(t, 0);
3681 url_set_visibility();
3683 break;
3685 return (rv);
3689 tabaction(struct tab *t, struct karg *args)
3691 int rv = XT_CB_HANDLED;
3692 char *url = args->s;
3693 struct undo *u;
3694 struct tab *tt;
3696 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3698 if (t == NULL)
3699 return (XT_CB_PASSTHROUGH);
3701 switch (args->i) {
3702 case XT_TAB_NEW:
3703 if (strlen(url) > 0)
3704 create_new_tab(url, NULL, 1, args->p);
3705 else
3706 create_new_tab(NULL, NULL, 1, args->p);
3707 break;
3708 case XT_TAB_DELETE:
3709 if (args->p < 0)
3710 delete_tab(t);
3711 else
3712 TAILQ_FOREACH(tt, &tabs, entry)
3713 if (tt->tab_id == args->p - 1) {
3714 delete_tab(tt);
3715 recalc_tabs();
3716 break;
3718 break;
3719 case XT_TAB_DELQUIT:
3720 if (gtk_notebook_get_n_pages(notebook) > 1)
3721 delete_tab(t);
3722 else
3723 quit(t, args);
3724 break;
3725 case XT_TAB_OPEN:
3726 if (strlen(url) > 0)
3728 else {
3729 rv = XT_CB_PASSTHROUGH;
3730 goto done;
3732 load_uri(t, url);
3733 break;
3734 case XT_TAB_SHOW:
3735 if (show_tabs == 0) {
3736 show_tabs = 1;
3737 notebook_tab_set_visibility(notebook);
3739 break;
3740 case XT_TAB_HIDE:
3741 if (show_tabs == 1) {
3742 show_tabs = 0;
3743 notebook_tab_set_visibility(notebook);
3745 break;
3746 case XT_TAB_UNDO_CLOSE:
3747 if (undo_count == 0) {
3748 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3749 goto done;
3750 } else {
3751 undo_count--;
3752 u = TAILQ_FIRST(&undos);
3753 create_new_tab(u->uri, u, 1, -1);
3755 TAILQ_REMOVE(&undos, u, entry);
3756 g_free(u->uri);
3757 /* u->history is freed in create_new_tab() */
3758 g_free(u);
3760 break;
3761 default:
3762 rv = XT_CB_PASSTHROUGH;
3763 goto done;
3766 done:
3767 if (args->s) {
3768 g_free(args->s);
3769 args->s = NULL;
3772 return (rv);
3776 resizetab(struct tab *t, struct karg *args)
3778 if (t == NULL || args == NULL) {
3779 show_oops_s("resizetab invalid parameters");
3780 return (XT_CB_PASSTHROUGH);
3783 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3784 t->tab_id, args->i);
3786 adjustfont_webkit(t, args->i);
3788 return (XT_CB_HANDLED);
3792 movetab(struct tab *t, struct karg *args)
3794 int n, dest;
3796 if (t == NULL || args == NULL) {
3797 show_oops_s("movetab invalid parameters");
3798 return (XT_CB_PASSTHROUGH);
3801 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3802 t->tab_id, args->i);
3804 if (args->i >= XT_TAB_INVALID)
3805 return (XT_CB_PASSTHROUGH);
3807 if (TAILQ_EMPTY(&tabs))
3808 return (XT_CB_PASSTHROUGH);
3810 n = gtk_notebook_get_n_pages(notebook);
3811 dest = gtk_notebook_get_current_page(notebook);
3813 switch (args->i) {
3814 case XT_TAB_NEXT:
3815 if (args->p < 0)
3816 dest = dest == n - 1 ? 0 : dest + 1;
3817 else
3818 dest = args->p - 1;
3820 break;
3821 case XT_TAB_PREV:
3822 if (args->p < 0)
3823 dest -= 1;
3824 else
3825 dest -= args->p % n;
3827 if (dest < 0)
3828 dest += n;
3830 break;
3831 case XT_TAB_FIRST:
3832 dest = 0;
3833 break;
3834 case XT_TAB_LAST:
3835 dest = n - 1;
3836 break;
3837 default:
3838 return (XT_CB_PASSTHROUGH);
3841 if (dest < 0 || dest >= n)
3842 return (XT_CB_PASSTHROUGH);
3843 if (t->tab_id == dest) {
3844 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3845 return (XT_CB_HANDLED);
3848 gtk_notebook_set_current_page(notebook, dest);
3850 return (XT_CB_HANDLED);
3853 int cmd_prefix = 0;
3856 command(struct tab *t, struct karg *args)
3858 char *s = NULL, *ss = NULL;
3859 GdkColor color;
3860 const gchar *uri;
3862 if (t == NULL || args == NULL) {
3863 show_oops_s("command invalid parameters");
3864 return (XT_CB_PASSTHROUGH);
3867 switch (args->i) {
3868 case '/':
3869 s = "/";
3870 break;
3871 case '?':
3872 s = "?";
3873 break;
3874 case ':':
3875 if (cmd_prefix == 0)
3876 s = ":";
3877 else {
3878 ss = g_strdup_printf(":%d", cmd_prefix);
3879 s = ss;
3880 cmd_prefix = 0;
3882 break;
3883 case XT_CMD_OPEN:
3884 s = ":open ";
3885 break;
3886 case XT_CMD_TABNEW:
3887 s = ":tabnew ";
3888 break;
3889 case XT_CMD_OPEN_CURRENT:
3890 s = ":open ";
3891 /* FALL THROUGH */
3892 case XT_CMD_TABNEW_CURRENT:
3893 if (!s) /* FALL THROUGH? */
3894 s = ":tabnew ";
3895 if ((uri = get_uri(t->wv)) != NULL) {
3896 ss = g_strdup_printf("%s%s", s, uri);
3897 s = ss;
3899 break;
3900 default:
3901 show_oops(t, "command: invalid opcode %d", args->i);
3902 return (XT_CB_PASSTHROUGH);
3905 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3907 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3908 gdk_color_parse(XT_COLOR_WHITE, &color);
3909 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3910 show_cmd(t);
3911 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3912 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3914 if (ss)
3915 g_free(ss);
3917 return (XT_CB_HANDLED);
3921 * Return a new string with a download row (in html)
3922 * appended. Old string is freed.
3924 char *
3925 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3928 WebKitDownloadStatus stat;
3929 char *status_html = NULL, *cmd_html = NULL, *new_html;
3930 gdouble progress;
3931 char cur_sz[FMT_SCALED_STRSIZE];
3932 char tot_sz[FMT_SCALED_STRSIZE];
3933 char *xtp_prefix;
3935 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3937 /* All actions wil take this form:
3938 * xxxt://class/seskey
3940 xtp_prefix = g_strdup_printf("%s%d/%s/",
3941 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3943 stat = webkit_download_get_status(dl->download);
3945 switch (stat) {
3946 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3947 status_html = g_strdup_printf("Finished");
3948 cmd_html = g_strdup_printf(
3949 "<a href='%s%d/%d'>Remove</a>",
3950 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3951 break;
3952 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3953 /* gather size info */
3954 progress = 100 * webkit_download_get_progress(dl->download);
3956 fmt_scaled(
3957 webkit_download_get_current_size(dl->download), cur_sz);
3958 fmt_scaled(
3959 webkit_download_get_total_size(dl->download), tot_sz);
3961 status_html = g_strdup_printf(
3962 "<div style='width: 100%%' align='center'>"
3963 "<div class='progress-outer'>"
3964 "<div class='progress-inner' style='width: %.2f%%'>"
3965 "</div></div></div>"
3966 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3967 progress, cur_sz, tot_sz, progress);
3969 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3970 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3972 break;
3973 /* LLL */
3974 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3975 status_html = g_strdup_printf("Cancelled");
3976 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3977 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3978 break;
3979 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3980 status_html = g_strdup_printf("Error!");
3981 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3982 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3983 break;
3984 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3985 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3986 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3987 status_html = g_strdup_printf("Starting");
3988 break;
3989 default:
3990 show_oops(t, "%s: unknown download status", __func__);
3993 new_html = g_strdup_printf(
3994 "%s\n<tr><td>%s</td><td>%s</td>"
3995 "<td style='text-align:center'>%s</td></tr>\n",
3996 html, basename(webkit_download_get_destination_uri(dl->download)),
3997 status_html, cmd_html);
3998 g_free(html);
4000 if (status_html)
4001 g_free(status_html);
4003 if (cmd_html)
4004 g_free(cmd_html);
4006 g_free(xtp_prefix);
4008 return new_html;
4012 * update all download tabs apart from one. Pass NULL if
4013 * you want to update all.
4015 void
4016 update_download_tabs(struct tab *apart_from)
4018 struct tab *t;
4019 if (!updating_dl_tabs) {
4020 updating_dl_tabs = 1; /* stop infinite recursion */
4021 TAILQ_FOREACH(t, &tabs, entry)
4022 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4023 && (t != apart_from))
4024 xtp_page_dl(t, NULL);
4025 updating_dl_tabs = 0;
4030 * update all cookie tabs apart from one. Pass NULL if
4031 * you want to update all.
4033 void
4034 update_cookie_tabs(struct tab *apart_from)
4036 struct tab *t;
4037 if (!updating_cl_tabs) {
4038 updating_cl_tabs = 1; /* stop infinite recursion */
4039 TAILQ_FOREACH(t, &tabs, entry)
4040 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4041 && (t != apart_from))
4042 xtp_page_cl(t, NULL);
4043 updating_cl_tabs = 0;
4048 * update all history tabs apart from one. Pass NULL if
4049 * you want to update all.
4051 void
4052 update_history_tabs(struct tab *apart_from)
4054 struct tab *t;
4056 if (!updating_hl_tabs) {
4057 updating_hl_tabs = 1; /* stop infinite recursion */
4058 TAILQ_FOREACH(t, &tabs, entry)
4059 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4060 && (t != apart_from))
4061 xtp_page_hl(t, NULL);
4062 updating_hl_tabs = 0;
4066 /* cookie management XTP page */
4068 xtp_page_cl(struct tab *t, struct karg *args)
4070 char *body, *page, *tmp;
4071 int i = 1; /* all ids start 1 */
4072 GSList *sc, *pc, *pc_start;
4073 SoupCookie *c;
4074 char *type, *table_headers;
4075 char *last_domain = strdup("");
4077 DNPRINTF(XT_D_CMD, "%s", __func__);
4079 if (t == NULL) {
4080 show_oops_s("%s invalid parameters", __func__);
4081 return (1);
4083 /* mark this tab as cookie jar */
4084 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4086 /* Generate a new session key */
4087 if (!updating_cl_tabs)
4088 generate_xtp_session_key(&cl_session_key);
4090 /* table headers */
4091 table_headers = g_strdup_printf("<table><tr>"
4092 "<th>Type</th>"
4093 "<th>Name</th>"
4094 "<th style='width:200px'>Value</th>"
4095 "<th>Path</th>"
4096 "<th>Expires</th>"
4097 "<th>Secure</th>"
4098 "<th>HTTP<br />only</th>"
4099 "<th style='width:40px'>Rm</th></tr>\n");
4101 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4102 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4103 pc_start = pc;
4105 body = NULL;
4106 for (; sc; sc = sc->next) {
4107 c = sc->data;
4109 if (strcmp(last_domain, c->domain) != 0) {
4110 /* new domain */
4111 free(last_domain);
4112 last_domain = strdup(c->domain);
4114 if (body != NULL) {
4115 tmp = body;
4116 body = g_strdup_printf("%s</table>"
4117 "<h2>%s</h2>%s\n",
4118 body, c->domain, table_headers);
4119 g_free(tmp);
4120 } else {
4121 /* first domain */
4122 body = g_strdup_printf("<h2>%s</h2>%s\n",
4123 c->domain, table_headers);
4127 type = "Session";
4128 for (pc = pc_start; pc; pc = pc->next)
4129 if (soup_cookie_equal(pc->data, c)) {
4130 type = "Session + Persistent";
4131 break;
4134 tmp = body;
4135 body = g_strdup_printf(
4136 "%s\n<tr>"
4137 "<td>%s</td>"
4138 "<td style='word-wrap:normal'>%s</td>"
4139 "<td>"
4140 " <textarea rows='4'>%s</textarea>"
4141 "</td>"
4142 "<td>%s</td>"
4143 "<td>%s</td>"
4144 "<td>%d</td>"
4145 "<td>%d</td>"
4146 "<td style='text-align:center'>"
4147 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4148 body,
4149 type,
4150 c->name,
4151 c->value,
4152 c->path,
4153 c->expires ?
4154 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4155 c->secure,
4156 c->http_only,
4158 XT_XTP_STR,
4159 XT_XTP_CL,
4160 cl_session_key,
4161 XT_XTP_CL_REMOVE,
4165 g_free(tmp);
4166 i++;
4169 soup_cookies_free(sc);
4170 soup_cookies_free(pc);
4172 /* small message if there are none */
4173 if (i == 1) {
4174 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4175 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4177 tmp = body;
4178 body = g_strdup_printf("%s</table>", body);
4179 g_free(tmp);
4181 page = get_html_page("Cookie Jar", body, "", TRUE);
4182 g_free(body);
4183 g_free(table_headers);
4184 g_free(last_domain);
4186 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4187 update_cookie_tabs(t);
4189 g_free(page);
4191 return (0);
4195 xtp_page_hl(struct tab *t, struct karg *args)
4197 char *body, *page, *tmp;
4198 struct history *h;
4199 int i = 1; /* all ids start 1 */
4201 DNPRINTF(XT_D_CMD, "%s", __func__);
4203 if (t == NULL) {
4204 show_oops_s("%s invalid parameters", __func__);
4205 return (1);
4208 /* mark this tab as history manager */
4209 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4211 /* Generate a new session key */
4212 if (!updating_hl_tabs)
4213 generate_xtp_session_key(&hl_session_key);
4215 /* body */
4216 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4217 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4219 RB_FOREACH_REVERSE(h, history_list, &hl) {
4220 tmp = body;
4221 body = g_strdup_printf(
4222 "%s\n<tr>"
4223 "<td><a href='%s'>%s</a></td>"
4224 "<td>%s</td>"
4225 "<td style='text-align: center'>"
4226 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4227 body, h->uri, h->uri, h->title,
4228 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4229 XT_XTP_HL_REMOVE, i);
4231 g_free(tmp);
4232 i++;
4235 /* small message if there are none */
4236 if (i == 1) {
4237 tmp = body;
4238 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4239 "colspan='3'>No History</td></tr>\n", body);
4240 g_free(tmp);
4243 tmp = body;
4244 body = g_strdup_printf("%s</table>", body);
4245 g_free(tmp);
4247 page = get_html_page("History", body, "", TRUE);
4248 g_free(body);
4251 * update all history manager tabs as the xtp session
4252 * key has now changed. No need to update the current tab.
4253 * Already did that above.
4255 update_history_tabs(t);
4257 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4258 g_free(page);
4260 return (0);
4264 * Generate a web page detailing the status of any downloads
4267 xtp_page_dl(struct tab *t, struct karg *args)
4269 struct download *dl;
4270 char *body, *page, *tmp;
4271 char *ref;
4272 int n_dl = 1;
4274 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4276 if (t == NULL) {
4277 show_oops_s("%s invalid parameters", __func__);
4278 return (1);
4280 /* mark as a download manager tab */
4281 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4284 * Generate a new session key for next page instance.
4285 * This only happens for the top level call to xtp_page_dl()
4286 * in which case updating_dl_tabs is 0.
4288 if (!updating_dl_tabs)
4289 generate_xtp_session_key(&dl_session_key);
4291 /* header - with refresh so as to update */
4292 if (refresh_interval >= 1)
4293 ref = g_strdup_printf(
4294 "<meta http-equiv='refresh' content='%u"
4295 ";url=%s%d/%s/%d' />\n",
4296 refresh_interval,
4297 XT_XTP_STR,
4298 XT_XTP_DL,
4299 dl_session_key,
4300 XT_XTP_DL_LIST);
4301 else
4302 ref = g_strdup("");
4304 body = g_strdup_printf("<div align='center'>"
4305 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4306 "</p><table><tr><th style='width: 60%%'>"
4307 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4308 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4310 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4311 body = xtp_page_dl_row(t, body, dl);
4312 n_dl++;
4315 /* message if no downloads in list */
4316 if (n_dl == 1) {
4317 tmp = body;
4318 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4319 " style='text-align: center'>"
4320 "No downloads</td></tr>\n", body);
4321 g_free(tmp);
4324 tmp = body;
4325 body = g_strdup_printf("%s</table></div>", body);
4326 g_free(tmp);
4328 page = get_html_page("Downloads", body, ref, 1);
4329 g_free(ref);
4330 g_free(body);
4333 * update all download manager tabs as the xtp session
4334 * key has now changed. No need to update the current tab.
4335 * Already did that above.
4337 update_download_tabs(t);
4339 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4340 g_free(page);
4342 return (0);
4346 search(struct tab *t, struct karg *args)
4348 gboolean d;
4350 if (t == NULL || args == NULL) {
4351 show_oops_s("search invalid parameters");
4352 return (1);
4354 if (t->search_text == NULL) {
4355 if (global_search == NULL)
4356 return (XT_CB_PASSTHROUGH);
4357 else {
4358 t->search_text = g_strdup(global_search);
4359 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4360 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4364 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4365 t->tab_id, args->i, t->search_forward, t->search_text);
4367 switch (args->i) {
4368 case XT_SEARCH_NEXT:
4369 d = t->search_forward;
4370 break;
4371 case XT_SEARCH_PREV:
4372 d = !t->search_forward;
4373 break;
4374 default:
4375 return (XT_CB_PASSTHROUGH);
4378 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4380 return (XT_CB_HANDLED);
4383 struct settings_args {
4384 char **body;
4385 int i;
4388 void
4389 print_setting(struct settings *s, char *val, void *cb_args)
4391 char *tmp, *color;
4392 struct settings_args *sa = cb_args;
4394 if (sa == NULL)
4395 return;
4397 if (s->flags & XT_SF_RUNTIME)
4398 color = "#22cc22";
4399 else
4400 color = "#cccccc";
4402 tmp = *sa->body;
4403 *sa->body = g_strdup_printf(
4404 "%s\n<tr>"
4405 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4406 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4407 *sa->body,
4408 color,
4409 s->name,
4410 color,
4413 g_free(tmp);
4414 sa->i++;
4418 set(struct tab *t, struct karg *args)
4420 char *body, *page, *tmp;
4421 int i = 1;
4422 struct settings_args sa;
4424 bzero(&sa, sizeof sa);
4425 sa.body = &body;
4427 /* body */
4428 body = g_strdup_printf("<div align='center'><table><tr>"
4429 "<th align='left'>Setting</th>"
4430 "<th align='left'>Value</th></tr>\n");
4432 settings_walk(print_setting, &sa);
4433 i = sa.i;
4435 /* small message if there are none */
4436 if (i == 1) {
4437 tmp = body;
4438 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4439 "colspan='2'>No settings</td></tr>\n", body);
4440 g_free(tmp);
4443 tmp = body;
4444 body = g_strdup_printf("%s</table></div>", body);
4445 g_free(tmp);
4447 page = get_html_page("Settings", body, "", 0);
4449 g_free(body);
4451 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4453 g_free(page);
4455 return (XT_CB_PASSTHROUGH);
4459 session_save(struct tab *t, char *filename)
4461 struct karg a;
4462 int rv = 1;
4464 if (strlen(filename) == 0)
4465 goto done;
4467 if (filename[0] == '.' || filename[0] == '/')
4468 goto done;
4470 a.s = filename;
4471 if (save_tabs(t, &a))
4472 goto done;
4473 strlcpy(named_session, filename, sizeof named_session);
4475 rv = 0;
4476 done:
4477 return (rv);
4481 session_open(struct tab *t, char *filename)
4483 struct karg a;
4484 int rv = 1;
4486 if (strlen(filename) == 0)
4487 goto done;
4489 if (filename[0] == '.' || filename[0] == '/')
4490 goto done;
4492 a.s = filename;
4493 a.i = XT_SES_CLOSETABS;
4494 if (open_tabs(t, &a))
4495 goto done;
4497 strlcpy(named_session, filename, sizeof named_session);
4499 rv = 0;
4500 done:
4501 return (rv);
4505 session_delete(struct tab *t, char *filename)
4507 char file[PATH_MAX];
4508 int rv = 1;
4510 if (strlen(filename) == 0)
4511 goto done;
4513 if (filename[0] == '.' || filename[0] == '/')
4514 goto done;
4516 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4517 if (unlink(file))
4518 goto done;
4520 if (!strcmp(filename, named_session))
4521 strlcpy(named_session, XT_SAVED_TABS_FILE,
4522 sizeof named_session);
4524 rv = 0;
4525 done:
4526 return (rv);
4530 session_cmd(struct tab *t, struct karg *args)
4532 char *filename = args->s;
4534 if (t == NULL)
4535 return (1);
4537 if (args->i & XT_SHOW)
4538 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4539 XT_SAVED_TABS_FILE : named_session);
4540 else if (args->i & XT_SAVE) {
4541 if (session_save(t, filename)) {
4542 show_oops(t, "Can't save session: %s",
4543 filename ? filename : "INVALID");
4544 goto done;
4546 } else if (args->i & XT_OPEN) {
4547 if (session_open(t, filename)) {
4548 show_oops(t, "Can't open session: %s",
4549 filename ? filename : "INVALID");
4550 goto done;
4552 } else if (args->i & XT_DELETE) {
4553 if (session_delete(t, filename)) {
4554 show_oops(t, "Can't delete session: %s",
4555 filename ? filename : "INVALID");
4556 goto done;
4559 done:
4560 return (XT_CB_PASSTHROUGH);
4564 * Make a hardcopy of the page
4567 print_page(struct tab *t, struct karg *args)
4569 WebKitWebFrame *frame;
4570 GtkPageSetup *ps;
4571 GtkPrintOperation *op;
4572 GtkPrintOperationAction action;
4573 GtkPrintOperationResult print_res;
4574 GError *g_err = NULL;
4575 int marg_l, marg_r, marg_t, marg_b;
4577 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4579 ps = gtk_page_setup_new();
4580 op = gtk_print_operation_new();
4581 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4582 frame = webkit_web_view_get_main_frame(t->wv);
4584 /* the default margins are too small, so we will bump them */
4585 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4586 XT_PRINT_EXTRA_MARGIN;
4587 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4588 XT_PRINT_EXTRA_MARGIN;
4589 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4590 XT_PRINT_EXTRA_MARGIN;
4591 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4592 XT_PRINT_EXTRA_MARGIN;
4594 /* set margins */
4595 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4596 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4597 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4598 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4600 gtk_print_operation_set_default_page_setup(op, ps);
4602 /* this appears to free 'op' and 'ps' */
4603 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4605 /* check it worked */
4606 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4607 show_oops_s("can't print: %s", g_err->message);
4608 g_error_free (g_err);
4609 return (1);
4612 return (0);
4616 go_home(struct tab *t, struct karg *args)
4618 load_uri(t, home);
4619 return (0);
4623 restart(struct tab *t, struct karg *args)
4625 struct karg a;
4627 a.s = XT_RESTART_TABS_FILE;
4628 save_tabs(t, &a);
4629 execvp(start_argv[0], start_argv);
4630 /* NOTREACHED */
4632 return (0);
4635 #define CTRL GDK_CONTROL_MASK
4636 #define MOD1 GDK_MOD1_MASK
4637 #define SHFT GDK_SHIFT_MASK
4639 /* inherent to GTK not all keys will be caught at all times */
4640 /* XXX sort key bindings */
4641 struct key_binding {
4642 char *cmd;
4643 guint mask;
4644 guint use_in_entry;
4645 guint key;
4646 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4647 } keys[] = {
4648 { "cookiejar", MOD1, 0, GDK_j },
4649 { "downloadmgr", MOD1, 0, GDK_d },
4650 { "history", MOD1, 0, GDK_h },
4651 { "print", CTRL, 0, GDK_p },
4652 { "search", 0, 0, GDK_slash },
4653 { "searchb", 0, 0, GDK_question },
4654 { "command", 0, 0, GDK_colon },
4655 { "qa", CTRL, 0, GDK_q },
4656 { "restart", MOD1, 0, GDK_q },
4657 { "js toggle", CTRL, 0, GDK_j },
4658 { "cookie toggle", MOD1, 0, GDK_c },
4659 { "togglesrc", CTRL, 0, GDK_s },
4660 { "yankuri", 0, 0, GDK_y },
4661 { "pasteuricur", 0, 0, GDK_p },
4662 { "pasteurinew", 0, 0, GDK_P },
4664 /* search */
4665 { "searchnext", 0, 0, GDK_n },
4666 { "searchprevious", 0, 0, GDK_N },
4668 /* focus */
4669 { "focusaddress", 0, 0, GDK_F6 },
4670 { "focussearch", 0, 0, GDK_F7 },
4672 /* hinting */
4673 { "hinting", 0, 0, GDK_f },
4675 /* custom stylesheet */
4676 { "userstyle", 0, 0, GDK_i },
4678 /* navigation */
4679 { "goback", 0, 0, GDK_BackSpace },
4680 { "goback", MOD1, 0, GDK_Left },
4681 { "goforward", SHFT, 0, GDK_BackSpace },
4682 { "goforward", MOD1, 0, GDK_Right },
4683 { "reload", 0, 0, GDK_F5 },
4684 { "reload", CTRL, 0, GDK_r },
4685 { "reloadforce", CTRL, 0, GDK_R },
4686 { "reload", CTRL, 0, GDK_l },
4687 { "favorites", MOD1, 1, GDK_f },
4689 /* vertical movement */
4690 { "scrolldown", 0, 0, GDK_j },
4691 { "scrolldown", 0, 0, GDK_Down },
4692 { "scrollup", 0, 0, GDK_Up },
4693 { "scrollup", 0, 0, GDK_k },
4694 { "scrollbottom", 0, 0, GDK_G },
4695 { "scrollbottom", 0, 0, GDK_End },
4696 { "scrolltop", 0, 0, GDK_Home },
4697 { "scrolltop", 0, 0, GDK_g },
4698 { "scrollpagedown", 0, 0, GDK_space },
4699 { "scrollpagedown", CTRL, 0, GDK_f },
4700 { "scrollhalfdown", CTRL, 0, GDK_d },
4701 { "scrollpagedown", 0, 0, GDK_Page_Down },
4702 { "scrollpageup", 0, 0, GDK_Page_Up },
4703 { "scrollpageup", CTRL, 0, GDK_b },
4704 { "scrollhalfup", CTRL, 0, GDK_u },
4705 /* horizontal movement */
4706 { "scrollright", 0, 0, GDK_l },
4707 { "scrollright", 0, 0, GDK_Right },
4708 { "scrollleft", 0, 0, GDK_Left },
4709 { "scrollleft", 0, 0, GDK_h },
4710 { "scrollfarright", 0, 0, GDK_dollar },
4711 { "scrollfarleft", 0, 0, GDK_0 },
4713 /* tabs */
4714 { "tabnew", CTRL, 0, GDK_t },
4715 { "999tabnew", CTRL, 0, GDK_T },
4716 { "tabclose", CTRL, 1, GDK_w },
4717 { "tabundoclose", 0, 0, GDK_U },
4718 { "tabnext 1", CTRL, 0, GDK_1 },
4719 { "tabnext 2", CTRL, 0, GDK_2 },
4720 { "tabnext 3", CTRL, 0, GDK_3 },
4721 { "tabnext 4", CTRL, 0, GDK_4 },
4722 { "tabnext 5", CTRL, 0, GDK_5 },
4723 { "tabnext 6", CTRL, 0, GDK_6 },
4724 { "tabnext 7", CTRL, 0, GDK_7 },
4725 { "tabnext 8", CTRL, 0, GDK_8 },
4726 { "tabnext 9", CTRL, 0, GDK_9 },
4727 { "tabnext 10", CTRL, 0, GDK_0 },
4728 { "tabfirst", CTRL, 0, GDK_less },
4729 { "tablast", CTRL, 0, GDK_greater },
4730 { "tabprevious", CTRL, 0, GDK_Left },
4731 { "tabnext", CTRL, 0, GDK_Right },
4732 { "focusout", CTRL, 0, GDK_minus },
4733 { "focusin", CTRL, 0, GDK_plus },
4734 { "focusin", CTRL, 0, GDK_equal },
4736 /* command aliases (handy when -S flag is used) */
4737 { "promptopen", 0, 0, GDK_F9 },
4738 { "promptopencurrent", 0, 0, GDK_F10 },
4739 { "prompttabnew", 0, 0, GDK_F11 },
4740 { "prompttabnewcurrent",0, 0, GDK_F12 },
4742 TAILQ_HEAD(keybinding_list, key_binding);
4744 void
4745 walk_kb(struct settings *s,
4746 void (*cb)(struct settings *, char *, void *), void *cb_args)
4748 struct key_binding *k;
4749 char str[1024];
4751 if (s == NULL || cb == NULL) {
4752 show_oops_s("walk_kb invalid parameters");
4753 return;
4756 TAILQ_FOREACH(k, &kbl, entry) {
4757 if (k->cmd == NULL)
4758 continue;
4759 str[0] = '\0';
4761 /* sanity */
4762 if (gdk_keyval_name(k->key) == NULL)
4763 continue;
4765 strlcat(str, k->cmd, sizeof str);
4766 strlcat(str, ",", sizeof str);
4768 if (k->mask & GDK_SHIFT_MASK)
4769 strlcat(str, "S-", sizeof str);
4770 if (k->mask & GDK_CONTROL_MASK)
4771 strlcat(str, "C-", sizeof str);
4772 if (k->mask & GDK_MOD1_MASK)
4773 strlcat(str, "M1-", sizeof str);
4774 if (k->mask & GDK_MOD2_MASK)
4775 strlcat(str, "M2-", sizeof str);
4776 if (k->mask & GDK_MOD3_MASK)
4777 strlcat(str, "M3-", sizeof str);
4778 if (k->mask & GDK_MOD4_MASK)
4779 strlcat(str, "M4-", sizeof str);
4780 if (k->mask & GDK_MOD5_MASK)
4781 strlcat(str, "M5-", sizeof str);
4783 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4784 cb(s, str, cb_args);
4788 void
4789 init_keybindings(void)
4791 int i;
4792 struct key_binding *k;
4794 for (i = 0; i < LENGTH(keys); i++) {
4795 k = g_malloc0(sizeof *k);
4796 k->cmd = keys[i].cmd;
4797 k->mask = keys[i].mask;
4798 k->use_in_entry = keys[i].use_in_entry;
4799 k->key = keys[i].key;
4800 TAILQ_INSERT_HEAD(&kbl, k, entry);
4802 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4803 k->cmd ? k->cmd : "unnamed key");
4807 void
4808 keybinding_clearall(void)
4810 struct key_binding *k, *next;
4812 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4813 next = TAILQ_NEXT(k, entry);
4814 if (k->cmd == NULL)
4815 continue;
4817 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4818 k->cmd ? k->cmd : "unnamed key");
4819 TAILQ_REMOVE(&kbl, k, entry);
4820 g_free(k);
4825 keybinding_add(char *cmd, char *key, int use_in_entry)
4827 struct key_binding *k;
4828 guint keyval, mask = 0;
4829 int i;
4831 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4833 /* Keys which are to be used in entry have been prefixed with an
4834 * exclamation mark. */
4835 if (use_in_entry)
4836 key++;
4838 /* find modifier keys */
4839 if (strstr(key, "S-"))
4840 mask |= GDK_SHIFT_MASK;
4841 if (strstr(key, "C-"))
4842 mask |= GDK_CONTROL_MASK;
4843 if (strstr(key, "M1-"))
4844 mask |= GDK_MOD1_MASK;
4845 if (strstr(key, "M2-"))
4846 mask |= GDK_MOD2_MASK;
4847 if (strstr(key, "M3-"))
4848 mask |= GDK_MOD3_MASK;
4849 if (strstr(key, "M4-"))
4850 mask |= GDK_MOD4_MASK;
4851 if (strstr(key, "M5-"))
4852 mask |= GDK_MOD5_MASK;
4854 /* find keyname */
4855 for (i = strlen(key) - 1; i > 0; i--)
4856 if (key[i] == '-')
4857 key = &key[i + 1];
4859 /* validate keyname */
4860 keyval = gdk_keyval_from_name(key);
4861 if (keyval == GDK_VoidSymbol) {
4862 warnx("invalid keybinding name %s", key);
4863 return (1);
4865 /* must run this test too, gtk+ doesn't handle 10 for example */
4866 if (gdk_keyval_name(keyval) == NULL) {
4867 warnx("invalid keybinding name %s", key);
4868 return (1);
4871 /* Remove eventual dupes. */
4872 TAILQ_FOREACH(k, &kbl, entry)
4873 if (k->key == keyval && k->mask == mask) {
4874 TAILQ_REMOVE(&kbl, k, entry);
4875 g_free(k);
4876 break;
4879 /* add keyname */
4880 k = g_malloc0(sizeof *k);
4881 k->cmd = g_strdup(cmd);
4882 k->mask = mask;
4883 k->use_in_entry = use_in_entry;
4884 k->key = keyval;
4886 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4887 k->cmd,
4888 k->mask,
4889 k->use_in_entry,
4890 k->key);
4891 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4892 k->cmd, gdk_keyval_name(keyval));
4894 TAILQ_INSERT_HEAD(&kbl, k, entry);
4896 return (0);
4900 add_kb(struct settings *s, char *entry)
4902 char *kb, *key;
4904 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4906 /* clearall is special */
4907 if (!strcmp(entry, "clearall")) {
4908 keybinding_clearall();
4909 return (0);
4912 kb = strstr(entry, ",");
4913 if (kb == NULL)
4914 return (1);
4915 *kb = '\0';
4916 key = kb + 1;
4918 return (keybinding_add(entry, key, key[0] == '!'));
4921 struct cmd {
4922 char *cmd;
4923 int level;
4924 int (*func)(struct tab *, struct karg *);
4925 int arg;
4926 int type;
4927 } cmds[] = {
4928 { "command", 0, command, ':', 0 },
4929 { "search", 0, command, '/', 0 },
4930 { "searchb", 0, command, '?', 0 },
4931 { "togglesrc", 0, toggle_src, 0, 0 },
4933 /* yanking and pasting */
4934 { "yankuri", 0, yank_uri, 0, 0 },
4935 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4936 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
4937 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
4939 /* search */
4940 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
4941 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
4943 /* focus */
4944 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
4945 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
4947 /* hinting */
4948 { "hinting", 0, hint, 0, 0 },
4950 /* custom stylesheet */
4951 { "userstyle", 0, userstyle, 0, 0 },
4953 /* navigation */
4954 { "goback", 0, navaction, XT_NAV_BACK, 0 },
4955 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
4956 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
4957 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
4959 /* vertical movement */
4960 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
4961 { "scrollup", 0, move, XT_MOVE_UP, 0 },
4962 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
4963 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
4964 { "1", 0, move, XT_MOVE_TOP, 0 },
4965 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
4966 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
4967 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
4968 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
4969 /* horizontal movement */
4970 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
4971 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
4972 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
4973 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
4976 { "favorites", 0, xtp_page_fl, 0, 0 },
4977 { "fav", 0, xtp_page_fl, 0, 0 },
4978 { "favadd", 0, add_favorite, 0, 0 },
4980 { "qall", 0, quit, 0, 0 },
4981 { "quitall", 0, quit, 0, 0 },
4982 { "w", 0, save_tabs, 0, 0 },
4983 { "wq", 0, save_tabs_and_quit, 0, 0 },
4984 { "help", 0, help, 0, 0 },
4985 { "about", 0, about, 0, 0 },
4986 { "stats", 0, stats, 0, 0 },
4987 { "version", 0, about, 0, 0 },
4989 /* js command */
4990 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
4991 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
4992 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
4993 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
4994 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
4995 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
4996 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
4997 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
4998 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
4999 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5000 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5002 /* cookie command */
5003 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5004 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5005 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5006 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5007 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5008 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5009 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5010 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5011 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5012 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5013 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5015 /* cookie jar */
5016 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5018 /* cert command */
5019 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5020 { "save", 1, cert_cmd, XT_SAVE, 0 },
5021 { "show", 1, cert_cmd, XT_SHOW, 0 },
5023 { "ca", 0, ca_cmd, 0, 0 },
5024 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5025 { "dl", 0, xtp_page_dl, 0, 0 },
5026 { "h", 0, xtp_page_hl, 0, 0 },
5027 { "history", 0, xtp_page_hl, 0, 0 },
5028 { "home", 0, go_home, 0, 0 },
5029 { "restart", 0, restart, 0, 0 },
5030 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5031 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5032 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5033 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5035 { "print", 0, print_page, 0, 0 },
5037 /* tabs */
5038 { "focusin", 0, resizetab, 1, 0 },
5039 { "focusout", 0, resizetab, -1, 0 },
5040 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5041 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5042 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5043 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5044 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5045 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5046 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5047 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5048 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5049 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5050 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5051 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5052 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5053 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5055 /* command aliases (handy when -S flag is used) */
5056 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5057 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5058 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5059 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5061 /* settings */
5062 { "set", 0, set, 0, 0 },
5063 { "fullscreen", 0, fullscreen, 0, 0 },
5064 { "f", 0, fullscreen, 0, 0 },
5066 /* sessions */
5067 { "session", 0, session_cmd, XT_SHOW, 0 },
5068 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5069 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5070 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5071 { "show", 1, session_cmd, XT_SHOW, 0 },
5074 struct {
5075 int index;
5076 int len;
5077 gchar *list[256];
5078 } cmd_status = {-1, 0};
5080 gboolean
5081 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5083 struct karg a;
5085 hide_oops(t);
5087 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5088 /* go backward */
5089 a.i = XT_NAV_BACK;
5090 navaction(t, &a);
5092 return (TRUE);
5093 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5094 /* go forward */
5095 a.i = XT_NAV_FORWARD;
5096 navaction(t, &a);
5098 return (TRUE);
5101 return (FALSE);
5104 gboolean
5105 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5107 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5109 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5110 delete_tab(t);
5112 return (FALSE);
5116 * cancel, remove, etc. downloads
5118 void
5119 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5121 struct download find, *d = NULL;
5123 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5125 /* some commands require a valid download id */
5126 if (cmd != XT_XTP_DL_LIST) {
5127 /* lookup download in question */
5128 find.id = id;
5129 d = RB_FIND(download_list, &downloads, &find);
5131 if (d == NULL) {
5132 show_oops(t, "%s: no such download", __func__);
5133 return;
5137 /* decide what to do */
5138 switch (cmd) {
5139 case XT_XTP_DL_CANCEL:
5140 webkit_download_cancel(d->download);
5141 break;
5142 case XT_XTP_DL_REMOVE:
5143 webkit_download_cancel(d->download); /* just incase */
5144 g_object_unref(d->download);
5145 RB_REMOVE(download_list, &downloads, d);
5146 break;
5147 case XT_XTP_DL_LIST:
5148 /* Nothing */
5149 break;
5150 default:
5151 show_oops(t, "%s: unknown command", __func__);
5152 break;
5154 xtp_page_dl(t, NULL);
5158 * Actions on history, only does one thing for now, but
5159 * we provide the function for future actions
5161 void
5162 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5164 struct history *h, *next;
5165 int i = 1;
5167 switch (cmd) {
5168 case XT_XTP_HL_REMOVE:
5169 /* walk backwards, as listed in reverse */
5170 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5171 next = RB_PREV(history_list, &hl, h);
5172 if (id == i) {
5173 RB_REMOVE(history_list, &hl, h);
5174 g_free((gpointer) h->title);
5175 g_free((gpointer) h->uri);
5176 g_free(h);
5177 break;
5179 i++;
5181 break;
5182 case XT_XTP_HL_LIST:
5183 /* Nothing - just xtp_page_hl() below */
5184 break;
5185 default:
5186 show_oops(t, "%s: unknown command", __func__);
5187 break;
5190 xtp_page_hl(t, NULL);
5193 /* remove a favorite */
5194 void
5195 remove_favorite(struct tab *t, int index)
5197 char file[PATH_MAX], *title, *uri = NULL;
5198 char *new_favs, *tmp;
5199 FILE *f;
5200 int i;
5201 size_t len, lineno;
5203 /* open favorites */
5204 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5206 if ((f = fopen(file, "r")) == NULL) {
5207 show_oops(t, "%s: can't open favorites: %s",
5208 __func__, strerror(errno));
5209 return;
5212 /* build a string which will become the new favroites file */
5213 new_favs = g_strdup("");
5215 for (i = 1;;) {
5216 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5217 if (feof(f) || ferror(f))
5218 break;
5219 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5220 if (len == 0) {
5221 free(title);
5222 title = NULL;
5223 continue;
5226 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5227 if (feof(f) || ferror(f)) {
5228 show_oops(t, "%s: can't parse favorites %s",
5229 __func__, strerror(errno));
5230 goto clean;
5234 /* as long as this isn't the one we are deleting add to file */
5235 if (i != index) {
5236 tmp = new_favs;
5237 new_favs = g_strdup_printf("%s%s\n%s\n",
5238 new_favs, title, uri);
5239 g_free(tmp);
5242 free(uri);
5243 uri = NULL;
5244 free(title);
5245 title = NULL;
5246 i++;
5248 fclose(f);
5250 /* write back new favorites file */
5251 if ((f = fopen(file, "w")) == NULL) {
5252 show_oops(t, "%s: can't open favorites: %s",
5253 __func__, strerror(errno));
5254 goto clean;
5257 fwrite(new_favs, strlen(new_favs), 1, f);
5258 fclose(f);
5260 clean:
5261 if (uri)
5262 free(uri);
5263 if (title)
5264 free(title);
5266 g_free(new_favs);
5269 void
5270 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5272 switch (cmd) {
5273 case XT_XTP_FL_LIST:
5274 /* nothing, just the below call to xtp_page_fl() */
5275 break;
5276 case XT_XTP_FL_REMOVE:
5277 remove_favorite(t, arg);
5278 break;
5279 default:
5280 show_oops(t, "%s: invalid favorites command", __func__);
5281 break;
5284 xtp_page_fl(t, NULL);
5287 void
5288 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5290 switch (cmd) {
5291 case XT_XTP_CL_LIST:
5292 /* nothing, just xtp_page_cl() */
5293 break;
5294 case XT_XTP_CL_REMOVE:
5295 remove_cookie(arg);
5296 break;
5297 default:
5298 show_oops(t, "%s: unknown cookie xtp command", __func__);
5299 break;
5302 xtp_page_cl(t, NULL);
5305 /* link an XTP class to it's session key and handler function */
5306 struct xtp_despatch {
5307 uint8_t xtp_class;
5308 char **session_key;
5309 void (*handle_func)(struct tab *, uint8_t, int);
5312 struct xtp_despatch xtp_despatches[] = {
5313 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5314 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5315 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5316 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5317 { XT_XTP_INVALID, NULL, NULL }
5321 * is the url xtp protocol? (xxxt://)
5322 * if so, parse and despatch correct bahvior
5325 parse_xtp_url(struct tab *t, const char *url)
5327 char *dup = NULL, *p, *last;
5328 uint8_t n_tokens = 0;
5329 char *tokens[4] = {NULL, NULL, NULL, ""};
5330 struct xtp_despatch *dsp, *dsp_match = NULL;
5331 uint8_t req_class;
5332 int ret = FALSE;
5335 * tokens array meaning:
5336 * tokens[0] = class
5337 * tokens[1] = session key
5338 * tokens[2] = action
5339 * tokens[3] = optional argument
5342 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5344 /*xtp tab meaning is normal unless proven special */
5345 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5347 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5348 goto clean;
5350 dup = g_strdup(url + strlen(XT_XTP_STR));
5352 /* split out the url */
5353 for ((p = strtok_r(dup, "/", &last)); p;
5354 (p = strtok_r(NULL, "/", &last))) {
5355 if (n_tokens < 4)
5356 tokens[n_tokens++] = p;
5359 /* should be atleast three fields 'class/seskey/command/arg' */
5360 if (n_tokens < 3)
5361 goto clean;
5363 dsp = xtp_despatches;
5364 req_class = atoi(tokens[0]);
5365 while (dsp->xtp_class) {
5366 if (dsp->xtp_class == req_class) {
5367 dsp_match = dsp;
5368 break;
5370 dsp++;
5373 /* did we find one atall? */
5374 if (dsp_match == NULL) {
5375 show_oops(t, "%s: no matching xtp despatch found", __func__);
5376 goto clean;
5379 /* check session key and call despatch function */
5380 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5381 ret = TRUE; /* all is well, this was a valid xtp request */
5382 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5385 clean:
5386 if (dup)
5387 g_free(dup);
5389 return (ret);
5394 void
5395 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5397 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5399 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5401 if (t == NULL) {
5402 show_oops_s("activate_uri_entry_cb invalid parameters");
5403 return;
5406 if (uri == NULL) {
5407 show_oops(t, "activate_uri_entry_cb no uri");
5408 return;
5411 uri += strspn(uri, "\t ");
5413 /* if xxxt:// treat specially */
5414 if (parse_xtp_url(t, uri))
5415 return;
5417 /* otherwise continue to load page normally */
5418 load_uri(t, (gchar *)uri);
5419 focus_webview(t);
5422 void
5423 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5425 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5426 char *newuri = NULL;
5427 gchar *enc_search;
5429 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5431 if (t == NULL) {
5432 show_oops_s("activate_search_entry_cb invalid parameters");
5433 return;
5436 if (search_string == NULL) {
5437 show_oops(t, "no search_string");
5438 return;
5441 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5442 newuri = g_strdup_printf(search_string, enc_search);
5443 g_free(enc_search);
5445 webkit_web_view_load_uri(t->wv, newuri);
5446 focus_webview(t);
5448 if (newuri)
5449 g_free(newuri);
5452 void
5453 check_and_set_js(const gchar *uri, struct tab *t)
5455 struct domain *d = NULL;
5456 int es = 0;
5458 if (uri == NULL || t == NULL)
5459 return;
5461 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5462 es = 0;
5463 else
5464 es = 1;
5466 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5467 es ? "enable" : "disable", uri);
5469 g_object_set(G_OBJECT(t->settings),
5470 "enable-scripts", es, (char *)NULL);
5471 g_object_set(G_OBJECT(t->settings),
5472 "javascript-can-open-windows-automatically", es, (char *)NULL);
5473 webkit_web_view_set_settings(t->wv, t->settings);
5475 button_set_stockid(t->js_toggle,
5476 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5479 void
5480 show_ca_status(struct tab *t, const char *uri)
5482 WebKitWebFrame *frame;
5483 WebKitWebDataSource *source;
5484 WebKitNetworkRequest *request;
5485 SoupMessage *message;
5486 GdkColor color;
5487 gchar *col_str = XT_COLOR_WHITE;
5488 int r;
5490 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5491 ssl_strict_certs, ssl_ca_file, uri);
5493 if (uri == NULL)
5494 goto done;
5495 if (ssl_ca_file == NULL) {
5496 if (g_str_has_prefix(uri, "http://"))
5497 goto done;
5498 if (g_str_has_prefix(uri, "https://")) {
5499 col_str = XT_COLOR_RED;
5500 goto done;
5502 return;
5504 if (g_str_has_prefix(uri, "http://") ||
5505 !g_str_has_prefix(uri, "https://"))
5506 goto done;
5508 frame = webkit_web_view_get_main_frame(t->wv);
5509 source = webkit_web_frame_get_data_source(frame);
5510 request = webkit_web_data_source_get_request(source);
5511 message = webkit_network_request_get_message(request);
5513 if (message && (soup_message_get_flags(message) &
5514 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5515 col_str = XT_COLOR_GREEN;
5516 goto done;
5517 } else {
5518 r = load_compare_cert(t, NULL);
5519 if (r == 0)
5520 col_str = XT_COLOR_BLUE;
5521 else if (r == 1)
5522 col_str = XT_COLOR_YELLOW;
5523 else
5524 col_str = XT_COLOR_RED;
5525 goto done;
5527 done:
5528 if (col_str) {
5529 gdk_color_parse(col_str, &color);
5530 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5532 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5533 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5534 &color);
5535 gdk_color_parse(XT_COLOR_BLACK, &color);
5536 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5537 &color);
5538 } else {
5539 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5540 &color);
5541 gdk_color_parse(XT_COLOR_BLACK, &color);
5542 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5543 &color);
5548 void
5549 free_favicon(struct tab *t)
5551 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5552 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5554 if (t->icon_request)
5555 g_object_unref(t->icon_request);
5556 if (t->icon_pixbuf)
5557 g_object_unref(t->icon_pixbuf);
5558 if (t->icon_dest_uri)
5559 g_free(t->icon_dest_uri);
5561 t->icon_pixbuf = NULL;
5562 t->icon_request = NULL;
5563 t->icon_dest_uri = NULL;
5566 void
5567 xt_icon_from_name(struct tab *t, gchar *name)
5569 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5570 GTK_ENTRY_ICON_PRIMARY, "text-html");
5571 if (show_url == 0)
5572 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5573 GTK_ENTRY_ICON_PRIMARY, "text-html");
5574 else
5575 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5576 GTK_ENTRY_ICON_PRIMARY, NULL);
5579 void
5580 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5582 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5583 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5584 if (show_url == 0)
5585 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5586 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5587 else
5588 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5589 GTK_ENTRY_ICON_PRIMARY, NULL);
5592 gboolean
5593 is_valid_icon(char *file)
5595 gboolean valid = 0;
5596 const char *mime_type;
5597 GFileInfo *fi;
5598 GFile *gf;
5600 gf = g_file_new_for_path(file);
5601 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5602 NULL, NULL);
5603 mime_type = g_file_info_get_content_type(fi);
5604 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5605 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5606 g_strcmp0(mime_type, "image/png") == 0 ||
5607 g_strcmp0(mime_type, "image/gif") == 0 ||
5608 g_strcmp0(mime_type, "application/octet-stream") == 0;
5609 g_object_unref(fi);
5610 g_object_unref(gf);
5612 return (valid);
5615 void
5616 set_favicon_from_file(struct tab *t, char *file)
5618 gint width, height;
5619 GdkPixbuf *pixbuf, *scaled;
5620 struct stat sb;
5622 if (t == NULL || file == NULL)
5623 return;
5624 if (t->icon_pixbuf) {
5625 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5626 return;
5629 if (g_str_has_prefix(file, "file://"))
5630 file += strlen("file://");
5631 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5633 if (!stat(file, &sb)) {
5634 if (sb.st_size == 0 || !is_valid_icon(file)) {
5635 /* corrupt icon so trash it */
5636 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5637 __func__, file);
5638 unlink(file);
5639 /* no need to set icon to default here */
5640 return;
5644 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5645 if (pixbuf == NULL) {
5646 xt_icon_from_name(t, "text-html");
5647 return;
5650 g_object_get(pixbuf, "width", &width, "height", &height,
5651 (char *)NULL);
5652 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5653 __func__, t->tab_id, width, height);
5655 if (width > 16 || height > 16) {
5656 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5657 GDK_INTERP_BILINEAR);
5658 g_object_unref(pixbuf);
5659 } else
5660 scaled = pixbuf;
5662 if (scaled == NULL) {
5663 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5664 GDK_INTERP_BILINEAR);
5665 return;
5668 t->icon_pixbuf = scaled;
5669 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5672 void
5673 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5674 WebKitWebView *wv)
5676 WebKitDownloadStatus status = webkit_download_get_status(download);
5677 struct tab *tt = NULL, *t = NULL;
5680 * find the webview instead of passing in the tab as it could have been
5681 * deleted from underneath us.
5683 TAILQ_FOREACH(tt, &tabs, entry) {
5684 if (tt->wv == wv) {
5685 t = tt;
5686 break;
5689 if (t == NULL)
5690 return;
5692 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5693 __func__, t->tab_id, status);
5695 switch (status) {
5696 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5697 /* -1 */
5698 t->icon_download = NULL;
5699 free_favicon(t);
5700 break;
5701 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5702 /* 0 */
5703 break;
5704 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5705 /* 1 */
5706 break;
5707 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5708 /* 2 */
5709 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5710 __func__, t->tab_id);
5711 t->icon_download = NULL;
5712 free_favicon(t);
5713 break;
5714 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5715 /* 3 */
5717 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5718 __func__, t->icon_dest_uri);
5719 set_favicon_from_file(t, t->icon_dest_uri);
5720 /* these will be freed post callback */
5721 t->icon_request = NULL;
5722 t->icon_download = NULL;
5723 break;
5724 default:
5725 break;
5729 void
5730 abort_favicon_download(struct tab *t)
5732 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5734 if (t->icon_download) {
5735 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5736 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5737 webkit_download_cancel(t->icon_download);
5738 t->icon_download = NULL;
5739 } else
5740 free_favicon(t);
5742 xt_icon_from_name(t, "text-html");
5745 void
5746 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5748 gchar *name_hash, file[PATH_MAX];
5749 struct stat sb;
5751 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5753 if (uri == NULL || t == NULL)
5754 return;
5756 if (t->icon_request) {
5757 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5758 return;
5761 /* check to see if we got the icon in cache */
5762 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5763 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5764 g_free(name_hash);
5766 if (!stat(file, &sb)) {
5767 if (sb.st_size > 0) {
5768 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5769 __func__, file);
5770 set_favicon_from_file(t, file);
5771 return;
5774 /* corrupt icon so trash it */
5775 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5776 __func__, file);
5777 unlink(file);
5780 /* create download for icon */
5781 t->icon_request = webkit_network_request_new(uri);
5782 if (t->icon_request == NULL) {
5783 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5784 __func__, uri);
5785 return;
5788 t->icon_download = webkit_download_new(t->icon_request);
5789 if (t->icon_download == NULL) {
5790 fprintf(stderr, "%s: icon_download", __func__);
5791 return;
5794 /* we have to free icon_dest_uri later */
5795 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5796 webkit_download_set_destination_uri(t->icon_download,
5797 t->icon_dest_uri);
5799 if (webkit_download_get_status(t->icon_download) ==
5800 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5801 fprintf(stderr, "%s: download failed to start", __func__);
5802 g_object_unref(t->icon_request);
5803 g_free(t->icon_dest_uri);
5804 t->icon_request = NULL;
5805 t->icon_dest_uri = NULL;
5806 return;
5809 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5810 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5812 webkit_download_start(t->icon_download);
5815 void
5816 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5818 const gchar *set = NULL, *uri = NULL, *title = NULL;
5819 struct history *h, find;
5820 const gchar *s_loading;
5821 struct karg a;
5823 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5824 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5826 if (t == NULL) {
5827 show_oops_s("notify_load_status_cb invalid paramters");
5828 return;
5831 switch (webkit_web_view_get_load_status(wview)) {
5832 case WEBKIT_LOAD_PROVISIONAL:
5833 /* 0 */
5834 abort_favicon_download(t);
5835 #if GTK_CHECK_VERSION(2, 20, 0)
5836 gtk_widget_show(t->spinner);
5837 gtk_spinner_start(GTK_SPINNER(t->spinner));
5838 #endif
5839 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5841 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5843 t->focus_wv = 1;
5845 break;
5847 case WEBKIT_LOAD_COMMITTED:
5848 /* 1 */
5849 if ((uri = get_uri(wview)) != NULL) {
5850 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5851 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5853 if (t->status) {
5854 g_free(t->status);
5855 t->status = NULL;
5857 set_status(t, (char *)uri, XT_STATUS_LOADING);
5860 /* check if js white listing is enabled */
5861 if (enable_js_whitelist) {
5862 uri = get_uri(wview);
5863 check_and_set_js(uri, t);
5866 if (t->styled)
5867 apply_style(t);
5869 show_ca_status(t, uri);
5871 /* we know enough to autosave the session */
5872 if (session_autosave) {
5873 a.s = NULL;
5874 save_tabs(t, &a);
5876 break;
5878 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5879 /* 3 */
5880 break;
5882 case WEBKIT_LOAD_FINISHED:
5883 /* 2 */
5884 uri = get_uri(wview);
5885 if (uri == NULL)
5886 return;
5888 if (!strncmp(uri, "http://", strlen("http://")) ||
5889 !strncmp(uri, "https://", strlen("https://")) ||
5890 !strncmp(uri, "file://", strlen("file://"))) {
5891 find.uri = uri;
5892 h = RB_FIND(history_list, &hl, &find);
5893 if (!h) {
5894 title = webkit_web_view_get_title(wview);
5895 set = title ? title: uri;
5896 h = g_malloc(sizeof *h);
5897 h->uri = g_strdup(uri);
5898 h->title = g_strdup(set);
5899 RB_INSERT(history_list, &hl, h);
5900 completion_add_uri(h->uri);
5901 update_history_tabs(NULL);
5905 set_status(t, (char *)uri, XT_STATUS_URI);
5906 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5907 case WEBKIT_LOAD_FAILED:
5908 /* 4 */
5909 #endif
5910 #if GTK_CHECK_VERSION(2, 20, 0)
5911 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5912 gtk_widget_hide(t->spinner);
5913 #endif
5914 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5915 if (s_loading && !strcmp(s_loading, "Loading"))
5916 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5917 default:
5918 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5919 break;
5922 if (t->item)
5923 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5924 else
5925 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5926 webkit_web_view_can_go_back(wview));
5928 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5929 webkit_web_view_can_go_forward(wview));
5931 /* take focus if we are visible */
5932 focus_webview(t);
5935 void
5936 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5938 const gchar *set = NULL, *title = NULL;
5940 title = webkit_web_view_get_title(wview);
5941 set = title ? title: get_uri(wview);
5942 if (set) {
5943 gtk_label_set_text(GTK_LABEL(t->label), set);
5944 gtk_window_set_title(GTK_WINDOW(main_window), set);
5945 } else {
5946 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5947 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5951 void
5952 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5954 run_script(t, JS_HINTING);
5957 void
5958 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5960 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5961 progress == 100 ? 0 : (double)progress / 100);
5962 if (show_url == 0) {
5963 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5964 progress == 100 ? 0 : (double)progress / 100);
5969 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5970 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5971 WebKitWebPolicyDecision *pd, struct tab *t)
5973 char *uri;
5974 WebKitWebNavigationReason reason;
5975 struct domain *d = NULL;
5977 if (t == NULL) {
5978 show_oops_s("webview_npd_cb invalid parameters");
5979 return (FALSE);
5982 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5983 t->ctrl_click,
5984 webkit_network_request_get_uri(request));
5986 uri = (char *)webkit_network_request_get_uri(request);
5988 /* if this is an xtp url, we don't load anything else */
5989 if (parse_xtp_url(t, uri))
5990 return (TRUE);
5992 if (t->ctrl_click) {
5993 t->ctrl_click = 0;
5994 create_new_tab(uri, NULL, ctrl_click_focus, -1);
5995 webkit_web_policy_decision_ignore(pd);
5996 return (TRUE); /* we made the decission */
6000 * This is a little hairy but it comes down to this:
6001 * when we run in whitelist mode we have to assist the browser in
6002 * opening the URL that it would have opened in a new tab.
6004 reason = webkit_web_navigation_action_get_reason(na);
6005 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6006 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6007 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6008 load_uri(t, uri);
6010 webkit_web_policy_decision_use(pd);
6011 return (TRUE); /* we made the decission */
6014 return (FALSE);
6017 WebKitWebView *
6018 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6020 struct tab *tt;
6021 struct domain *d = NULL;
6022 const gchar *uri;
6023 WebKitWebView *webview = NULL;
6025 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6026 webkit_web_view_get_uri(wv));
6028 if (tabless) {
6029 /* open in current tab */
6030 webview = t->wv;
6031 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6032 uri = webkit_web_view_get_uri(wv);
6033 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6034 return (NULL);
6036 tt = create_new_tab(NULL, NULL, 1, -1);
6037 webview = tt->wv;
6038 } else if (enable_scripts == 1) {
6039 tt = create_new_tab(NULL, NULL, 1, -1);
6040 webview = tt->wv;
6043 return (webview);
6046 gboolean
6047 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6049 const gchar *uri;
6050 struct domain *d = NULL;
6052 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6054 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6055 uri = webkit_web_view_get_uri(wv);
6056 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6057 return (FALSE);
6059 delete_tab(t);
6060 } else if (enable_scripts == 1)
6061 delete_tab(t);
6063 return (TRUE);
6067 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6069 /* we can not eat the event without throwing gtk off so defer it */
6071 /* catch middle click */
6072 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6073 t->ctrl_click = 1;
6074 goto done;
6077 /* catch ctrl click */
6078 if (e->type == GDK_BUTTON_RELEASE &&
6079 CLEAN(e->state) == GDK_CONTROL_MASK)
6080 t->ctrl_click = 1;
6081 else
6082 t->ctrl_click = 0;
6083 done:
6084 return (XT_CB_PASSTHROUGH);
6088 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6090 struct mime_type *m;
6092 m = find_mime_type(mime_type);
6093 if (m == NULL)
6094 return (1);
6095 if (m->mt_download)
6096 return (1);
6098 switch (fork()) {
6099 case -1:
6100 show_oops(t, "can't fork mime handler");
6101 /* NOTREACHED */
6102 case 0:
6103 break;
6104 default:
6105 return (0);
6108 /* child */
6109 execlp(m->mt_action, m->mt_action,
6110 webkit_network_request_get_uri(request), (void *)NULL);
6112 _exit(0);
6114 /* NOTREACHED */
6115 return (0);
6118 const gchar *
6119 get_mime_type(char *file)
6121 const char *mime_type;
6122 GFileInfo *fi;
6123 GFile *gf;
6125 if (g_str_has_prefix(file, "file://"))
6126 file += strlen("file://");
6128 gf = g_file_new_for_path(file);
6129 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6130 NULL, NULL);
6131 mime_type = g_file_info_get_content_type(fi);
6132 g_object_unref(fi);
6133 g_object_unref(gf);
6135 return (mime_type);
6139 run_download_mimehandler(char *mime_type, char *file)
6141 struct mime_type *m;
6143 m = find_mime_type(mime_type);
6144 if (m == NULL)
6145 return (1);
6147 switch (fork()) {
6148 case -1:
6149 show_oops_s("can't fork download mime handler");
6150 /* NOTREACHED */
6151 case 0:
6152 break;
6153 default:
6154 return (0);
6157 /* child */
6158 if (g_str_has_prefix(file, "file://"))
6159 file += strlen("file://");
6160 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6162 _exit(0);
6164 /* NOTREACHED */
6165 return (0);
6168 void
6169 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6170 WebKitWebView *wv)
6172 WebKitDownloadStatus status;
6173 const gchar *file = NULL, *mime = NULL;
6175 if (download == NULL)
6176 return;
6177 status = webkit_download_get_status(download);
6178 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6179 return;
6181 file = webkit_download_get_destination_uri(download);
6182 if (file == NULL)
6183 return;
6184 mime = get_mime_type((char *)file);
6185 if (mime == NULL)
6186 return;
6188 run_download_mimehandler((char *)mime, (char *)file);
6192 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6193 WebKitNetworkRequest *request, char *mime_type,
6194 WebKitWebPolicyDecision *decision, struct tab *t)
6196 if (t == NULL) {
6197 show_oops_s("webview_mimetype_cb invalid parameters");
6198 return (FALSE);
6201 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6202 t->tab_id, mime_type);
6204 if (run_mimehandler(t, mime_type, request) == 0) {
6205 webkit_web_policy_decision_ignore(decision);
6206 focus_webview(t);
6207 return (TRUE);
6210 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6211 webkit_web_policy_decision_download(decision);
6212 return (TRUE);
6215 return (FALSE);
6219 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6220 struct tab *t)
6222 const gchar *filename;
6223 char *uri = NULL;
6224 struct download *download_entry;
6225 int ret = TRUE;
6227 if (wk_download == NULL || t == NULL) {
6228 show_oops_s("%s invalid parameters", __func__);
6229 return (FALSE);
6232 filename = webkit_download_get_suggested_filename(wk_download);
6233 if (filename == NULL)
6234 return (FALSE); /* abort download */
6236 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6238 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6239 "local %s\n", __func__, t->tab_id, filename, uri);
6241 webkit_download_set_destination_uri(wk_download, uri);
6243 if (webkit_download_get_status(wk_download) ==
6244 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6245 show_oops(t, "%s: download failed to start", __func__);
6246 ret = FALSE;
6247 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6248 } else {
6249 /* connect "download first" mime handler */
6250 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6251 G_CALLBACK(download_status_changed_cb), NULL);
6253 download_entry = g_malloc(sizeof(struct download));
6254 download_entry->download = wk_download;
6255 download_entry->tab = t;
6256 download_entry->id = next_download_id++;
6257 RB_INSERT(download_list, &downloads, download_entry);
6258 /* get from history */
6259 g_object_ref(wk_download);
6260 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6261 show_oops(t, "Download of '%s' started...",
6262 basename(webkit_download_get_destination_uri(wk_download)));
6265 if (uri)
6266 g_free(uri);
6268 /* sync other download manager tabs */
6269 update_download_tabs(NULL);
6272 * NOTE: never redirect/render the current tab before this
6273 * function returns. This will cause the download to never start.
6275 return (ret); /* start download */
6278 void
6279 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6281 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6283 if (t == NULL) {
6284 show_oops_s("webview_hover_cb");
6285 return;
6288 if (uri)
6289 set_status(t, uri, XT_STATUS_LINK);
6290 else {
6291 if (t->status)
6292 set_status(t, t->status, XT_STATUS_NOTHING);
6296 gboolean
6297 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6299 struct key_binding *k;
6301 TAILQ_FOREACH(k, &kbl, entry)
6302 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6303 if (k->mask == 0) {
6304 if ((e->state & (CTRL | MOD1)) == 0)
6305 return (cmd_execute(t, k->cmd));
6306 } else if ((e->state & k->mask) == k->mask) {
6307 return (cmd_execute(t, k->cmd));
6311 return (XT_CB_PASSTHROUGH);
6315 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6317 char s[2], buf[128];
6318 const char *errstr = NULL;
6319 long long link;
6321 /* don't use w directly; use t->whatever instead */
6323 if (t == NULL) {
6324 show_oops_s("wv_keypress_after_cb");
6325 return (XT_CB_PASSTHROUGH);
6328 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6329 e->keyval, e->state, t);
6331 if (t->hints_on) {
6332 /* ESC */
6333 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6334 disable_hints(t);
6335 return (XT_CB_HANDLED);
6338 /* RETURN */
6339 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6340 link = strtonum(t->hint_num, 1, 1000, &errstr);
6341 if (errstr) {
6342 /* we have a string */
6343 } else {
6344 /* we have a number */
6345 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6346 t->hint_num);
6347 run_script(t, buf);
6349 disable_hints(t);
6352 /* BACKSPACE */
6353 /* XXX unfuck this */
6354 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6355 if (t->hint_mode == XT_HINT_NUMERICAL) {
6356 /* last input was numerical */
6357 int l;
6358 l = strlen(t->hint_num);
6359 if (l > 0) {
6360 l--;
6361 if (l == 0) {
6362 disable_hints(t);
6363 enable_hints(t);
6364 } else {
6365 t->hint_num[l] = '\0';
6366 goto num;
6369 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6370 /* last input was alphanumerical */
6371 int l;
6372 l = strlen(t->hint_buf);
6373 if (l > 0) {
6374 l--;
6375 if (l == 0) {
6376 disable_hints(t);
6377 enable_hints(t);
6378 } else {
6379 t->hint_buf[l] = '\0';
6380 goto anum;
6383 } else {
6384 /* bogus */
6385 disable_hints(t);
6389 /* numerical input */
6390 if (CLEAN(e->state) == 0 &&
6391 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6392 snprintf(s, sizeof s, "%c", e->keyval);
6393 strlcat(t->hint_num, s, sizeof t->hint_num);
6394 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6395 t->hint_num);
6396 num:
6397 link = strtonum(t->hint_num, 1, 1000, &errstr);
6398 if (errstr) {
6399 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6400 disable_hints(t);
6401 } else {
6402 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6403 t->hint_num);
6404 t->hint_mode = XT_HINT_NUMERICAL;
6405 run_script(t, buf);
6408 /* empty the counter buffer */
6409 bzero(t->hint_buf, sizeof t->hint_buf);
6410 return (XT_CB_HANDLED);
6413 /* alphanumerical input */
6414 if (
6415 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6416 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6417 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6418 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6419 snprintf(s, sizeof s, "%c", e->keyval);
6420 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6421 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6422 t->hint_buf);
6423 anum:
6424 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6425 run_script(t, buf);
6427 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6428 t->hint_buf);
6429 t->hint_mode = XT_HINT_ALPHANUM;
6430 run_script(t, buf);
6432 /* empty the counter buffer */
6433 bzero(t->hint_num, sizeof t->hint_num);
6434 return (XT_CB_HANDLED);
6437 return (XT_CB_HANDLED);
6438 } else {
6439 /* prefix input*/
6440 snprintf(s, sizeof s, "%c", e->keyval);
6441 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6442 cmd_prefix = 10 * cmd_prefix + atoi(s);
6446 return (handle_keypress(t, e, 0));
6450 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6452 hide_oops(t);
6454 return (XT_CB_PASSTHROUGH);
6458 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6460 const gchar *c = gtk_entry_get_text(w);
6461 GdkColor color;
6462 int forward = TRUE;
6464 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6465 e->keyval, e->state, t);
6467 if (t == NULL) {
6468 show_oops_s("cmd_keyrelease_cb invalid parameters");
6469 return (XT_CB_PASSTHROUGH);
6472 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6473 e->keyval, e->state, t);
6475 if (c[0] == ':')
6476 goto done;
6477 if (strlen(c) == 1) {
6478 webkit_web_view_unmark_text_matches(t->wv);
6479 goto done;
6482 if (c[0] == '/')
6483 forward = TRUE;
6484 else if (c[0] == '?')
6485 forward = FALSE;
6486 else
6487 goto done;
6489 /* search */
6490 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6491 FALSE) {
6492 /* not found, mark red */
6493 gdk_color_parse(XT_COLOR_RED, &color);
6494 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6495 /* unmark and remove selection */
6496 webkit_web_view_unmark_text_matches(t->wv);
6497 /* my kingdom for a way to unselect text in webview */
6498 } else {
6499 /* found, highlight all */
6500 webkit_web_view_unmark_text_matches(t->wv);
6501 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6502 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6503 gdk_color_parse(XT_COLOR_WHITE, &color);
6504 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6506 done:
6507 return (XT_CB_PASSTHROUGH);
6510 gboolean
6511 match_uri(const gchar *uri, const gchar *key) {
6512 gchar *voffset;
6513 size_t len;
6514 gboolean match = FALSE;
6516 len = strlen(key);
6518 if (!strncmp(key, uri, len))
6519 match = TRUE;
6520 else {
6521 voffset = strstr(uri, "/") + 2;
6522 if (!strncmp(key, voffset, len))
6523 match = TRUE;
6524 else if (g_str_has_prefix(voffset, "www.")) {
6525 voffset = voffset + strlen("www.");
6526 if (!strncmp(key, voffset, len))
6527 match = TRUE;
6531 return (match);
6534 void
6535 cmd_getlist(int id, char *key)
6537 int i, dep, c = 0;
6538 struct history *h;
6540 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6541 RB_FOREACH_REVERSE(h, history_list, &hl)
6542 if (match_uri(h->uri, key)) {
6543 cmd_status.list[c] = (char *)h->uri;
6544 if (++c > 255)
6545 break;
6548 cmd_status.len = c;
6549 return;
6552 dep = (id == -1) ? 0 : cmds[id].level + 1;
6554 for (i = id + 1; i < LENGTH(cmds); i++) {
6555 if(cmds[i].level < dep)
6556 break;
6557 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6558 cmd_status.list[c++] = cmds[i].cmd;
6562 cmd_status.len = c;
6565 char *
6566 cmd_getnext(int dir)
6568 cmd_status.index += dir;
6570 if (cmd_status.index < 0)
6571 cmd_status.index = cmd_status.len - 1;
6572 else if (cmd_status.index >= cmd_status.len)
6573 cmd_status.index = 0;
6575 return cmd_status.list[cmd_status.index];
6579 cmd_tokenize(char *s, char *tokens[])
6581 int i = 0;
6582 char *tok, *last;
6583 size_t len = strlen(s);
6584 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6586 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6587 tokens[i] = tok;
6589 if (blank && i < 3)
6590 tokens[i++] = "";
6592 return (i);
6595 void
6596 cmd_complete(struct tab *t, char *str, int dir)
6598 GtkEntry *w = GTK_ENTRY(t->cmd);
6599 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6600 char *tok, *match, *s = g_strdup(str);
6601 char *tokens[3];
6602 char res[XT_MAX_URL_LENGTH + 32] = ":";
6603 char *sc = s;
6605 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6607 /* copy prefix*/
6608 for (i = 0; isdigit(s[i]); i++)
6609 res[i + 1] = s[i];
6611 for (; isspace(s[i]); i++)
6612 res[i + 1] = s[i];
6614 s += i;
6616 levels = cmd_tokenize(s, tokens);
6618 for (i = 0; i < levels - 1; i++) {
6619 tok = tokens[i];
6620 matchcount = 0;
6621 for (j = c; j < LENGTH(cmds); j++) {
6622 if (cmds[j].level < dep)
6623 break;
6624 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6625 matchcount++;
6626 c = j + 1;
6627 if (strlen(tok) == strlen(cmds[j].cmd)) {
6628 matchcount = 1;
6629 break;
6634 if (matchcount == 1) {
6635 strlcat(res, tok, sizeof res);
6636 strlcat(res, " ", sizeof res);
6637 dep++;
6638 } else {
6639 g_free(sc);
6640 return;
6643 parent = c - 1;
6646 if (cmd_status.index == -1)
6647 cmd_getlist(parent, tokens[i]);
6649 if (cmd_status.len > 0) {
6650 match = cmd_getnext(dir);
6651 strlcat(res, match, sizeof res);
6652 gtk_entry_set_text(w, res);
6653 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6656 g_free(sc);
6659 gboolean
6660 cmd_execute(struct tab *t, char *str)
6662 struct cmd *cmd = NULL;
6663 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6664 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6665 struct karg arg = {0, NULL, -1};
6666 int rv = XT_CB_PASSTHROUGH;
6668 sc = s;
6670 /* copy prefix*/
6671 for (j = 0; j<3 && isdigit(s[j]); j++)
6672 prefixstr[j]=s[j];
6674 prefixstr[j]='\0';
6676 s += j;
6677 while (isspace(s[0]))
6678 s++;
6680 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6681 prefix = atoi(prefixstr);
6682 else
6683 s = sc;
6685 for (tok = strtok_r(s, " ", &last); tok;
6686 tok = strtok_r(NULL, " ", &last)) {
6687 matchcount = 0;
6688 for (j = c; j < LENGTH(cmds); j++) {
6689 if (cmds[j].level < dep)
6690 break;
6691 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6692 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6693 matchcount++;
6694 c = j + 1;
6695 cmd = &cmds[j];
6696 if (len == strlen(cmds[j].cmd)) {
6697 matchcount = 1;
6698 break;
6702 if (matchcount == 1) {
6703 if (cmd->type > 0)
6704 goto execute_cmd;
6705 dep++;
6706 } else {
6707 show_oops(t, "Invalid command: %s", str);
6708 goto done;
6711 execute_cmd:
6712 arg.i = cmd->arg;
6714 if (prefix != -1)
6715 arg.p = prefix;
6716 else if (cmd_prefix > 0)
6717 arg.p = cmd_prefix;
6719 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6720 show_oops(t, "No prefix allowed: %s", str);
6721 goto done;
6723 if (cmd->type > 1)
6724 arg.s = last ? g_strdup(last) : g_strdup("");
6725 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6726 arg.p = atoi(arg.s);
6727 if (arg.p <= 0) {
6728 if (arg.s[0]=='0')
6729 show_oops(t, "Zero count");
6730 else
6731 show_oops(t, "Trailing characters");
6732 goto done;
6736 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6738 cmd->func(t, &arg);
6740 rv = XT_CB_HANDLED;
6741 done:
6742 if (j > 0)
6743 cmd_prefix = 0;
6744 g_free(sc);
6745 if (arg.s)
6746 g_free(arg.s);
6748 return (rv);
6752 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6754 if (t == NULL) {
6755 show_oops_s("entry_key_cb invalid parameters");
6756 return (XT_CB_PASSTHROUGH);
6759 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6760 e->keyval, e->state, t);
6762 hide_oops(t);
6764 if (e->keyval == GDK_Escape) {
6765 /* don't use focus_webview(t) because we want to type :cmds */
6766 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6769 return (handle_keypress(t, e, 1));
6773 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6775 int rv = XT_CB_HANDLED;
6776 const gchar *c = gtk_entry_get_text(w);
6778 if (t == NULL) {
6779 show_oops_s("cmd_keypress_cb parameters");
6780 return (XT_CB_PASSTHROUGH);
6783 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6784 e->keyval, e->state, t);
6786 /* sanity */
6787 if (c == NULL)
6788 e->keyval = GDK_Escape;
6789 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6790 e->keyval = GDK_Escape;
6792 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6793 cmd_status.index = -1;
6795 switch (e->keyval) {
6796 case GDK_Tab:
6797 if (c[0] == ':')
6798 cmd_complete(t, (char *)&c[1], 1);
6799 goto done;
6800 case GDK_ISO_Left_Tab:
6801 if (c[0] == ':')
6802 cmd_complete(t, (char *)&c[1], -1);
6804 goto done;
6805 case GDK_BackSpace:
6806 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6807 break;
6808 /* FALLTHROUGH */
6809 case GDK_Escape:
6810 hide_cmd(t);
6811 focus_webview(t);
6813 /* cancel search */
6814 if (c[0] == '/' || c[0] == '?')
6815 webkit_web_view_unmark_text_matches(t->wv);
6816 goto done;
6819 rv = XT_CB_PASSTHROUGH;
6820 done:
6821 return (rv);
6825 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6827 if (t == NULL) {
6828 show_oops_s("cmd_focusout_cb invalid parameters");
6829 return (XT_CB_PASSTHROUGH);
6831 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6833 hide_cmd(t);
6834 hide_oops(t);
6836 if (show_url == 0 || t->focus_wv)
6837 focus_webview(t);
6838 else
6839 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6841 return (XT_CB_PASSTHROUGH);
6844 void
6845 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6847 char *s;
6848 const gchar *c = gtk_entry_get_text(entry);
6850 if (t == NULL) {
6851 show_oops_s("cmd_activate_cb invalid parameters");
6852 return;
6855 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6857 hide_cmd(t);
6859 /* sanity */
6860 if (c == NULL)
6861 goto done;
6862 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6863 goto done;
6864 if (strlen(c) < 2)
6865 goto done;
6866 s = (char *)&c[1];
6868 if (c[0] == '/' || c[0] == '?') {
6869 if (t->search_text) {
6870 g_free(t->search_text);
6871 t->search_text = NULL;
6874 t->search_text = g_strdup(s);
6875 if (global_search)
6876 g_free(global_search);
6877 global_search = g_strdup(s);
6878 t->search_forward = c[0] == '/';
6880 goto done;
6883 cmd_execute(t, s);
6885 done:
6886 return;
6889 void
6890 backward_cb(GtkWidget *w, struct tab *t)
6892 struct karg a;
6894 if (t == NULL) {
6895 show_oops_s("backward_cb invalid parameters");
6896 return;
6899 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6901 a.i = XT_NAV_BACK;
6902 navaction(t, &a);
6905 void
6906 forward_cb(GtkWidget *w, struct tab *t)
6908 struct karg a;
6910 if (t == NULL) {
6911 show_oops_s("forward_cb invalid parameters");
6912 return;
6915 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6917 a.i = XT_NAV_FORWARD;
6918 navaction(t, &a);
6921 void
6922 home_cb(GtkWidget *w, struct tab *t)
6924 if (t == NULL) {
6925 show_oops_s("home_cb invalid parameters");
6926 return;
6929 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6931 load_uri(t, home);
6934 void
6935 stop_cb(GtkWidget *w, struct tab *t)
6937 WebKitWebFrame *frame;
6939 if (t == NULL) {
6940 show_oops_s("stop_cb invalid parameters");
6941 return;
6944 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6946 frame = webkit_web_view_get_main_frame(t->wv);
6947 if (frame == NULL) {
6948 show_oops(t, "stop_cb: no frame");
6949 return;
6952 webkit_web_frame_stop_loading(frame);
6953 abort_favicon_download(t);
6956 void
6957 setup_webkit(struct tab *t)
6959 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6960 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6961 FALSE, (char *)NULL);
6962 else
6963 warnx("webkit does not have \"enable-dns-prefetching\" property");
6964 g_object_set(G_OBJECT(t->settings),
6965 "user-agent", t->user_agent, (char *)NULL);
6966 g_object_set(G_OBJECT(t->settings),
6967 "enable-scripts", enable_scripts, (char *)NULL);
6968 g_object_set(G_OBJECT(t->settings),
6969 "enable-plugins", enable_plugins, (char *)NULL);
6970 g_object_set(G_OBJECT(t->settings),
6971 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6972 g_object_set(G_OBJECT(t->settings),
6973 "enable_spell_checking", enable_spell_checking, (char *)NULL);
6974 g_object_set(G_OBJECT(t->settings),
6975 "spell_checking_languages", spell_check_languages, (char *)NULL);
6976 g_object_set(G_OBJECT(t->wv),
6977 "full-content-zoom", TRUE, (char *)NULL);
6978 adjustfont_webkit(t, XT_FONT_SET);
6980 webkit_web_view_set_settings(t->wv, t->settings);
6983 GtkWidget *
6984 create_browser(struct tab *t)
6986 GtkWidget *w;
6987 gchar *strval;
6989 if (t == NULL) {
6990 show_oops_s("create_browser invalid parameters");
6991 return (NULL);
6994 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6995 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6996 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6997 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6999 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7001 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7003 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7004 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7006 /* set defaults */
7007 t->settings = webkit_web_settings_new();
7009 if (user_agent == NULL) {
7010 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7011 (char *)NULL);
7012 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7013 g_free(strval);
7014 } else
7015 t->user_agent = g_strdup(user_agent);
7017 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7019 setup_webkit(t);
7021 return (w);
7024 GtkWidget *
7025 create_window(void)
7027 GtkWidget *w;
7029 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7030 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7031 gtk_widget_set_name(w, "xxxterm");
7032 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7033 g_signal_connect(G_OBJECT(w), "delete_event",
7034 G_CALLBACK (gtk_main_quit), NULL);
7036 return (w);
7039 GtkWidget *
7040 create_kiosk_toolbar(struct tab *t)
7042 GtkWidget *toolbar = NULL, *b;
7044 b = gtk_hbox_new(FALSE, 0);
7045 toolbar = b;
7046 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7048 /* backward button */
7049 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7050 gtk_widget_set_sensitive(t->backward, FALSE);
7051 g_signal_connect(G_OBJECT(t->backward), "clicked",
7052 G_CALLBACK(backward_cb), t);
7053 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7055 /* forward button */
7056 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7057 gtk_widget_set_sensitive(t->forward, FALSE);
7058 g_signal_connect(G_OBJECT(t->forward), "clicked",
7059 G_CALLBACK(forward_cb), t);
7060 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7062 /* home button */
7063 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7064 gtk_widget_set_sensitive(t->gohome, true);
7065 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7066 G_CALLBACK(home_cb), t);
7067 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7069 /* create widgets but don't use them */
7070 t->uri_entry = gtk_entry_new();
7071 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7072 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7073 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7075 return (toolbar);
7078 GtkWidget *
7079 create_toolbar(struct tab *t)
7081 GtkWidget *toolbar = NULL, *b, *eb1;
7083 b = gtk_hbox_new(FALSE, 0);
7084 toolbar = b;
7085 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7087 if (fancy_bar) {
7088 /* backward button */
7089 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7090 gtk_widget_set_sensitive(t->backward, FALSE);
7091 g_signal_connect(G_OBJECT(t->backward), "clicked",
7092 G_CALLBACK(backward_cb), t);
7093 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7095 /* forward button */
7096 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7097 gtk_widget_set_sensitive(t->forward, FALSE);
7098 g_signal_connect(G_OBJECT(t->forward), "clicked",
7099 G_CALLBACK(forward_cb), t);
7100 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7101 FALSE, 0);
7103 /* stop button */
7104 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7105 gtk_widget_set_sensitive(t->stop, FALSE);
7106 g_signal_connect(G_OBJECT(t->stop), "clicked",
7107 G_CALLBACK(stop_cb), t);
7108 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7109 FALSE, 0);
7111 /* JS button */
7112 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7113 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7114 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7115 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7116 G_CALLBACK(js_toggle_cb), t);
7117 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7120 t->uri_entry = gtk_entry_new();
7121 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7122 G_CALLBACK(activate_uri_entry_cb), t);
7123 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7124 G_CALLBACK(entry_key_cb), t);
7125 completion_add(t);
7126 eb1 = gtk_hbox_new(FALSE, 0);
7127 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7128 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7129 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7131 /* search entry */
7132 if (fancy_bar && search_string) {
7133 GtkWidget *eb2;
7134 t->search_entry = gtk_entry_new();
7135 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7136 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7137 G_CALLBACK(activate_search_entry_cb), t);
7138 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7139 G_CALLBACK(entry_key_cb), t);
7140 gtk_widget_set_size_request(t->search_entry, -1, -1);
7141 eb2 = gtk_hbox_new(FALSE, 0);
7142 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7143 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7145 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7147 return (toolbar);
7150 void
7151 recalc_tabs(void)
7153 struct tab *t;
7155 TAILQ_FOREACH(t, &tabs, entry)
7156 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7160 undo_close_tab_save(struct tab *t)
7162 int m, n;
7163 const gchar *uri;
7164 struct undo *u1, *u2;
7165 GList *items;
7166 WebKitWebHistoryItem *item;
7168 if ((uri = get_uri(t->wv)) == NULL)
7169 return (1);
7171 u1 = g_malloc0(sizeof(struct undo));
7172 u1->uri = g_strdup(uri);
7174 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7176 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7177 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7178 u1->back = n;
7180 /* forward history */
7181 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7183 while (items) {
7184 item = items->data;
7185 u1->history = g_list_prepend(u1->history,
7186 webkit_web_history_item_copy(item));
7187 items = g_list_next(items);
7190 /* current item */
7191 if (m) {
7192 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7193 u1->history = g_list_prepend(u1->history,
7194 webkit_web_history_item_copy(item));
7197 /* back history */
7198 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7200 while (items) {
7201 item = items->data;
7202 u1->history = g_list_prepend(u1->history,
7203 webkit_web_history_item_copy(item));
7204 items = g_list_next(items);
7207 TAILQ_INSERT_HEAD(&undos, u1, entry);
7209 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7210 u2 = TAILQ_LAST(&undos, undo_tailq);
7211 TAILQ_REMOVE(&undos, u2, entry);
7212 g_free(u2->uri);
7213 g_list_free(u2->history);
7214 g_free(u2);
7215 } else
7216 undo_count++;
7218 return (0);
7221 void
7222 delete_tab(struct tab *t)
7224 struct karg a;
7226 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7228 if (t == NULL)
7229 return;
7231 TAILQ_REMOVE(&tabs, t, entry);
7233 /* halt all webkit activity */
7234 abort_favicon_download(t);
7235 webkit_web_view_stop_loading(t->wv);
7236 undo_close_tab_save(t);
7238 if (browser_mode == XT_BM_KIOSK) {
7239 gtk_widget_destroy(t->uri_entry);
7240 gtk_widget_destroy(t->stop);
7241 gtk_widget_destroy(t->js_toggle);
7244 gtk_widget_destroy(t->vbox);
7245 g_free(t->user_agent);
7246 g_free(t->stylesheet);
7247 g_free(t);
7249 if (TAILQ_EMPTY(&tabs)) {
7250 if (browser_mode == XT_BM_KIOSK)
7251 create_new_tab(home, NULL, 1, -1);
7252 else
7253 create_new_tab(NULL, NULL, 1, -1);
7256 /* recreate session */
7257 if (session_autosave) {
7258 a.s = NULL;
7259 save_tabs(t, &a);
7263 void
7264 adjustfont_webkit(struct tab *t, int adjust)
7266 gfloat zoom;
7268 if (t == NULL) {
7269 show_oops_s("adjustfont_webkit invalid parameters");
7270 return;
7273 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7274 if (adjust == XT_FONT_SET) {
7275 t->font_size = default_font_size;
7276 zoom = default_zoom_level;
7277 t->font_size += adjust;
7278 g_object_set(G_OBJECT(t->settings), "default-font-size",
7279 t->font_size, (char *)NULL);
7280 g_object_get(G_OBJECT(t->settings), "default-font-size",
7281 &t->font_size, (char *)NULL);
7282 } else {
7283 t->font_size += adjust;
7284 zoom += adjust/25.0;
7285 if (zoom < 0.0) {
7286 zoom = 0.04;
7289 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7290 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7293 void
7294 append_tab(struct tab *t)
7296 if (t == NULL)
7297 return;
7299 TAILQ_INSERT_TAIL(&tabs, t, entry);
7300 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7303 struct tab *
7304 create_new_tab(char *title, struct undo *u, int focus, int position)
7306 struct tab *t;
7307 int load = 1, id;
7308 GtkWidget *b, *bb;
7309 WebKitWebHistoryItem *item;
7310 GList *items;
7311 GdkColor color;
7313 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7315 if (tabless && !TAILQ_EMPTY(&tabs)) {
7316 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7317 return (NULL);
7320 t = g_malloc0(sizeof *t);
7322 if (title == NULL) {
7323 title = "(untitled)";
7324 load = 0;
7327 t->vbox = gtk_vbox_new(FALSE, 0);
7329 /* label + button for tab */
7330 b = gtk_hbox_new(FALSE, 0);
7331 t->tab_content = b;
7333 #if GTK_CHECK_VERSION(2, 20, 0)
7334 t->spinner = gtk_spinner_new ();
7335 #endif
7336 t->label = gtk_label_new(title);
7337 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7338 gtk_widget_set_size_request(t->label, 100, 0);
7339 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7340 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7341 gtk_widget_set_size_request(b, 130, 0);
7343 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7344 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7345 #if GTK_CHECK_VERSION(2, 20, 0)
7346 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7347 #endif
7349 /* toolbar */
7350 if (browser_mode == XT_BM_KIOSK)
7351 t->toolbar = create_kiosk_toolbar(t);
7352 else
7353 t->toolbar = create_toolbar(t);
7355 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7357 /* browser */
7358 t->browser_win = create_browser(t);
7359 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7361 /* oops message for user feedback */
7362 t->oops = gtk_entry_new();
7363 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7364 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7365 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7366 gdk_color_parse(XT_COLOR_RED, &color);
7367 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7368 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7370 /* command entry */
7371 t->cmd = gtk_entry_new();
7372 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7373 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7374 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7376 /* status bar */
7377 t->statusbar = gtk_entry_new();
7378 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7379 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7380 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7381 gdk_color_parse(XT_COLOR_BLACK, &color);
7382 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7383 gdk_color_parse(XT_COLOR_WHITE, &color);
7384 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7385 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7387 /* xtp meaning is normal by default */
7388 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7390 /* set empty favicon */
7391 xt_icon_from_name(t, "text-html");
7393 /* and show it all */
7394 gtk_widget_show_all(b);
7395 gtk_widget_show_all(t->vbox);
7397 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7398 append_tab(t);
7399 else {
7400 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7401 if (id > gtk_notebook_get_n_pages(notebook))
7402 append_tab(t);
7403 else {
7404 TAILQ_INSERT_TAIL(&tabs, t, entry);
7405 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7406 recalc_tabs();
7410 #if GTK_CHECK_VERSION(2, 20, 0)
7411 /* turn spinner off if we are a new tab without uri */
7412 if (!load) {
7413 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7414 gtk_widget_hide(t->spinner);
7416 #endif
7417 /* make notebook tabs reorderable */
7418 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7420 g_object_connect(G_OBJECT(t->cmd),
7421 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7422 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7423 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7424 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7425 (char *)NULL);
7427 /* reuse wv_button_cb to hide oops */
7428 g_object_connect(G_OBJECT(t->oops),
7429 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7430 (char *)NULL);
7432 g_object_connect(G_OBJECT(t->wv),
7433 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7434 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7435 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7436 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7437 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7438 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7439 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7440 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7441 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7442 "signal::event", G_CALLBACK(webview_event_cb), t,
7443 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7444 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7445 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7446 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7447 #endif
7448 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7449 (char *)NULL);
7450 g_signal_connect(t->wv,
7451 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7452 g_signal_connect(t->wv,
7453 "notify::title", G_CALLBACK(notify_title_cb), t);
7455 /* hijack the unused keys as if we were the browser */
7456 g_object_connect(G_OBJECT(t->toolbar),
7457 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7458 (char *)NULL);
7460 g_signal_connect(G_OBJECT(bb), "button_press_event",
7461 G_CALLBACK(tab_close_cb), t);
7463 /* hide stuff */
7464 hide_cmd(t);
7465 hide_oops(t);
7466 url_set_visibility();
7467 statusbar_set_visibility();
7469 if (focus) {
7470 gtk_notebook_set_current_page(notebook, t->tab_id);
7471 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7472 t->tab_id);
7475 if (load) {
7476 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7477 load_uri(t, title);
7478 } else {
7479 if (show_url == 1)
7480 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7481 else
7482 focus_webview(t);
7485 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7486 /* restore the tab's history */
7487 if (u && u->history) {
7488 items = u->history;
7489 while (items) {
7490 item = items->data;
7491 webkit_web_back_forward_list_add_item(t->bfl, item);
7492 items = g_list_next(items);
7495 item = g_list_nth_data(u->history, u->back);
7496 if (item)
7497 webkit_web_view_go_to_back_forward_item(t->wv, item);
7499 g_list_free(items);
7500 g_list_free(u->history);
7501 } else
7502 webkit_web_back_forward_list_clear(t->bfl);
7504 return (t);
7507 void
7508 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7509 gpointer *udata)
7511 struct tab *t;
7512 const gchar *uri;
7514 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7516 if (gtk_notebook_get_current_page(notebook) == -1)
7517 recalc_tabs();
7519 TAILQ_FOREACH(t, &tabs, entry) {
7520 if (t->tab_id == pn) {
7521 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7522 "%d\n", pn);
7524 uri = webkit_web_view_get_title(t->wv);
7525 if (uri == NULL)
7526 uri = XT_NAME;
7527 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7529 hide_cmd(t);
7530 hide_oops(t);
7532 if (t->focus_wv)
7533 focus_webview(t);
7538 void
7539 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7540 gpointer *udata)
7542 recalc_tabs();
7545 void
7546 menuitem_response(struct tab *t)
7548 gtk_notebook_set_current_page(notebook, t->tab_id);
7551 gboolean
7552 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7554 GtkWidget *menu, *menu_items;
7555 GdkEventButton *bevent;
7556 const gchar *uri;
7557 struct tab *ti;
7559 if (event->type == GDK_BUTTON_PRESS) {
7560 bevent = (GdkEventButton *) event;
7561 menu = gtk_menu_new();
7563 TAILQ_FOREACH(ti, &tabs, entry) {
7564 if ((uri = get_uri(ti->wv)) == NULL)
7565 /* XXX make sure there is something to print */
7566 /* XXX add gui pages in here to look purdy */
7567 uri = "(untitled)";
7568 menu_items = gtk_menu_item_new_with_label(uri);
7569 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7570 gtk_widget_show(menu_items);
7572 g_signal_connect_swapped((menu_items),
7573 "activate", G_CALLBACK(menuitem_response),
7574 (gpointer)ti);
7577 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7578 bevent->button, bevent->time);
7580 /* unref object so it'll free itself when popped down */
7581 #if !GTK_CHECK_VERSION(3, 0, 0)
7582 /* XXX does not need unref with gtk+3? */
7583 g_object_ref_sink(menu);
7584 g_object_unref(menu);
7585 #endif
7587 return (TRUE /* eat event */);
7590 return (FALSE /* propagate */);
7594 icon_size_map(int icon_size)
7596 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7597 icon_size > GTK_ICON_SIZE_DIALOG)
7598 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7600 return (icon_size);
7603 GtkWidget *
7604 create_button(char *name, char *stockid, int size)
7606 GtkWidget *button, *image;
7607 gchar *rcstring;
7608 int gtk_icon_size;
7610 rcstring = g_strdup_printf(
7611 "style \"%s-style\"\n"
7612 "{\n"
7613 " GtkWidget::focus-padding = 0\n"
7614 " GtkWidget::focus-line-width = 0\n"
7615 " xthickness = 0\n"
7616 " ythickness = 0\n"
7617 "}\n"
7618 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7619 gtk_rc_parse_string(rcstring);
7620 g_free(rcstring);
7621 button = gtk_button_new();
7622 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7623 gtk_icon_size = icon_size_map(size ? size : icon_size);
7625 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7626 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7627 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7628 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7629 gtk_widget_set_name(button, name);
7630 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7632 return (button);
7635 void
7636 button_set_stockid(GtkWidget *button, char *stockid)
7638 GtkWidget *image;
7640 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7641 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7642 gtk_button_set_image(GTK_BUTTON(button), image);
7645 void
7646 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
7648 GtkClipboard *clipboard;
7649 gchar *p = NULL, *s = NULL;
7652 * This code is very aggressive!
7653 * It basically ensures that the primary and regular clipboard are
7654 * always set the same. This obviously messes with standard X protocol
7655 * but those clowns should have come up with something better.
7658 /* XXX make this setting? */
7659 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7660 p = gtk_clipboard_wait_for_text(primary);
7661 if (p == NULL) {
7662 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
7663 p = gtk_clipboard_wait_for_text(clipboard);
7664 if (p)
7665 gtk_clipboard_set_text(primary, p, -1);
7666 } else {
7667 DNPRINTF(XT_D_CLIP, "primary got selection\n");
7668 s = gtk_clipboard_wait_for_text(clipboard);
7669 if (s) {
7671 * if s and p are the same the string was set by
7672 * clipb_clipboard_cb so do nothing in that case
7673 * to prevent endless loop
7675 if (!strcmp(s, p))
7676 goto done;
7678 gtk_clipboard_set_text(clipboard, p, -1);
7680 done:
7681 if (p)
7682 g_free(p);
7683 if (s)
7684 g_free(s);
7687 void
7688 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
7690 GtkClipboard *primary;
7691 gchar *p = NULL, *s = NULL;
7693 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
7695 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7696 p = gtk_clipboard_wait_for_text(clipboard);
7697 if (p) {
7698 s = gtk_clipboard_wait_for_text(primary);
7699 if (s) {
7701 * if s and p are the same the string was set by
7702 * clipb_primary_cb so do nothing in that case
7703 * to prevent endless loop and deselection of text
7705 if (!strcmp(s, p))
7706 goto done;
7708 gtk_clipboard_set_text(primary, p, -1);
7710 done:
7711 if (p)
7712 g_free(p);
7713 if (s)
7714 g_free(s);
7717 void
7718 create_canvas(void)
7720 GtkWidget *vbox;
7721 GList *l = NULL;
7722 GdkPixbuf *pb;
7723 char file[PATH_MAX];
7724 int i;
7726 vbox = gtk_vbox_new(FALSE, 0);
7727 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7728 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7729 #if !GTK_CHECK_VERSION(3, 0, 0)
7730 /* XXX seems to be needed with gtk+2 */
7731 gtk_notebook_set_tab_hborder(notebook, 0);
7732 gtk_notebook_set_tab_vborder(notebook, 0);
7733 #endif
7734 gtk_notebook_set_scrollable(notebook, TRUE);
7735 notebook_tab_set_visibility(notebook);
7736 gtk_notebook_set_show_border(notebook, FALSE);
7737 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7739 abtn = gtk_button_new();
7740 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7741 gtk_widget_set_size_request(arrow, -1, -1);
7742 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7743 gtk_widget_set_size_request(abtn, -1, 20);
7745 #if GTK_CHECK_VERSION(2, 20, 0)
7746 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7747 #endif
7748 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7749 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7750 gtk_widget_set_size_request(vbox, -1, -1);
7752 g_object_connect(G_OBJECT(notebook),
7753 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7754 (char *)NULL);
7755 g_object_connect(G_OBJECT(notebook),
7756 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
7757 (char *)NULL);
7758 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7759 G_CALLBACK(arrow_cb), NULL);
7761 main_window = create_window();
7762 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7763 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7765 /* icons */
7766 for (i = 0; i < LENGTH(icons); i++) {
7767 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7768 pb = gdk_pixbuf_new_from_file(file, NULL);
7769 l = g_list_append(l, pb);
7771 gtk_window_set_default_icon_list(l);
7773 /* clipboard */
7774 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
7775 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
7776 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
7777 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
7779 gtk_widget_show_all(abtn);
7780 gtk_widget_show_all(main_window);
7783 void
7784 set_hook(void **hook, char *name)
7786 if (hook == NULL)
7787 errx(1, "set_hook");
7789 if (*hook == NULL) {
7790 *hook = dlsym(RTLD_NEXT, name);
7791 if (*hook == NULL)
7792 errx(1, "can't hook %s", name);
7796 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7797 gboolean
7798 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7800 g_return_val_if_fail(cookie1, FALSE);
7801 g_return_val_if_fail(cookie2, FALSE);
7803 return (!strcmp (cookie1->name, cookie2->name) &&
7804 !strcmp (cookie1->value, cookie2->value) &&
7805 !strcmp (cookie1->path, cookie2->path) &&
7806 !strcmp (cookie1->domain, cookie2->domain));
7809 void
7810 transfer_cookies(void)
7812 GSList *cf;
7813 SoupCookie *sc, *pc;
7815 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7817 for (;cf; cf = cf->next) {
7818 pc = cf->data;
7819 sc = soup_cookie_copy(pc);
7820 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7823 soup_cookies_free(cf);
7826 void
7827 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7829 GSList *cf;
7830 SoupCookie *ci;
7832 print_cookie("soup_cookie_jar_delete_cookie", c);
7834 if (cookies_enabled == 0)
7835 return;
7837 if (jar == NULL || c == NULL)
7838 return;
7840 /* find and remove from persistent jar */
7841 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7843 for (;cf; cf = cf->next) {
7844 ci = cf->data;
7845 if (soup_cookie_equal(ci, c)) {
7846 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7847 break;
7851 soup_cookies_free(cf);
7853 /* delete from session jar */
7854 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7857 void
7858 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7860 struct domain *d = NULL;
7861 SoupCookie *c;
7862 FILE *r_cookie_f;
7864 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7865 jar, p_cookiejar, s_cookiejar);
7867 if (cookies_enabled == 0)
7868 return;
7870 /* see if we are up and running */
7871 if (p_cookiejar == NULL) {
7872 _soup_cookie_jar_add_cookie(jar, cookie);
7873 return;
7875 /* disallow p_cookiejar adds, shouldn't happen */
7876 if (jar == p_cookiejar)
7877 return;
7879 /* sanity */
7880 if (jar == NULL || cookie == NULL)
7881 return;
7883 if (enable_cookie_whitelist &&
7884 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7885 blocked_cookies++;
7886 DNPRINTF(XT_D_COOKIE,
7887 "soup_cookie_jar_add_cookie: reject %s\n",
7888 cookie->domain);
7889 if (save_rejected_cookies) {
7890 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7891 show_oops_s("can't open reject cookie file");
7892 return;
7894 fseek(r_cookie_f, 0, SEEK_END);
7895 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7896 cookie->http_only ? "#HttpOnly_" : "",
7897 cookie->domain,
7898 *cookie->domain == '.' ? "TRUE" : "FALSE",
7899 cookie->path,
7900 cookie->secure ? "TRUE" : "FALSE",
7901 cookie->expires ?
7902 (gulong)soup_date_to_time_t(cookie->expires) :
7904 cookie->name,
7905 cookie->value);
7906 fflush(r_cookie_f);
7907 fclose(r_cookie_f);
7909 if (!allow_volatile_cookies)
7910 return;
7913 if (cookie->expires == NULL && session_timeout) {
7914 soup_cookie_set_expires(cookie,
7915 soup_date_new_from_now(session_timeout));
7916 print_cookie("modified add cookie", cookie);
7919 /* see if we are white listed for persistence */
7920 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7921 /* add to persistent jar */
7922 c = soup_cookie_copy(cookie);
7923 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7924 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7927 /* add to session jar */
7928 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7929 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7932 void
7933 setup_cookies(void)
7935 char file[PATH_MAX];
7937 set_hook((void *)&_soup_cookie_jar_add_cookie,
7938 "soup_cookie_jar_add_cookie");
7939 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7940 "soup_cookie_jar_delete_cookie");
7942 if (cookies_enabled == 0)
7943 return;
7946 * the following code is intricate due to overriding several libsoup
7947 * functions.
7948 * do not alter order of these operations.
7951 /* rejected cookies */
7952 if (save_rejected_cookies)
7953 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
7955 /* persistent cookies */
7956 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
7957 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7959 /* session cookies */
7960 s_cookiejar = soup_cookie_jar_new();
7961 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7962 cookie_policy, (void *)NULL);
7963 transfer_cookies();
7965 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7968 void
7969 setup_proxy(char *uri)
7971 if (proxy_uri) {
7972 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7973 soup_uri_free(proxy_uri);
7974 proxy_uri = NULL;
7976 if (http_proxy) {
7977 if (http_proxy != uri) {
7978 g_free(http_proxy);
7979 http_proxy = NULL;
7983 if (uri) {
7984 http_proxy = g_strdup(uri);
7985 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7986 proxy_uri = soup_uri_new(http_proxy);
7987 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7992 send_cmd_to_socket(char *cmd)
7994 int s, len, rv = 1;
7995 struct sockaddr_un sa;
7997 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7998 warnx("%s: socket", __func__);
7999 return (rv);
8002 sa.sun_family = AF_UNIX;
8003 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8004 work_dir, XT_SOCKET_FILE);
8005 len = SUN_LEN(&sa);
8007 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8008 warnx("%s: connect", __func__);
8009 goto done;
8012 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8013 warnx("%s: send", __func__);
8014 goto done;
8017 rv = 0;
8018 done:
8019 close(s);
8020 return (rv);
8023 gboolean
8024 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8026 int s, n;
8027 char str[XT_MAX_URL_LENGTH];
8028 socklen_t t = sizeof(struct sockaddr_un);
8029 struct sockaddr_un sa;
8030 struct passwd *p;
8031 uid_t uid;
8032 gid_t gid;
8033 struct tab *tt;
8034 gint fd = g_io_channel_unix_get_fd(source);
8036 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8037 warn("accept");
8038 return (FALSE);
8041 if (getpeereid(s, &uid, &gid) == -1) {
8042 warn("getpeereid");
8043 return (FALSE);
8045 if (uid != getuid() || gid != getgid()) {
8046 warnx("unauthorized user");
8047 return (FALSE);
8050 p = getpwuid(uid);
8051 if (p == NULL) {
8052 warnx("not a valid user");
8053 return (FALSE);
8056 n = recv(s, str, sizeof(str), 0);
8057 if (n <= 0)
8058 return (FALSE);
8060 tt = TAILQ_LAST(&tabs, tab_list);
8061 cmd_execute(tt, str);
8062 return (TRUE);
8066 is_running(void)
8068 int s, len, rv = 1;
8069 struct sockaddr_un sa;
8071 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8072 warn("is_running: socket");
8073 return (-1);
8076 sa.sun_family = AF_UNIX;
8077 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8078 work_dir, XT_SOCKET_FILE);
8079 len = SUN_LEN(&sa);
8081 /* connect to see if there is a listener */
8082 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8083 rv = 0; /* not running */
8084 else
8085 rv = 1; /* already running */
8087 close(s);
8089 return (rv);
8093 build_socket(void)
8095 int s, len;
8096 struct sockaddr_un sa;
8098 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8099 warn("build_socket: socket");
8100 return (-1);
8103 sa.sun_family = AF_UNIX;
8104 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8105 work_dir, XT_SOCKET_FILE);
8106 len = SUN_LEN(&sa);
8108 /* connect to see if there is a listener */
8109 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8110 /* no listener so we will */
8111 unlink(sa.sun_path);
8113 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8114 warn("build_socket: bind");
8115 goto done;
8118 if (listen(s, 1) == -1) {
8119 warn("build_socket: listen");
8120 goto done;
8123 return (s);
8126 done:
8127 close(s);
8128 return (-1);
8131 gboolean
8132 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8133 GtkTreeIter *iter, struct tab *t)
8135 gchar *value;
8137 gtk_tree_model_get(model, iter, 0, &value, -1);
8138 load_uri(t, value);
8139 g_free(value);
8141 return (FALSE);
8144 gboolean
8145 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8146 GtkTreeIter *iter, struct tab *t)
8148 gchar *value;
8150 gtk_tree_model_get(model, iter, 0, &value, -1);
8151 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8152 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8153 g_free(value);
8155 return (TRUE);
8158 void
8159 completion_add_uri(const gchar *uri)
8161 GtkTreeIter iter;
8163 /* add uri to list_store */
8164 gtk_list_store_append(completion_model, &iter);
8165 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8168 gboolean
8169 completion_match(GtkEntryCompletion *completion, const gchar *key,
8170 GtkTreeIter *iter, gpointer user_data)
8172 gchar *value;
8173 gboolean match = FALSE;
8175 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8176 -1);
8178 if (value == NULL)
8179 return FALSE;
8181 match = match_uri(value, key);
8183 g_free(value);
8184 return (match);
8187 void
8188 completion_add(struct tab *t)
8190 /* enable completion for tab */
8191 t->completion = gtk_entry_completion_new();
8192 gtk_entry_completion_set_text_column(t->completion, 0);
8193 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8194 gtk_entry_completion_set_model(t->completion,
8195 GTK_TREE_MODEL(completion_model));
8196 gtk_entry_completion_set_match_func(t->completion, completion_match,
8197 NULL, NULL);
8198 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8199 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8200 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8201 G_CALLBACK(completion_select_cb), t);
8202 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8203 G_CALLBACK(completion_hover_cb), t);
8206 void
8207 xxx_dir(char *dir)
8209 struct stat sb;
8211 if (stat(dir, &sb)) {
8212 if (mkdir(dir, S_IRWXU) == -1)
8213 err(1, "mkdir %s", dir);
8214 if (stat(dir, &sb))
8215 err(1, "stat %s", dir);
8217 if (S_ISDIR(sb.st_mode) == 0)
8218 errx(1, "%s not a dir", dir);
8219 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8220 warnx("fixing invalid permissions on %s", dir);
8221 if (chmod(dir, S_IRWXU) == -1)
8222 err(1, "chmod %s", dir);
8226 void
8227 usage(void)
8229 fprintf(stderr,
8230 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8231 exit(0);
8236 main(int argc, char *argv[])
8238 struct stat sb;
8239 int c, s, optn = 0, opte = 0, focus = 1;
8240 char conf[PATH_MAX] = { '\0' };
8241 char file[PATH_MAX];
8242 char *env_proxy = NULL;
8243 FILE *f = NULL;
8244 struct karg a;
8245 struct sigaction sact;
8246 gchar *priority = g_strdup("NORMAL");
8247 GIOChannel *channel;
8249 start_argv = argv;
8251 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8253 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8254 switch (c) {
8255 case 'S':
8256 show_url = 0;
8257 break;
8258 case 'T':
8259 show_tabs = 0;
8260 break;
8261 case 'V':
8262 errx(0 , "Version: %s", version);
8263 break;
8264 case 'f':
8265 strlcpy(conf, optarg, sizeof(conf));
8266 break;
8267 case 's':
8268 strlcpy(named_session, optarg, sizeof(named_session));
8269 break;
8270 case 't':
8271 tabless = 1;
8272 break;
8273 case 'n':
8274 optn = 1;
8275 break;
8276 case 'e':
8277 opte = 1;
8278 break;
8279 default:
8280 usage();
8281 /* NOTREACHED */
8284 argc -= optind;
8285 argv += optind;
8287 RB_INIT(&hl);
8288 RB_INIT(&js_wl);
8289 RB_INIT(&downloads);
8291 TAILQ_INIT(&tabs);
8292 TAILQ_INIT(&mtl);
8293 TAILQ_INIT(&aliases);
8294 TAILQ_INIT(&undos);
8295 TAILQ_INIT(&kbl);
8297 init_keybindings();
8299 gnutls_global_init();
8301 /* generate session keys for xtp pages */
8302 generate_xtp_session_key(&dl_session_key);
8303 generate_xtp_session_key(&hl_session_key);
8304 generate_xtp_session_key(&cl_session_key);
8305 generate_xtp_session_key(&fl_session_key);
8307 /* prepare gtk */
8308 gtk_init(&argc, &argv);
8309 if (!g_thread_supported())
8310 g_thread_init(NULL);
8312 /* signals */
8313 bzero(&sact, sizeof(sact));
8314 sigemptyset(&sact.sa_mask);
8315 sact.sa_handler = sigchild;
8316 sact.sa_flags = SA_NOCLDSTOP;
8317 sigaction(SIGCHLD, &sact, NULL);
8319 /* set download dir */
8320 pwd = getpwuid(getuid());
8321 if (pwd == NULL)
8322 errx(1, "invalid user %d", getuid());
8323 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8325 /* set default string settings */
8326 home = g_strdup("https://www.cyphertite.com");
8327 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8328 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8329 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8331 /* read config file */
8332 if (strlen(conf) == 0)
8333 snprintf(conf, sizeof conf, "%s/.%s",
8334 pwd->pw_dir, XT_CONF_FILE);
8335 config_parse(conf, 0);
8337 /* working directory */
8338 if (strlen(work_dir) == 0)
8339 snprintf(work_dir, sizeof work_dir, "%s/%s",
8340 pwd->pw_dir, XT_DIR);
8341 xxx_dir(work_dir);
8343 /* icon cache dir */
8344 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8345 xxx_dir(cache_dir);
8347 /* certs dir */
8348 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8349 xxx_dir(certs_dir);
8351 /* sessions dir */
8352 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8353 work_dir, XT_SESSIONS_DIR);
8354 xxx_dir(sessions_dir);
8356 /* runtime settings that can override config file */
8357 if (runtime_settings[0] != '\0')
8358 config_parse(runtime_settings, 1);
8360 /* download dir */
8361 if (!strcmp(download_dir, pwd->pw_dir))
8362 strlcat(download_dir, "/downloads", sizeof download_dir);
8363 xxx_dir(download_dir);
8365 /* favorites file */
8366 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8367 if (stat(file, &sb)) {
8368 warnx("favorites file doesn't exist, creating it");
8369 if ((f = fopen(file, "w")) == NULL)
8370 err(1, "favorites");
8371 fclose(f);
8374 /* cookies */
8375 session = webkit_get_default_session();
8376 /* XXX ssl-priority property not quite available yet */
8377 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8378 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8379 (char *)NULL);
8380 else
8381 warnx("session does not have \"ssl-priority\" property");
8382 setup_cookies();
8384 /* certs */
8385 if (ssl_ca_file) {
8386 if (stat(ssl_ca_file, &sb)) {
8387 warnx("no CA file: %s", ssl_ca_file);
8388 g_free(ssl_ca_file);
8389 ssl_ca_file = NULL;
8390 } else
8391 g_object_set(session,
8392 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8393 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8394 (void *)NULL);
8397 /* proxy */
8398 env_proxy = getenv("http_proxy");
8399 if (env_proxy)
8400 setup_proxy(env_proxy);
8401 else
8402 setup_proxy(http_proxy);
8404 if (opte) {
8405 send_cmd_to_socket(argv[0]);
8406 exit(0);
8409 /* set some connection parameters */
8410 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8411 g_object_set(session, "max-conns-per-host", max_host_connections,
8412 (char *)NULL);
8414 /* see if there is already an xxxterm running */
8415 if (single_instance && is_running()) {
8416 optn = 1;
8417 warnx("already running");
8420 char *cmd = NULL;
8421 if (optn) {
8422 while (argc) {
8423 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8424 send_cmd_to_socket(cmd);
8425 if (cmd)
8426 g_free(cmd);
8428 argc--;
8429 argv++;
8431 exit(0);
8434 /* uri completion */
8435 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8437 /* go graphical */
8438 create_canvas();
8440 if (save_global_history)
8441 restore_global_history();
8443 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8444 restore_saved_tabs();
8445 else {
8446 a.s = named_session;
8447 a.i = XT_SES_DONOTHING;
8448 open_tabs(NULL, &a);
8451 while (argc) {
8452 create_new_tab(argv[0], NULL, focus, -1);
8453 focus = 0;
8455 argc--;
8456 argv++;
8459 if (TAILQ_EMPTY(&tabs))
8460 create_new_tab(home, NULL, 1, -1);
8462 if (enable_socket)
8463 if ((s = build_socket()) != -1) {
8464 channel = g_io_channel_unix_new(s);
8465 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8468 gtk_main();
8470 gnutls_global_deinit();
8472 return (0);