unbreak xtp pages in 2 places. ok marco
[xxxterm.git] / xxxterm.c
blobb11dcceb4ec796a31e39e8590bb42f647b1630e8
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <err.h>
34 #include <pwd.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pthread.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <libgen.h>
42 #include <ctype.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/stat.h>
59 #include <sys/socket.h>
60 #include <sys/un.h>
62 #include <gtk/gtk.h>
63 #include <gdk/gdkkeysyms.h>
64 #include <webkit/webkit.h>
65 #include <libsoup/soup.h>
66 #include <gnutls/gnutls.h>
67 #include <JavaScriptCore/JavaScript.h>
68 #include <gnutls/x509.h>
70 #include "javascript.h"
73 javascript.h borrowed from vimprobable2 under the following license:
75 Copyright (c) 2009 Leon Winter
76 Copyright (c) 2009 Hannes Schueller
77 Copyright (c) 2009 Matto Fransen
79 Permission is hereby granted, free of charge, to any person obtaining a copy
80 of this software and associated documentation files (the "Software"), to deal
81 in the Software without restriction, including without limitation the rights
82 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83 copies of the Software, and to permit persons to whom the Software is
84 furnished to do so, subject to the following conditions:
86 The above copyright notice and this permission notice shall be included in
87 all copies or substantial portions of the Software.
89 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
95 THE SOFTWARE.
98 static char *version = "$xxxterm$";
100 /* hooked functions */
101 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
102 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
103 SoupCookie *);
105 /*#define XT_DEBUG*/
106 #ifdef XT_DEBUG
107 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
108 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
109 #define XT_D_MOVE 0x0001
110 #define XT_D_KEY 0x0002
111 #define XT_D_TAB 0x0004
112 #define XT_D_URL 0x0008
113 #define XT_D_CMD 0x0010
114 #define XT_D_NAV 0x0020
115 #define XT_D_DOWNLOAD 0x0040
116 #define XT_D_CONFIG 0x0080
117 #define XT_D_JS 0x0100
118 #define XT_D_FAVORITE 0x0200
119 #define XT_D_PRINTING 0x0400
120 #define XT_D_COOKIE 0x0800
121 #define XT_D_KEYBINDING 0x1000
122 u_int32_t swm_debug = 0
123 | XT_D_MOVE
124 | XT_D_KEY
125 | XT_D_TAB
126 | XT_D_URL
127 | XT_D_CMD
128 | XT_D_NAV
129 | XT_D_DOWNLOAD
130 | XT_D_CONFIG
131 | XT_D_JS
132 | XT_D_FAVORITE
133 | XT_D_PRINTING
134 | XT_D_COOKIE
135 | XT_D_KEYBINDING
137 #else
138 #define DPRINTF(x...)
139 #define DNPRINTF(n,x...)
140 #endif
142 #define LENGTH(x) (sizeof x / sizeof x[0])
143 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
144 ~(GDK_BUTTON1_MASK) & \
145 ~(GDK_BUTTON2_MASK) & \
146 ~(GDK_BUTTON3_MASK) & \
147 ~(GDK_BUTTON4_MASK) & \
148 ~(GDK_BUTTON5_MASK))
150 char *icons[] = {
151 "xxxtermicon16.png",
152 "xxxtermicon32.png",
153 "xxxtermicon48.png",
154 "xxxtermicon64.png",
155 "xxxtermicon128.png"
158 struct tab {
159 TAILQ_ENTRY(tab) entry;
160 GtkWidget *vbox;
161 GtkWidget *tab_content;
162 GtkWidget *label;
163 GtkWidget *spinner;
164 GtkWidget *uri_entry;
165 GtkWidget *search_entry;
166 GtkWidget *toolbar;
167 GtkWidget *browser_win;
168 GtkWidget *statusbar;
169 GtkWidget *cmd;
170 GtkWidget *oops;
171 GtkWidget *backward;
172 GtkWidget *forward;
173 GtkWidget *stop;
174 GtkWidget *gohome;
175 GtkWidget *js_toggle;
176 GtkEntryCompletion *completion;
177 guint tab_id;
178 WebKitWebView *wv;
180 WebKitWebHistoryItem *item;
181 WebKitWebBackForwardList *bfl;
183 /* favicon */
184 WebKitNetworkRequest *icon_request;
185 WebKitDownload *icon_download;
186 GdkPixbuf *icon_pixbuf;
187 gchar *icon_dest_uri;
189 /* adjustments for browser */
190 GtkScrollbar *sb_h;
191 GtkScrollbar *sb_v;
192 GtkAdjustment *adjust_h;
193 GtkAdjustment *adjust_v;
195 /* flags */
196 int focus_wv;
197 int ctrl_click;
198 gchar *status;
199 int xtp_meaning; /* identifies dls/favorites */
201 /* hints */
202 int hints_on;
203 int hint_mode;
204 #define XT_HINT_NONE (0)
205 #define XT_HINT_NUMERICAL (1)
206 #define XT_HINT_ALPHANUM (2)
207 char hint_buf[128];
208 char hint_num[128];
210 /* custom stylesheet */
211 int styled;
212 char *stylesheet;
214 /* search */
215 char *search_text;
216 int search_forward;
218 /* settings */
219 WebKitWebSettings *settings;
220 int font_size;
221 gchar *user_agent;
223 TAILQ_HEAD(tab_list, tab);
225 struct history {
226 RB_ENTRY(history) entry;
227 const gchar *uri;
228 const gchar *title;
230 RB_HEAD(history_list, history);
232 struct download {
233 RB_ENTRY(download) entry;
234 int id;
235 WebKitDownload *download;
236 struct tab *tab;
238 RB_HEAD(download_list, download);
240 struct domain {
241 RB_ENTRY(domain) entry;
242 gchar *d;
243 int handy; /* app use */
245 RB_HEAD(domain_list, domain);
247 struct undo {
248 TAILQ_ENTRY(undo) entry;
249 gchar *uri;
250 GList *history;
251 int back; /* Keeps track of how many back
252 * history items there are. */
254 TAILQ_HEAD(undo_tailq, undo);
256 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
257 int next_download_id = 1;
259 struct karg {
260 int i;
261 char *s;
264 /* defines */
265 #define XT_NAME ("XXXTerm")
266 #define XT_DIR (".xxxterm")
267 #define XT_CACHE_DIR ("cache")
268 #define XT_CERT_DIR ("certs/")
269 #define XT_SESSIONS_DIR ("sessions/")
270 #define XT_CONF_FILE ("xxxterm.conf")
271 #define XT_FAVS_FILE ("favorites")
272 #define XT_SAVED_TABS_FILE ("main_session")
273 #define XT_RESTART_TABS_FILE ("restart_tabs")
274 #define XT_SOCKET_FILE ("socket")
275 #define XT_HISTORY_FILE ("history")
276 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
277 #define XT_CB_HANDLED (TRUE)
278 #define XT_CB_PASSTHROUGH (FALSE)
279 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
280 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
281 #define XT_DLMAN_REFRESH "10"
282 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
283 "td {overflow: hidden;" \
284 " padding: 2px 2px 2px 2px;" \
285 " border: 1px solid black}\n" \
286 "tr:hover {background: #ffff99 ;}\n" \
287 "th {background-color: #cccccc;" \
288 " border: 1px solid black}" \
289 "table {border-spacing: 0; " \
290 " width: 90%%;" \
291 " border: 1px black solid;}\n" \
292 ".progress-outer{" \
293 " border: 1px solid black;" \
294 " height: 8px;" \
295 " width: 90%%;}" \
296 ".progress-inner{" \
297 " float: left;" \
298 " height: 8px;" \
299 " background: green;}" \
300 ".dlstatus{" \
301 " font-size: small;" \
302 " text-align: center;}" \
303 "</style>\n\n"
304 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
305 #define XT_MAX_UNDO_CLOSE_TAB (32)
306 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
307 #define XT_PRINT_EXTRA_MARGIN 10
309 /* colors */
310 #define XT_COLOR_RED "#cc0000"
311 #define XT_COLOR_YELLOW "#ffff66"
312 #define XT_COLOR_BLUE "lightblue"
313 #define XT_COLOR_GREEN "#99ff66"
314 #define XT_COLOR_WHITE "white"
315 #define XT_COLOR_BLACK "black"
318 * xxxterm "protocol" (xtp)
319 * We use this for managing stuff like downloads and favorites. They
320 * make magical HTML pages in memory which have xxxt:// links in order
321 * to communicate with xxxterm's internals. These links take the format:
322 * xxxt://class/session_key/action/arg
324 * Don't begin xtp class/actions as 0. atoi returns that on error.
326 * Typically we have not put addition of items in this framework, as
327 * adding items is either done via an ex-command or via a keybinding instead.
330 #define XT_XTP_STR "xxxt://"
332 /* XTP classes (xxxt://<class>) */
333 #define XT_XTP_DL 1 /* downloads */
334 #define XT_XTP_HL 2 /* history */
335 #define XT_XTP_CL 3 /* cookies */
336 #define XT_XTP_FL 4 /* favorites */
338 /* XTP download actions */
339 #define XT_XTP_DL_LIST 1
340 #define XT_XTP_DL_CANCEL 2
341 #define XT_XTP_DL_REMOVE 3
343 /* XTP history actions */
344 #define XT_XTP_HL_LIST 1
345 #define XT_XTP_HL_REMOVE 2
347 /* XTP cookie actions */
348 #define XT_XTP_CL_LIST 1
349 #define XT_XTP_CL_REMOVE 2
351 /* XTP cookie actions */
352 #define XT_XTP_FL_LIST 1
353 #define XT_XTP_FL_REMOVE 2
355 /* xtp tab meanings - identifies which tabs have xtp pages in */
356 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
357 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
358 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
359 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
360 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
362 /* actions */
363 #define XT_MOVE_INVALID (0)
364 #define XT_MOVE_DOWN (1)
365 #define XT_MOVE_UP (2)
366 #define XT_MOVE_BOTTOM (3)
367 #define XT_MOVE_TOP (4)
368 #define XT_MOVE_PAGEDOWN (5)
369 #define XT_MOVE_PAGEUP (6)
370 #define XT_MOVE_HALFDOWN (7)
371 #define XT_MOVE_HALFUP (8)
372 #define XT_MOVE_LEFT (9)
373 #define XT_MOVE_FARLEFT (10)
374 #define XT_MOVE_RIGHT (11)
375 #define XT_MOVE_FARRIGHT (12)
377 #define XT_TAB_LAST (-4)
378 #define XT_TAB_FIRST (-3)
379 #define XT_TAB_PREV (-2)
380 #define XT_TAB_NEXT (-1)
381 #define XT_TAB_INVALID (0)
382 #define XT_TAB_NEW (1)
383 #define XT_TAB_DELETE (2)
384 #define XT_TAB_DELQUIT (3)
385 #define XT_TAB_OPEN (4)
386 #define XT_TAB_UNDO_CLOSE (5)
387 #define XT_TAB_SHOW (6)
388 #define XT_TAB_HIDE (7)
390 #define XT_NAV_INVALID (0)
391 #define XT_NAV_BACK (1)
392 #define XT_NAV_FORWARD (2)
393 #define XT_NAV_RELOAD (3)
394 #define XT_NAV_RELOAD_CACHE (4)
396 #define XT_FOCUS_INVALID (0)
397 #define XT_FOCUS_URI (1)
398 #define XT_FOCUS_SEARCH (2)
400 #define XT_SEARCH_INVALID (0)
401 #define XT_SEARCH_NEXT (1)
402 #define XT_SEARCH_PREV (2)
404 #define XT_PASTE_CURRENT_TAB (0)
405 #define XT_PASTE_NEW_TAB (1)
407 #define XT_FONT_SET (0)
409 #define XT_URL_SHOW (1)
410 #define XT_URL_HIDE (2)
412 #define XT_STATUSBAR_SHOW (1)
413 #define XT_STATUSBAR_HIDE (2)
415 #define XT_WL_TOGGLE (1<<0)
416 #define XT_WL_ENABLE (1<<1)
417 #define XT_WL_DISABLE (1<<2)
418 #define XT_WL_FQDN (1<<3) /* default */
419 #define XT_WL_TOPLEVEL (1<<4)
420 #define XT_WL_PERSISTENT (1<<5)
421 #define XT_WL_SESSION (1<<6)
423 #define XT_SHOW (1<<7)
424 #define XT_DELETE (1<<8)
425 #define XT_SAVE (1<<9)
426 #define XT_OPEN (1<<10)
428 #define XT_CMD_OPEN (0)
429 #define XT_CMD_OPEN_CURRENT (1)
430 #define XT_CMD_TABNEW (2)
431 #define XT_CMD_TABNEW_CURRENT (3)
433 #define XT_STATUS_NOTHING (0)
434 #define XT_STATUS_LINK (1)
435 #define XT_STATUS_URI (2)
436 #define XT_STATUS_LOADING (3)
438 #define XT_SES_DONOTHING (0)
439 #define XT_SES_CLOSETABS (1)
441 #define XT_BM_NORMAL (0)
442 #define XT_BM_WHITELIST (1)
443 #define XT_BM_KIOSK (2)
445 /* mime types */
446 struct mime_type {
447 char *mt_type;
448 char *mt_action;
449 int mt_default;
450 int mt_download;
451 TAILQ_ENTRY(mime_type) entry;
453 TAILQ_HEAD(mime_type_list, mime_type);
455 /* uri aliases */
456 struct alias {
457 char *a_name;
458 char *a_uri;
459 TAILQ_ENTRY(alias) entry;
461 TAILQ_HEAD(alias_list, alias);
463 /* settings that require restart */
464 int tabless = 0; /* allow only 1 tab */
465 int enable_socket = 0;
466 int single_instance = 0; /* only allow one xxxterm to run */
467 int fancy_bar = 1; /* fancy toolbar */
468 int browser_mode = XT_BM_NORMAL;
470 /* runtime settings */
471 int show_tabs = 1; /* show tabs on notebook */
472 int show_url = 1; /* show url toolbar on notebook */
473 int show_statusbar = 0; /* vimperator style status bar */
474 int ctrl_click_focus = 0; /* ctrl click gets focus */
475 int cookies_enabled = 1; /* enable cookies */
476 int read_only_cookies = 0; /* enable to not write cookies */
477 int enable_scripts = 1;
478 int enable_plugins = 0;
479 int default_font_size = 12;
480 gfloat default_zoom_level = 1.0;
481 int window_height = 768;
482 int window_width = 1024;
483 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
484 unsigned refresh_interval = 10; /* download refresh interval */
485 int enable_cookie_whitelist = 0;
486 int enable_js_whitelist = 0;
487 time_t session_timeout = 3600; /* cookie session timeout */
488 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
489 char *ssl_ca_file = NULL;
490 char *resource_dir = NULL;
491 gboolean ssl_strict_certs = FALSE;
492 int append_next = 1; /* append tab after current tab */
493 char *home = NULL;
494 char *search_string = NULL;
495 char *http_proxy = NULL;
496 char download_dir[PATH_MAX];
497 char runtime_settings[PATH_MAX]; /* override of settings */
498 int allow_volatile_cookies = 0;
499 int save_global_history = 0; /* save global history to disk */
500 char *user_agent = NULL;
501 int save_rejected_cookies = 0;
502 time_t session_autosave = 0;
503 int guess_search = 0;
504 int dns_prefetch = FALSE;
505 gint max_connections = 25;
506 gint max_host_connections = 5;
508 struct settings;
509 struct key_binding;
510 int set_download_dir(struct settings *, char *);
511 int set_work_dir(struct settings *, char *);
512 int set_runtime_dir(struct settings *, char *);
513 int set_browser_mode(struct settings *, char *);
514 int set_cookie_policy(struct settings *, char *);
515 int add_alias(struct settings *, char *);
516 int add_mime_type(struct settings *, char *);
517 int add_cookie_wl(struct settings *, char *);
518 int add_js_wl(struct settings *, char *);
519 int add_kb(struct settings *, char *);
520 void button_set_stockid(GtkWidget *, char *);
521 GtkWidget * create_button(char *, char *, int);
523 char *get_browser_mode(struct settings *);
524 char *get_cookie_policy(struct settings *);
526 char *get_download_dir(struct settings *);
527 char *get_work_dir(struct settings *);
528 char *get_runtime_dir(struct settings *);
530 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
531 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
532 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
533 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
534 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
536 struct special {
537 int (*set)(struct settings *, char *);
538 char *(*get)(struct settings *);
539 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
542 struct special s_browser_mode = {
543 set_browser_mode,
544 get_browser_mode,
545 NULL
548 struct special s_cookie = {
549 set_cookie_policy,
550 get_cookie_policy,
551 NULL
554 struct special s_alias = {
555 add_alias,
556 NULL,
557 walk_alias
560 struct special s_mime = {
561 add_mime_type,
562 NULL,
563 walk_mime_type
566 struct special s_js = {
567 add_js_wl,
568 NULL,
569 walk_js_wl
572 struct special s_kb = {
573 add_kb,
574 NULL,
575 walk_kb
578 struct special s_cookie_wl = {
579 add_cookie_wl,
580 NULL,
581 walk_cookie_wl
584 struct special s_download_dir = {
585 set_download_dir,
586 get_download_dir,
587 NULL
590 struct special s_work_dir = {
591 set_work_dir,
592 get_work_dir,
593 NULL
596 struct settings {
597 char *name;
598 int type;
599 #define XT_S_INVALID (0)
600 #define XT_S_INT (1)
601 #define XT_S_STR (2)
602 #define XT_S_FLOAT (3)
603 uint32_t flags;
604 #define XT_SF_RESTART (1<<0)
605 #define XT_SF_RUNTIME (1<<1)
606 int *ival;
607 char **sval;
608 struct special *s;
609 gfloat *fval;
610 } rs[] = {
611 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
612 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
613 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
614 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
615 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
616 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
617 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
618 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
619 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
620 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
621 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
622 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
623 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
624 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
625 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
626 { "home", XT_S_STR, 0, NULL, &home, NULL },
627 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
628 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
629 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
630 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
631 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
632 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
633 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
634 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
635 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
636 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
637 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
638 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
639 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
640 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
641 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
642 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
643 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
644 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
645 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
646 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
647 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
648 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
649 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
651 /* runtime settings */
652 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
653 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
654 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
655 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
656 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
659 int about(struct tab *, struct karg *);
660 int blank(struct tab *, struct karg *);
661 int cookie_show_wl(struct tab *, struct karg *);
662 int js_show_wl(struct tab *, struct karg *);
663 int help(struct tab *, struct karg *);
664 int set(struct tab *, struct karg *);
665 int stats(struct tab *, struct karg *);
666 int marco(struct tab *, struct karg *);
667 const char * marco_message(int *);
668 int xtp_page_cl(struct tab *, struct karg *);
669 int xtp_page_dl(struct tab *, struct karg *);
670 int xtp_page_fl(struct tab *, struct karg *);
671 int xtp_page_hl(struct tab *, struct karg *);
673 #define XT_URI_ABOUT ("about:")
674 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
675 #define XT_URI_ABOUT_ABOUT ("about")
676 #define XT_URI_ABOUT_BLANK ("blank")
677 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
678 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
679 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
680 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
681 #define XT_URI_ABOUT_FAVORITES ("favorites")
682 #define XT_URI_ABOUT_HELP ("help")
683 #define XT_URI_ABOUT_HISTORY ("history")
684 #define XT_URI_ABOUT_JSWL ("jswl")
685 #define XT_URI_ABOUT_SET ("set")
686 #define XT_URI_ABOUT_STATS ("stats")
687 #define XT_URI_ABOUT_MARCO ("marco")
689 struct about_type {
690 char *name;
691 int (*func)(struct tab *, struct karg *);
692 } about_list[] = {
693 { XT_URI_ABOUT_ABOUT, about },
694 { XT_URI_ABOUT_BLANK, blank },
695 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
696 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
697 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
698 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
699 { XT_URI_ABOUT_HELP, help },
700 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
701 { XT_URI_ABOUT_JSWL, js_show_wl },
702 { XT_URI_ABOUT_SET, set },
703 { XT_URI_ABOUT_STATS, stats },
704 { XT_URI_ABOUT_MARCO, marco },
707 /* globals */
708 extern char *__progname;
709 char **start_argv;
710 struct passwd *pwd;
711 GtkWidget *main_window;
712 GtkNotebook *notebook;
713 GtkWidget *arrow, *abtn;
714 struct tab_list tabs;
715 struct history_list hl;
716 struct download_list downloads;
717 struct domain_list c_wl;
718 struct domain_list js_wl;
719 struct undo_tailq undos;
720 struct keybinding_list kbl;
721 int undo_count;
722 int updating_dl_tabs = 0;
723 int updating_hl_tabs = 0;
724 int updating_cl_tabs = 0;
725 int updating_fl_tabs = 0;
726 char *global_search;
727 uint64_t blocked_cookies = 0;
728 char named_session[PATH_MAX];
729 void update_favicon(struct tab *);
730 int icon_size_map(int);
732 GtkListStore *completion_model;
733 void completion_add(struct tab *);
734 void completion_add_uri(const gchar *);
736 void
737 sigchild(int sig)
739 int saved_errno, status;
740 pid_t pid;
742 saved_errno = errno;
744 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
745 if (pid == -1) {
746 if (errno == EINTR)
747 continue;
748 if (errno != ECHILD) {
750 clog_warn("sigchild: waitpid:");
753 break;
756 if (WIFEXITED(status)) {
757 if (WEXITSTATUS(status) != 0) {
759 clog_warnx("sigchild: child exit status: %d",
760 WEXITSTATUS(status));
763 } else {
765 clog_warnx("sigchild: child is terminated abnormally");
770 errno = saved_errno;
774 is_g_object_setting(GObject *o, char *str)
776 guint n_props = 0, i;
777 GParamSpec **proplist;
779 if (! G_IS_OBJECT(o))
780 return (0);
782 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
783 &n_props);
785 for (i=0; i < n_props; i++) {
786 if (! strcmp(proplist[i]->name, str))
787 return (1);
789 return (0);
793 * Display a web page from a HTML string in memory, rather than from a URL
795 void
796 load_webkit_string(struct tab *t, const char *str, gchar *title)
798 gchar *uri;
799 char file[PATH_MAX];
800 GdkPixbuf *pb;
802 /* we set this to indicate we want to manually do navaction */
803 if (t->bfl)
804 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
806 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
807 #if GTK_CHECK_VERSION(2, 20, 0)
808 gtk_spinner_stop(GTK_SPINNER(t->spinner));
809 gtk_widget_hide(t->spinner);
810 #endif
812 if (title) {
813 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
814 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
815 g_free(uri);
817 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
818 pb = gdk_pixbuf_new_from_file(file, NULL);
819 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
820 GTK_ENTRY_ICON_PRIMARY, pb);
821 gdk_pixbuf_unref(pb);
825 void
826 set_status(struct tab *t, gchar *s, int status)
828 gchar *type = NULL;
830 if (s == NULL)
831 return;
833 switch (status) {
834 case XT_STATUS_LOADING:
835 type = g_strdup_printf("Loading: %s", s);
836 s = type;
837 break;
838 case XT_STATUS_LINK:
839 type = g_strdup_printf("Link: %s", s);
840 if (!t->status)
841 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
842 s = type;
843 break;
844 case XT_STATUS_URI:
845 type = g_strdup_printf("%s", s);
846 if (!t->status) {
847 t->status = g_strdup(type);
849 s = type;
850 if (!t->status)
851 t->status = g_strdup(s);
852 break;
853 case XT_STATUS_NOTHING:
854 /* FALL THROUGH */
855 default:
856 break;
858 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
859 if (type)
860 g_free(type);
863 void
864 hide_oops(struct tab *t)
866 gtk_widget_hide(t->oops);
869 void
870 hide_cmd(struct tab *t)
872 gtk_widget_hide(t->cmd);
875 void
876 show_cmd(struct tab *t)
878 gtk_widget_hide(t->oops);
879 gtk_widget_show(t->cmd);
882 void
883 show_oops(struct tab *t, const char *fmt, ...)
885 va_list ap;
886 char *msg;
888 if (fmt == NULL)
889 return;
891 va_start(ap, fmt);
892 if (vasprintf(&msg, fmt, ap) == -1)
893 errx(1, "show_oops failed");
894 va_end(ap);
896 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
897 gtk_widget_hide(t->cmd);
898 gtk_widget_show(t->oops);
901 /* XXX collapse with show_oops */
902 void
903 show_oops_s(const char *fmt, ...)
905 va_list ap;
906 char *msg;
907 struct tab *ti, *t = NULL;
909 if (fmt == NULL)
910 return;
912 TAILQ_FOREACH(ti, &tabs, entry)
913 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
914 t = ti;
915 break;
917 if (t == NULL)
918 return;
920 va_start(ap, fmt);
921 if (vasprintf(&msg, fmt, ap) == -1)
922 errx(1, "show_oops_s failed");
923 va_end(ap);
925 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
926 gtk_widget_hide(t->cmd);
927 gtk_widget_show(t->oops);
930 char *
931 get_as_string(struct settings *s)
933 char *r = NULL;
935 if (s == NULL)
936 return (NULL);
938 if (s->s) {
939 if (s->s->get)
940 r = s->s->get(s);
941 else
942 warnx("get_as_string skip %s\n", s->name);
943 } else if (s->type == XT_S_INT)
944 r = g_strdup_printf("%d", *s->ival);
945 else if (s->type == XT_S_STR)
946 r = g_strdup(*s->sval);
947 else if (s->type == XT_S_FLOAT)
948 r = g_strdup_printf("%f", *s->fval);
949 else
950 r = g_strdup_printf("INVALID TYPE");
952 return (r);
955 void
956 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
958 int i;
959 char *s;
961 for (i = 0; i < LENGTH(rs); i++) {
962 if (rs[i].s && rs[i].s->walk)
963 rs[i].s->walk(&rs[i], cb, cb_args);
964 else {
965 s = get_as_string(&rs[i]);
966 cb(&rs[i], s, cb_args);
967 g_free(s);
973 set_browser_mode(struct settings *s, char *val)
975 if (!strcmp(val, "whitelist")) {
976 browser_mode = XT_BM_WHITELIST;
977 allow_volatile_cookies = 0;
978 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
979 cookies_enabled = 1;
980 enable_cookie_whitelist = 1;
981 read_only_cookies = 0;
982 save_rejected_cookies = 0;
983 session_timeout = 3600;
984 enable_scripts = 0;
985 enable_js_whitelist = 1;
986 } else if (!strcmp(val, "normal")) {
987 browser_mode = XT_BM_NORMAL;
988 allow_volatile_cookies = 0;
989 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
990 cookies_enabled = 1;
991 enable_cookie_whitelist = 0;
992 read_only_cookies = 0;
993 save_rejected_cookies = 0;
994 session_timeout = 3600;
995 enable_scripts = 1;
996 enable_js_whitelist = 0;
997 } else if (!strcmp(val, "kiosk")) {
998 browser_mode = XT_BM_KIOSK;
999 allow_volatile_cookies = 0;
1000 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1001 cookies_enabled = 1;
1002 enable_cookie_whitelist = 0;
1003 read_only_cookies = 0;
1004 save_rejected_cookies = 0;
1005 session_timeout = 3600;
1006 enable_scripts = 1;
1007 enable_js_whitelist = 0;
1008 show_tabs = 0;
1009 tabless = 1;
1010 } else
1011 return (1);
1013 return (0);
1016 char *
1017 get_browser_mode(struct settings *s)
1019 char *r = NULL;
1021 if (browser_mode == XT_BM_WHITELIST)
1022 r = g_strdup("whitelist");
1023 else if (browser_mode == XT_BM_NORMAL)
1024 r = g_strdup("normal");
1025 else if (browser_mode == XT_BM_KIOSK)
1026 r = g_strdup("kiosk");
1027 else
1028 return (NULL);
1030 return (r);
1034 set_cookie_policy(struct settings *s, char *val)
1036 if (!strcmp(val, "no3rdparty"))
1037 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1038 else if (!strcmp(val, "accept"))
1039 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1040 else if (!strcmp(val, "reject"))
1041 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1042 else
1043 return (1);
1045 return (0);
1048 char *
1049 get_cookie_policy(struct settings *s)
1051 char *r = NULL;
1053 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1054 r = g_strdup("no3rdparty");
1055 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1056 r = g_strdup("accept");
1057 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1058 r = g_strdup("reject");
1059 else
1060 return (NULL);
1062 return (r);
1065 char *
1066 get_download_dir(struct settings *s)
1068 if (download_dir[0] == '\0')
1069 return (0);
1070 return (g_strdup(download_dir));
1074 set_download_dir(struct settings *s, char *val)
1076 if (val[0] == '~')
1077 snprintf(download_dir, sizeof download_dir, "%s/%s",
1078 pwd->pw_dir, &val[1]);
1079 else
1080 strlcpy(download_dir, val, sizeof download_dir);
1082 return (0);
1086 * Session IDs.
1087 * We use these to prevent people putting xxxt:// URLs on
1088 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1090 #define XT_XTP_SES_KEY_SZ 8
1091 #define XT_XTP_SES_KEY_HEX_FMT \
1092 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1093 char *dl_session_key; /* downloads */
1094 char *hl_session_key; /* history list */
1095 char *cl_session_key; /* cookie list */
1096 char *fl_session_key; /* favorites list */
1098 char work_dir[PATH_MAX];
1099 char certs_dir[PATH_MAX];
1100 char cache_dir[PATH_MAX];
1101 char sessions_dir[PATH_MAX];
1102 char cookie_file[PATH_MAX];
1103 SoupURI *proxy_uri = NULL;
1104 SoupSession *session;
1105 SoupCookieJar *s_cookiejar;
1106 SoupCookieJar *p_cookiejar;
1107 char rc_fname[PATH_MAX];
1109 struct mime_type_list mtl;
1110 struct alias_list aliases;
1112 /* protos */
1113 struct tab *create_new_tab(char *, struct undo *, int);
1114 void delete_tab(struct tab *);
1115 void adjustfont_webkit(struct tab *, int);
1116 int run_script(struct tab *, char *);
1117 int download_rb_cmp(struct download *, struct download *);
1118 gboolean cmd_execute(struct tab *t, char *str);
1121 history_rb_cmp(struct history *h1, struct history *h2)
1123 return (strcmp(h1->uri, h2->uri));
1125 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1128 domain_rb_cmp(struct domain *d1, struct domain *d2)
1130 return (strcmp(d1->d, d2->d));
1132 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1134 char *
1135 get_work_dir(struct settings *s)
1137 if (work_dir[0] == '\0')
1138 return (0);
1139 return (g_strdup(work_dir));
1143 set_work_dir(struct settings *s, char *val)
1145 if (val[0] == '~')
1146 snprintf(work_dir, sizeof work_dir, "%s/%s",
1147 pwd->pw_dir, &val[1]);
1148 else
1149 strlcpy(work_dir, val, sizeof work_dir);
1151 return (0);
1155 * generate a session key to secure xtp commands.
1156 * pass in a ptr to the key in question and it will
1157 * be modified in place.
1159 void
1160 generate_xtp_session_key(char **key)
1162 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1164 /* free old key */
1165 if (*key)
1166 g_free(*key);
1168 /* make a new one */
1169 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1170 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1171 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1172 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1174 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1178 * validate a xtp session key.
1179 * return 1 if OK
1182 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1184 if (strcmp(trusted, untrusted) != 0) {
1185 show_oops(t, "%s: xtp session key mismatch possible spoof",
1186 __func__);
1187 return (0);
1190 return (1);
1194 download_rb_cmp(struct download *e1, struct download *e2)
1196 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1198 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1200 struct valid_url_types {
1201 char *type;
1202 } vut[] = {
1203 { "http://" },
1204 { "https://" },
1205 { "ftp://" },
1206 { "file://" },
1207 { XT_XTP_STR },
1211 valid_url_type(char *url)
1213 int i;
1215 for (i = 0; i < LENGTH(vut); i++)
1216 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1217 return (0);
1219 return (1);
1222 void
1223 print_cookie(char *msg, SoupCookie *c)
1225 if (c == NULL)
1226 return;
1228 if (msg)
1229 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1230 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1231 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1232 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1233 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1234 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1235 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1236 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1237 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1238 DNPRINTF(XT_D_COOKIE, "====================================\n");
1241 void
1242 walk_alias(struct settings *s,
1243 void (*cb)(struct settings *, char *, void *), void *cb_args)
1245 struct alias *a;
1246 char *str;
1248 if (s == NULL || cb == NULL) {
1249 show_oops_s("walk_alias invalid parameters");
1250 return;
1253 TAILQ_FOREACH(a, &aliases, entry) {
1254 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1255 cb(s, str, cb_args);
1256 g_free(str);
1260 char *
1261 match_alias(char *url_in)
1263 struct alias *a;
1264 char *arg;
1265 char *url_out = NULL, *search, *enc_arg;
1267 search = g_strdup(url_in);
1268 arg = search;
1269 if (strsep(&arg, " \t") == NULL) {
1270 show_oops_s("match_alias: NULL URL");
1271 goto done;
1274 TAILQ_FOREACH(a, &aliases, entry) {
1275 if (!strcmp(search, a->a_name))
1276 break;
1279 if (a != NULL) {
1280 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1281 a->a_name);
1282 if (arg != NULL) {
1283 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1284 url_out = g_strdup_printf(a->a_uri, enc_arg);
1285 g_free(enc_arg);
1286 } else
1287 url_out = g_strdup(a->a_uri);
1289 done:
1290 g_free(search);
1291 return (url_out);
1294 char *
1295 guess_url_type(char *url_in)
1297 struct stat sb;
1298 char *url_out = NULL, *enc_search = NULL;
1300 url_out = match_alias(url_in);
1301 if (url_out != NULL)
1302 return (url_out);
1304 if (guess_search) {
1306 * If there is no dot nor slash in the string and it isn't a
1307 * path to a local file and doesn't resolves to an IP, assume
1308 * that the user wants to search for the string.
1311 if (strchr(url_in, '.') == NULL &&
1312 strchr(url_in, '/') == NULL &&
1313 stat(url_in, &sb) != 0 &&
1314 gethostbyname(url_in) == NULL) {
1316 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1317 url_out = g_strdup_printf(search_string, enc_search);
1318 g_free(enc_search);
1319 return (url_out);
1323 /* XXX not sure about this heuristic */
1324 if (stat(url_in, &sb) == 0)
1325 url_out = g_strdup_printf("file://%s", url_in);
1326 else
1327 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1329 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1331 return (url_out);
1334 void
1335 load_uri(struct tab *t, gchar *uri)
1337 struct karg args;
1338 gchar *newuri = NULL;
1339 int i;
1341 if (uri == NULL)
1342 return;
1344 /* Strip leading spaces. */
1345 while(*uri && isspace(*uri))
1346 uri++;
1348 if (strlen(uri) == 0) {
1349 blank(t, NULL);
1350 return;
1353 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1354 for (i = 0; i < LENGTH(about_list); i++)
1355 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1356 bzero(&args, sizeof args);
1357 about_list[i].func(t, &args);
1358 return;
1360 show_oops(t, "invalid about page");
1361 return;
1364 if (valid_url_type(uri)) {
1365 newuri = guess_url_type(uri);
1366 uri = newuri;
1369 set_status(t, (char *)uri, XT_STATUS_LOADING);
1370 webkit_web_view_load_uri(t->wv, uri);
1372 if (newuri)
1373 g_free(newuri);
1376 const gchar *
1377 get_uri(WebKitWebView *wv)
1379 WebKitWebFrame *frame;
1380 const gchar *uri;
1382 frame = webkit_web_view_get_main_frame(wv);
1383 uri = webkit_web_frame_get_uri(frame);
1385 if (uri && strlen(uri) > 0)
1386 return (uri);
1387 else
1388 return (NULL);
1392 add_alias(struct settings *s, char *line)
1394 char *l, *alias;
1395 struct alias *a = NULL;
1397 if (s == NULL || line == NULL) {
1398 show_oops_s("add_alias invalid parameters");
1399 return (1);
1402 l = line;
1403 a = g_malloc(sizeof(*a));
1405 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1406 show_oops_s("add_alias: incomplete alias definition");
1407 goto bad;
1409 if (strlen(alias) == 0 || strlen(l) == 0) {
1410 show_oops_s("add_alias: invalid alias definition");
1411 goto bad;
1414 a->a_name = g_strdup(alias);
1415 a->a_uri = g_strdup(l);
1417 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1419 TAILQ_INSERT_TAIL(&aliases, a, entry);
1421 return (0);
1422 bad:
1423 if (a)
1424 g_free(a);
1425 return (1);
1429 add_mime_type(struct settings *s, char *line)
1431 char *mime_type;
1432 char *l;
1433 struct mime_type *m = NULL;
1434 int downloadfirst = 0;
1436 /* XXX this could be smarter */
1438 if (line == NULL && strlen(line) == 0) {
1439 show_oops_s("add_mime_type invalid parameters");
1440 return (1);
1443 l = line;
1444 if (*l == '@') {
1445 downloadfirst = 1;
1446 l++;
1448 m = g_malloc(sizeof(*m));
1450 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1451 show_oops_s("add_mime_type: invalid mime_type");
1452 goto bad;
1454 if (mime_type[strlen(mime_type) - 1] == '*') {
1455 mime_type[strlen(mime_type) - 1] = '\0';
1456 m->mt_default = 1;
1457 } else
1458 m->mt_default = 0;
1460 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1461 show_oops_s("add_mime_type: invalid mime_type");
1462 goto bad;
1465 m->mt_type = g_strdup(mime_type);
1466 m->mt_action = g_strdup(l);
1467 m->mt_download = downloadfirst;
1469 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1470 m->mt_type, m->mt_action, m->mt_default);
1472 TAILQ_INSERT_TAIL(&mtl, m, entry);
1474 return (0);
1475 bad:
1476 if (m)
1477 g_free(m);
1478 return (1);
1481 struct mime_type *
1482 find_mime_type(char *mime_type)
1484 struct mime_type *m, *def = NULL, *rv = NULL;
1486 TAILQ_FOREACH(m, &mtl, entry) {
1487 if (m->mt_default &&
1488 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1489 def = m;
1491 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1492 rv = m;
1493 break;
1497 if (rv == NULL)
1498 rv = def;
1500 return (rv);
1503 void
1504 walk_mime_type(struct settings *s,
1505 void (*cb)(struct settings *, char *, void *), void *cb_args)
1507 struct mime_type *m;
1508 char *str;
1510 if (s == NULL || cb == NULL)
1511 show_oops_s("walk_mime_type invalid parameters");
1513 TAILQ_FOREACH(m, &mtl, entry) {
1514 str = g_strdup_printf("%s%s --> %s",
1515 m->mt_type,
1516 m->mt_default ? "*" : "",
1517 m->mt_action);
1518 cb(s, str, cb_args);
1519 g_free(str);
1523 void
1524 wl_add(char *str, struct domain_list *wl, int handy)
1526 struct domain *d;
1527 int add_dot = 0;
1529 if (str == NULL || wl == NULL)
1530 return;
1531 if (strlen(str) < 2)
1532 return;
1534 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1536 /* treat *.moo.com the same as .moo.com */
1537 if (str[0] == '*' && str[1] == '.')
1538 str = &str[1];
1539 else if (str[0] == '.')
1540 str = &str[0];
1541 else
1542 add_dot = 1;
1544 d = g_malloc(sizeof *d);
1545 if (add_dot)
1546 d->d = g_strdup_printf(".%s", str);
1547 else
1548 d->d = g_strdup(str);
1549 d->handy = handy;
1551 if (RB_INSERT(domain_list, wl, d))
1552 goto unwind;
1554 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1555 return;
1556 unwind:
1557 if (d) {
1558 if (d->d)
1559 g_free(d->d);
1560 g_free(d);
1565 add_cookie_wl(struct settings *s, char *entry)
1567 wl_add(entry, &c_wl, 1);
1568 return (0);
1571 void
1572 walk_cookie_wl(struct settings *s,
1573 void (*cb)(struct settings *, char *, void *), void *cb_args)
1575 struct domain *d;
1577 if (s == NULL || cb == NULL) {
1578 show_oops_s("walk_cookie_wl invalid parameters");
1579 return;
1582 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1583 cb(s, d->d, cb_args);
1586 void
1587 walk_js_wl(struct settings *s,
1588 void (*cb)(struct settings *, char *, void *), void *cb_args)
1590 struct domain *d;
1592 if (s == NULL || cb == NULL) {
1593 show_oops_s("walk_js_wl invalid parameters");
1594 return;
1597 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1598 cb(s, d->d, cb_args);
1602 add_js_wl(struct settings *s, char *entry)
1604 wl_add(entry, &js_wl, 1 /* persistent */);
1605 return (0);
1608 struct domain *
1609 wl_find(const gchar *search, struct domain_list *wl)
1611 int i;
1612 struct domain *d = NULL, dfind;
1613 gchar *s = NULL;
1615 if (search == NULL || wl == NULL)
1616 return (NULL);
1617 if (strlen(search) < 2)
1618 return (NULL);
1620 if (search[0] != '.')
1621 s = g_strdup_printf(".%s", search);
1622 else
1623 s = g_strdup(search);
1625 for (i = strlen(s) - 1; i >= 0; i--) {
1626 if (s[i] == '.') {
1627 dfind.d = &s[i];
1628 d = RB_FIND(domain_list, wl, &dfind);
1629 if (d)
1630 goto done;
1634 done:
1635 if (s)
1636 g_free(s);
1638 return (d);
1641 struct domain *
1642 wl_find_uri(const gchar *s, struct domain_list *wl)
1644 int i;
1645 char *ss;
1646 struct domain *r;
1648 if (s == NULL || wl == NULL)
1649 return (NULL);
1651 if (!strncmp(s, "http://", strlen("http://")))
1652 s = &s[strlen("http://")];
1653 else if (!strncmp(s, "https://", strlen("https://")))
1654 s = &s[strlen("https://")];
1656 if (strlen(s) < 2)
1657 return (NULL);
1659 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1660 /* chop string at first slash */
1661 if (s[i] == '/' || s[i] == '\0') {
1662 ss = g_strdup(s);
1663 ss[i] = '\0';
1664 r = wl_find(ss, wl);
1665 g_free(ss);
1666 return (r);
1669 return (NULL);
1672 char *
1673 get_toplevel_domain(char *domain)
1675 char *s;
1676 int found = 0;
1678 if (domain == NULL)
1679 return (NULL);
1680 if (strlen(domain) < 2)
1681 return (NULL);
1683 s = &domain[strlen(domain) - 1];
1684 while (s != domain) {
1685 if (*s == '.') {
1686 found++;
1687 if (found == 2)
1688 return (s);
1690 s--;
1693 if (found)
1694 return (domain);
1696 return (NULL);
1700 settings_add(char *var, char *val)
1702 int i, rv, *p;
1703 gfloat *f;
1704 char **s;
1706 /* get settings */
1707 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1708 if (strcmp(var, rs[i].name))
1709 continue;
1711 if (rs[i].s) {
1712 if (rs[i].s->set(&rs[i], val))
1713 errx(1, "invalid value for %s: %s", var, val);
1714 rv = 1;
1715 break;
1716 } else
1717 switch (rs[i].type) {
1718 case XT_S_INT:
1719 p = rs[i].ival;
1720 *p = atoi(val);
1721 rv = 1;
1722 break;
1723 case XT_S_STR:
1724 s = rs[i].sval;
1725 if (s == NULL)
1726 errx(1, "invalid sval for %s",
1727 rs[i].name);
1728 if (*s)
1729 g_free(*s);
1730 *s = g_strdup(val);
1731 rv = 1;
1732 break;
1733 case XT_S_FLOAT:
1734 f = rs[i].fval;
1735 *f = atof(val);
1736 rv = 1;
1737 break;
1738 case XT_S_INVALID:
1739 default:
1740 errx(1, "invalid type for %s", var);
1742 break;
1744 return (rv);
1747 #define WS "\n= \t"
1748 void
1749 config_parse(char *filename, int runtime)
1751 FILE *config, *f;
1752 char *line, *cp, *var, *val;
1753 size_t len, lineno = 0;
1754 int handled;
1755 char file[PATH_MAX];
1756 struct stat sb;
1758 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1760 if (filename == NULL)
1761 return;
1763 if (runtime && runtime_settings[0] != '\0') {
1764 snprintf(file, sizeof file, "%s/%s",
1765 work_dir, runtime_settings);
1766 if (stat(file, &sb)) {
1767 warnx("runtime file doesn't exist, creating it");
1768 if ((f = fopen(file, "w")) == NULL)
1769 err(1, "runtime");
1770 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1771 fclose(f);
1773 } else
1774 strlcpy(file, filename, sizeof file);
1776 if ((config = fopen(file, "r")) == NULL) {
1777 warn("config_parse: cannot open %s", filename);
1778 return;
1781 for (;;) {
1782 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1783 if (feof(config) || ferror(config))
1784 break;
1786 cp = line;
1787 cp += (long)strspn(cp, WS);
1788 if (cp[0] == '\0') {
1789 /* empty line */
1790 free(line);
1791 continue;
1794 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1795 errx(1, "invalid config file entry: %s", line);
1797 cp += (long)strspn(cp, WS);
1799 if ((val = strsep(&cp, "\0")) == NULL)
1800 break;
1802 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1803 handled = settings_add(var, val);
1804 if (handled == 0)
1805 errx(1, "invalid conf file entry: %s=%s", var, val);
1807 free(line);
1810 fclose(config);
1813 char *
1814 js_ref_to_string(JSContextRef context, JSValueRef ref)
1816 char *s = NULL;
1817 size_t l;
1818 JSStringRef jsref;
1820 jsref = JSValueToStringCopy(context, ref, NULL);
1821 if (jsref == NULL)
1822 return (NULL);
1824 l = JSStringGetMaximumUTF8CStringSize(jsref);
1825 s = g_malloc(l);
1826 if (s)
1827 JSStringGetUTF8CString(jsref, s, l);
1828 JSStringRelease(jsref);
1830 return (s);
1833 void
1834 disable_hints(struct tab *t)
1836 bzero(t->hint_buf, sizeof t->hint_buf);
1837 bzero(t->hint_num, sizeof t->hint_num);
1838 run_script(t, "vimprobable_clear()");
1839 t->hints_on = 0;
1840 t->hint_mode = XT_HINT_NONE;
1843 void
1844 enable_hints(struct tab *t)
1846 bzero(t->hint_buf, sizeof t->hint_buf);
1847 run_script(t, "vimprobable_show_hints()");
1848 t->hints_on = 1;
1849 t->hint_mode = XT_HINT_NONE;
1852 #define XT_JS_OPEN ("open;")
1853 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1854 #define XT_JS_FIRE ("fire;")
1855 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1856 #define XT_JS_FOUND ("found;")
1857 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1860 run_script(struct tab *t, char *s)
1862 JSGlobalContextRef ctx;
1863 WebKitWebFrame *frame;
1864 JSStringRef str;
1865 JSValueRef val, exception;
1866 char *es, buf[128];
1868 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1869 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1871 frame = webkit_web_view_get_main_frame(t->wv);
1872 ctx = webkit_web_frame_get_global_context(frame);
1874 str = JSStringCreateWithUTF8CString(s);
1875 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1876 NULL, 0, &exception);
1877 JSStringRelease(str);
1879 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1880 if (val == NULL) {
1881 es = js_ref_to_string(ctx, exception);
1882 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1883 g_free(es);
1884 return (1);
1885 } else {
1886 es = js_ref_to_string(ctx, val);
1887 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1889 /* handle return value right here */
1890 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1891 disable_hints(t);
1892 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1895 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1896 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1897 &es[XT_JS_FIRE_LEN]);
1898 run_script(t, buf);
1899 disable_hints(t);
1902 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1903 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1904 disable_hints(t);
1907 g_free(es);
1910 return (0);
1914 hint(struct tab *t, struct karg *args)
1917 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1919 if (t->hints_on == 0)
1920 enable_hints(t);
1921 else
1922 disable_hints(t);
1924 return (0);
1927 void
1928 apply_style(struct tab *t)
1930 g_object_set(G_OBJECT(t->settings),
1931 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1935 userstyle(struct tab *t, struct karg *args)
1937 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1939 if (t->styled) {
1940 t->styled = 0;
1941 g_object_set(G_OBJECT(t->settings),
1942 "user-stylesheet-uri", NULL, (char *)NULL);
1943 } else {
1944 t->styled = 1;
1945 apply_style(t);
1947 return (0);
1951 * Doesn't work fully, due to the following bug:
1952 * https://bugs.webkit.org/show_bug.cgi?id=51747
1955 restore_global_history(void)
1957 char file[PATH_MAX];
1958 FILE *f;
1959 struct history *h;
1960 gchar *uri;
1961 gchar *title;
1963 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1965 if ((f = fopen(file, "r")) == NULL) {
1966 warnx("%s: fopen", __func__);
1967 return (1);
1970 for (;;) {
1971 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1972 if (feof(f) || ferror(f))
1973 break;
1975 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1976 if (feof(f) || ferror(f)) {
1977 free(uri);
1978 warnx("%s: broken history file\n", __func__);
1979 return (1);
1982 if (uri && strlen(uri) && title && strlen(title)) {
1983 webkit_web_history_item_new_with_data(uri, title);
1984 h = g_malloc(sizeof(struct history));
1985 h->uri = g_strdup(uri);
1986 h->title = g_strdup(title);
1987 RB_INSERT(history_list, &hl, h);
1988 completion_add_uri(h->uri);
1989 } else {
1990 warnx("%s: failed to restore history\n", __func__);
1991 free(uri);
1992 free(title);
1993 return (1);
1996 free(uri);
1997 free(title);
1998 uri = NULL;
1999 title = NULL;
2002 return (0);
2006 save_global_history_to_disk(struct tab *t)
2008 char file[PATH_MAX];
2009 FILE *f;
2010 struct history *h;
2012 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2014 if ((f = fopen(file, "w")) == NULL) {
2015 show_oops(t, "%s: global history file: %s",
2016 __func__, strerror(errno));
2017 return (1);
2020 RB_FOREACH_REVERSE(h, history_list, &hl) {
2021 if (h->uri && h->title)
2022 fprintf(f, "%s\n%s\n", h->uri, h->title);
2025 fclose(f);
2027 return (0);
2031 quit(struct tab *t, struct karg *args)
2033 if (save_global_history)
2034 save_global_history_to_disk(t);
2036 gtk_main_quit();
2038 return (1);
2042 open_tabs(struct tab *t, struct karg *a)
2044 char file[PATH_MAX];
2045 FILE *f = NULL;
2046 char *uri = NULL;
2047 int rv = 1;
2048 struct tab *ti, *tt;
2050 if (a == NULL)
2051 goto done;
2053 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2054 if ((f = fopen(file, "r")) == NULL)
2055 goto done;
2057 ti = TAILQ_LAST(&tabs, tab_list);
2059 for (;;) {
2060 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2061 if (feof(f) || ferror(f))
2062 break;
2064 /* retrieve session name */
2065 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2066 strlcpy(named_session,
2067 &uri[strlen(XT_SAVE_SESSION_ID)],
2068 sizeof named_session);
2069 continue;
2072 if (uri && strlen(uri))
2073 create_new_tab(uri, NULL, 1);
2075 free(uri);
2076 uri = NULL;
2079 /* close open tabs */
2080 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2081 for (;;) {
2082 tt = TAILQ_FIRST(&tabs);
2083 if (tt != ti) {
2084 delete_tab(tt);
2085 continue;
2087 delete_tab(tt);
2088 break;
2092 rv = 0;
2093 done:
2094 if (f)
2095 fclose(f);
2097 return (rv);
2101 restore_saved_tabs(void)
2103 char file[PATH_MAX];
2104 int unlink_file = 0;
2105 struct stat sb;
2106 struct karg a;
2107 int rv = 0;
2109 snprintf(file, sizeof file, "%s/%s",
2110 sessions_dir, XT_RESTART_TABS_FILE);
2111 if (stat(file, &sb) == -1)
2112 a.s = XT_SAVED_TABS_FILE;
2113 else {
2114 unlink_file = 1;
2115 a.s = XT_RESTART_TABS_FILE;
2118 a.i = XT_SES_DONOTHING;
2119 rv = open_tabs(NULL, &a);
2121 if (unlink_file)
2122 unlink(file);
2124 return (rv);
2128 save_tabs(struct tab *t, struct karg *a)
2130 char file[PATH_MAX];
2131 FILE *f;
2132 struct tab *ti;
2133 const gchar *uri;
2134 int len = 0, i;
2135 const gchar **arr = NULL;
2137 if (a == NULL)
2138 return (1);
2139 if (a->s == NULL)
2140 snprintf(file, sizeof file, "%s/%s",
2141 sessions_dir, named_session);
2142 else
2143 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2145 if ((f = fopen(file, "w")) == NULL) {
2146 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2147 return (1);
2150 /* save session name */
2151 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2153 /* save tabs, in the order they are arranged in the notebook */
2154 TAILQ_FOREACH(ti, &tabs, entry)
2155 len++;
2157 arr = g_malloc0(len * sizeof(gchar *));
2159 TAILQ_FOREACH(ti, &tabs, entry) {
2160 if ((uri = get_uri(ti->wv)) != NULL)
2161 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2164 for (i = 0; i < len; i++)
2165 if (arr[i])
2166 fprintf(f, "%s\n", arr[i]);
2168 g_free(arr);
2169 fclose(f);
2171 return (0);
2175 save_tabs_and_quit(struct tab *t, struct karg *args)
2177 struct karg a;
2179 a.s = NULL;
2180 save_tabs(t, &a);
2181 quit(t, NULL);
2183 return (1);
2187 yank_uri(struct tab *t, struct karg *args)
2189 const gchar *uri;
2190 GtkClipboard *clipboard;
2192 if ((uri = get_uri(t->wv)) == NULL)
2193 return (1);
2195 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2196 gtk_clipboard_set_text(clipboard, uri, -1);
2198 return (0);
2201 struct paste_args {
2202 struct tab *t;
2203 int i;
2206 void
2207 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2209 struct paste_args *pap;
2211 if (data == NULL || text == NULL || !strlen(text))
2212 return;
2214 pap = (struct paste_args *)data;
2216 switch(pap->i) {
2217 case XT_PASTE_CURRENT_TAB:
2218 load_uri(pap->t, (gchar *)text);
2219 break;
2220 case XT_PASTE_NEW_TAB:
2221 create_new_tab((gchar *)text, NULL, 1);
2222 break;
2225 g_free(pap);
2229 paste_uri(struct tab *t, struct karg *args)
2231 GtkClipboard *clipboard;
2232 struct paste_args *pap;
2234 pap = g_malloc(sizeof(struct paste_args));
2236 pap->t = t;
2237 pap->i = args->i;
2239 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2240 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2242 return (0);
2245 char *
2246 find_domain(const gchar *s, int add_dot)
2248 int i;
2249 char *r = NULL, *ss = NULL;
2251 if (s == NULL)
2252 return (NULL);
2254 if (!strncmp(s, "http://", strlen("http://")))
2255 s = &s[strlen("http://")];
2256 else if (!strncmp(s, "https://", strlen("https://")))
2257 s = &s[strlen("https://")];
2259 if (strlen(s) < 2)
2260 return (NULL);
2262 ss = g_strdup(s);
2263 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2264 /* chop string at first slash */
2265 if (ss[i] == '/' || ss[i] == '\0') {
2266 ss[i] = '\0';
2267 if (add_dot)
2268 r = g_strdup_printf(".%s", ss);
2269 else
2270 r = g_strdup(ss);
2271 break;
2273 g_free(ss);
2275 return (r);
2279 toggle_cwl(struct tab *t, struct karg *args)
2281 struct domain *d;
2282 const gchar *uri;
2283 char *dom = NULL, *dom_toggle = NULL;
2284 int es;
2286 if (args == NULL)
2287 return (1);
2289 uri = get_uri(t->wv);
2290 dom = find_domain(uri, 1);
2291 d = wl_find(dom, &c_wl);
2293 if (d == NULL)
2294 es = 0;
2295 else
2296 es = 1;
2298 if (args->i & XT_WL_TOGGLE)
2299 es = !es;
2300 else if ((args->i & XT_WL_ENABLE) && es != 1)
2301 es = 1;
2302 else if ((args->i & XT_WL_DISABLE) && es != 0)
2303 es = 0;
2305 if (args->i & XT_WL_TOPLEVEL)
2306 dom_toggle = get_toplevel_domain(dom);
2307 else
2308 dom_toggle = dom;
2310 if (es)
2311 /* enable cookies for domain */
2312 wl_add(dom_toggle, &c_wl, 0);
2313 else
2314 /* disable cookies for domain */
2315 RB_REMOVE(domain_list, &c_wl, d);
2317 webkit_web_view_reload(t->wv);
2319 g_free(dom);
2320 return (0);
2324 toggle_js(struct tab *t, struct karg *args)
2326 int es;
2327 const gchar *uri;
2328 struct domain *d;
2329 char *dom = NULL, *dom_toggle = NULL;
2331 if (args == NULL)
2332 return (1);
2334 g_object_get(G_OBJECT(t->settings),
2335 "enable-scripts", &es, (char *)NULL);
2336 if (args->i & XT_WL_TOGGLE)
2337 es = !es;
2338 else if ((args->i & XT_WL_ENABLE) && es != 1)
2339 es = 1;
2340 else if ((args->i & XT_WL_DISABLE) && es != 0)
2341 es = 0;
2342 else
2343 return (1);
2345 uri = get_uri(t->wv);
2346 dom = find_domain(uri, 1);
2348 if (uri == NULL || dom == NULL) {
2349 show_oops(t, "Can't toggle domain in JavaScript white list");
2350 goto done;
2353 if (args->i & XT_WL_TOPLEVEL)
2354 dom_toggle = get_toplevel_domain(dom);
2355 else
2356 dom_toggle = dom;
2358 if (es) {
2359 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2360 wl_add(dom_toggle, &js_wl, 0 /* session */);
2361 } else {
2362 d = wl_find(dom_toggle, &js_wl);
2363 if (d)
2364 RB_REMOVE(domain_list, &js_wl, d);
2365 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2367 g_object_set(G_OBJECT(t->settings),
2368 "enable-scripts", es, (char *)NULL);
2369 g_object_set(G_OBJECT(t->settings),
2370 "javascript-can-open-windows-automatically", es, (char *)NULL);
2371 webkit_web_view_set_settings(t->wv, t->settings);
2372 webkit_web_view_reload(t->wv);
2373 done:
2374 if (dom)
2375 g_free(dom);
2376 return (0);
2379 void
2380 js_toggle_cb(GtkWidget *w, struct tab *t)
2382 struct karg a;
2384 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2385 toggle_js(t, &a);
2389 toggle_src(struct tab *t, struct karg *args)
2391 gboolean mode;
2393 if (t == NULL)
2394 return (0);
2396 mode = webkit_web_view_get_view_source_mode(t->wv);
2397 webkit_web_view_set_view_source_mode(t->wv, !mode);
2398 webkit_web_view_reload(t->wv);
2400 return (0);
2403 void
2404 focus_webview(struct tab *t)
2406 if (t == NULL)
2407 return;
2409 /* only grab focus if we are visible */
2410 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2411 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2415 focus(struct tab *t, struct karg *args)
2417 if (t == NULL || args == NULL)
2418 return (1);
2420 if (show_url == 0)
2421 return (0);
2423 if (args->i == XT_FOCUS_URI)
2424 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2425 else if (args->i == XT_FOCUS_SEARCH)
2426 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2428 return (0);
2432 stats(struct tab *t, struct karg *args)
2434 char *stats, *s, line[64 * 1024];
2435 uint64_t line_count = 0;
2436 FILE *r_cookie_f;
2438 if (t == NULL)
2439 show_oops_s("stats invalid parameters");
2441 line[0] = '\0';
2442 if (save_rejected_cookies) {
2443 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2444 for (;;) {
2445 s = fgets(line, sizeof line, r_cookie_f);
2446 if (s == NULL || feof(r_cookie_f) ||
2447 ferror(r_cookie_f))
2448 break;
2449 line_count++;
2451 fclose(r_cookie_f);
2452 snprintf(line, sizeof line,
2453 "<br>Cookies blocked(*) total: %llu", line_count);
2454 } else
2455 show_oops(t, "Can't open blocked cookies file: %s",
2456 strerror(errno));
2459 stats = g_strdup_printf(XT_DOCTYPE
2460 "<html>"
2461 "<head>"
2462 "<title>Statistics</title>"
2463 "</head>"
2464 "<h1>Statistics</h1>"
2465 "<body>"
2466 "Cookies blocked(*) this session: %llu"
2467 "%s"
2468 "<p><small><b>*</b> results vary based on settings"
2469 "</body>"
2470 "</html>",
2471 blocked_cookies,
2472 line);
2474 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2475 g_free(stats);
2477 return (0);
2481 marco(struct tab *t, struct karg *args)
2483 char *message, line[64 * 1024];
2484 int len;
2486 if (t == NULL)
2487 show_oops_s("marco invalid parameters");
2489 line[0] = '\0';
2490 snprintf(line, sizeof line, "<br>%s", marco_message(&len));
2492 message = g_strdup_printf(XT_DOCTYPE
2493 "<html>"
2494 "<head>"
2495 "<title>Marco Sez...</title>"
2496 "</head>"
2497 "<h1>Moo</h1>"
2498 "<body>"
2499 "%s"
2500 "</body>"
2501 "</html>",
2502 line);
2504 load_webkit_string(t, message, XT_URI_ABOUT_MARCO);
2505 g_free(message);
2507 return (0);
2511 blank(struct tab *t, struct karg *args)
2513 if (t == NULL)
2514 show_oops_s("about invalid parameters");
2516 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2518 return (0);
2521 about(struct tab *t, struct karg *args)
2523 char *about;
2525 if (t == NULL)
2526 show_oops_s("about invalid parameters");
2528 about = g_strdup_printf(XT_DOCTYPE
2529 "<html>"
2530 "<head>"
2531 "<title>About</title>"
2532 "</head>"
2533 "<h1>About</h1>"
2534 "<body>"
2535 "<b>Version: %s</b><p>"
2536 "Authors:"
2537 "<ul>"
2538 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2539 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2540 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2541 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2542 "</ul>"
2543 "Copyrights and licenses can be found on the XXXterm "
2544 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2545 "</body>"
2546 "</html>",
2547 version
2550 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2551 g_free(about);
2553 return (0);
2557 help(struct tab *t, struct karg *args)
2559 char *help;
2561 if (t == NULL)
2562 show_oops_s("help invalid parameters");
2564 help = XT_DOCTYPE
2565 "<html>"
2566 "<head>"
2567 "<title>XXXterm</title>"
2568 "<meta http-equiv=\"REFRESH\" content=\"0;"
2569 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2570 "</head>"
2571 "<body>"
2572 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2573 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2574 "cgi-bin/man-cgi?xxxterm</a>"
2575 "</body>"
2576 "</html>"
2579 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2581 return (0);
2585 * update all favorite tabs apart from one. Pass NULL if
2586 * you want to update all.
2588 void
2589 update_favorite_tabs(struct tab *apart_from)
2591 struct tab *t;
2592 if (!updating_fl_tabs) {
2593 updating_fl_tabs = 1; /* stop infinite recursion */
2594 TAILQ_FOREACH(t, &tabs, entry)
2595 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2596 && (t != apart_from))
2597 xtp_page_fl(t, NULL);
2598 updating_fl_tabs = 0;
2602 /* show a list of favorites (bookmarks) */
2604 xtp_page_fl(struct tab *t, struct karg *args)
2606 char file[PATH_MAX];
2607 FILE *f;
2608 char *uri = NULL, *title = NULL;
2609 size_t len, lineno = 0;
2610 int i, failed = 0;
2611 char *header, *body, *tmp, *html = NULL;
2613 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2615 if (t == NULL)
2616 warn("%s: bad param", __func__);
2618 /* mark tab as favorite list */
2619 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2621 /* new session key */
2622 if (!updating_fl_tabs)
2623 generate_xtp_session_key(&fl_session_key);
2625 /* open favorites */
2626 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2627 if ((f = fopen(file, "r")) == NULL) {
2628 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2629 return (1);
2632 /* header */
2633 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2634 "<title>Favorites</title>\n"
2635 "%s"
2636 "</head>"
2637 "<h1>Favorites</h1>\n",
2638 XT_PAGE_STYLE);
2640 /* body */
2641 body = g_strdup_printf("<div align='center'><table><tr>"
2642 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2643 "<th style='width: 15%%'>Remove</th></tr>\n");
2645 for (i = 1;;) {
2646 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2647 if (feof(f) || ferror(f))
2648 break;
2649 if (len == 0) {
2650 free(title);
2651 title = NULL;
2652 continue;
2655 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2656 if (feof(f) || ferror(f)) {
2657 show_oops(t, "favorites file corrupt");
2658 failed = 1;
2659 break;
2662 tmp = body;
2663 body = g_strdup_printf("%s<tr>"
2664 "<td>%d</td>"
2665 "<td><a href='%s'>%s</a></td>"
2666 "<td style='text-align: center'>"
2667 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2668 "</tr>\n",
2669 body, i, uri, title,
2670 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2672 g_free(tmp);
2674 free(uri);
2675 uri = NULL;
2676 free(title);
2677 title = NULL;
2678 i++;
2680 fclose(f);
2682 /* if none, say so */
2683 if (i == 1) {
2684 tmp = body;
2685 body = g_strdup_printf("%s<tr>"
2686 "<td colspan='3' style='text-align: center'>"
2687 "No favorites - To add one use the 'favadd' command."
2688 "</td></tr>", body);
2689 g_free(tmp);
2692 if (uri)
2693 free(uri);
2694 if (title)
2695 free(title);
2697 /* render */
2698 if (!failed) {
2699 html = g_strdup_printf("%s%s</table></div></html>",
2700 header, body);
2701 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2704 update_favorite_tabs(t);
2706 if (header)
2707 g_free(header);
2708 if (body)
2709 g_free(body);
2710 if (html)
2711 g_free(html);
2713 return (failed);
2716 void
2717 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2718 size_t cert_count, char *title)
2720 gnutls_datum_t cinfo;
2721 char *tmp, *header, *body, *footer;
2722 int i;
2724 header = g_strdup_printf("<html><head><title>%s</title></head><body>", title);
2725 footer = g_strdup("</body></html>");
2726 body = g_strdup("");
2728 for (i = 0; i < cert_count; i++) {
2729 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2730 &cinfo))
2731 return;
2733 tmp = body;
2734 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2735 body, i, cinfo.data);
2736 gnutls_free(cinfo.data);
2737 g_free(tmp);
2740 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2741 g_free(header);
2742 g_free(body);
2743 g_free(footer);
2744 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2745 g_free(tmp);
2749 ca_cmd(struct tab *t, struct karg *args)
2751 FILE *f = NULL;
2752 int rv = 1, certs = 0, certs_read;
2753 struct stat sb;
2754 gnutls_datum dt;
2755 gnutls_x509_crt_t *c = NULL;
2756 char *certs_buf = NULL, *s;
2758 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2759 show_oops(t, "Can't open CA file: %s", strerror(errno));
2760 return (1);
2763 if (fstat(fileno(f), &sb) == -1) {
2764 show_oops(t, "Can't stat CA file: %s", strerror(errno));
2765 goto done;
2768 certs_buf = g_malloc(sb.st_size + 1);
2769 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2770 show_oops(t, "Can't read CA file: %s", strerror(errno));
2771 goto done;
2773 certs_buf[sb.st_size] = '\0';
2775 s = certs_buf;
2776 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2777 certs++;
2778 s += strlen("BEGIN CERTIFICATE");
2781 bzero(&dt, sizeof dt);
2782 dt.data = certs_buf;
2783 dt.size = sb.st_size;
2784 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2785 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2786 GNUTLS_X509_FMT_PEM, 0);
2787 if (certs_read <= 0) {
2788 show_oops(t, "No cert(s) available");
2789 goto done;
2791 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2792 done:
2793 if (c)
2794 g_free(c);
2795 if (certs_buf)
2796 g_free(certs_buf);
2797 if (f)
2798 fclose(f);
2800 return (rv);
2804 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2806 SoupURI *su = NULL;
2807 struct addrinfo hints, *res = NULL, *ai;
2808 int s = -1, on;
2809 char port[8];
2811 if (uri && !g_str_has_prefix(uri, "https://"))
2812 goto done;
2814 su = soup_uri_new(uri);
2815 if (su == NULL)
2816 goto done;
2817 if (!SOUP_URI_VALID_FOR_HTTP(su))
2818 goto done;
2820 snprintf(port, sizeof port, "%d", su->port);
2821 bzero(&hints, sizeof(struct addrinfo));
2822 hints.ai_flags = AI_CANONNAME;
2823 hints.ai_family = AF_UNSPEC;
2824 hints.ai_socktype = SOCK_STREAM;
2826 if (getaddrinfo(su->host, port, &hints, &res))
2827 goto done;
2829 for (ai = res; ai; ai = ai->ai_next) {
2830 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2831 continue;
2833 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2834 if (s < 0)
2835 goto done;
2836 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2837 sizeof(on)) == -1)
2838 goto done;
2840 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2841 goto done;
2844 if (domain)
2845 strlcpy(domain, su->host, domain_sz);
2846 done:
2847 if (su)
2848 soup_uri_free(su);
2849 if (res)
2850 freeaddrinfo(res);
2852 return (s);
2856 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2858 if (gsession)
2859 gnutls_deinit(gsession);
2860 if (xcred)
2861 gnutls_certificate_free_credentials(xcred);
2863 return (0);
2867 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2868 gnutls_certificate_credentials_t *xc)
2870 gnutls_certificate_credentials_t xcred;
2871 gnutls_session_t gsession;
2872 int rv = 1;
2874 if (gs == NULL || xc == NULL)
2875 goto done;
2877 *gs = NULL;
2878 *xc = NULL;
2880 gnutls_certificate_allocate_credentials(&xcred);
2881 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2882 GNUTLS_X509_FMT_PEM);
2883 gnutls_init(&gsession, GNUTLS_CLIENT);
2884 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2885 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2886 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2887 if ((rv = gnutls_handshake(gsession)) < 0) {
2888 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2890 gnutls_error_is_fatal(rv),
2891 gnutls_strerror_name(rv));
2892 stop_tls(gsession, xcred);
2893 goto done;
2896 gnutls_credentials_type_t cred;
2897 cred = gnutls_auth_get_type(gsession);
2898 if (cred != GNUTLS_CRD_CERTIFICATE) {
2899 stop_tls(gsession, xcred);
2900 goto done;
2903 *gs = gsession;
2904 *xc = xcred;
2905 rv = 0;
2906 done:
2907 return (rv);
2911 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2912 size_t *cert_count)
2914 unsigned int len;
2915 const gnutls_datum_t *cl;
2916 gnutls_x509_crt_t *all_certs;
2917 int i, rv = 1;
2919 if (certs == NULL || cert_count == NULL)
2920 goto done;
2921 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2922 goto done;
2923 cl = gnutls_certificate_get_peers(gsession, &len);
2924 if (len == 0)
2925 goto done;
2927 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2928 for (i = 0; i < len; i++) {
2929 gnutls_x509_crt_init(&all_certs[i]);
2930 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2931 GNUTLS_X509_FMT_PEM < 0)) {
2932 g_free(all_certs);
2933 goto done;
2937 *certs = all_certs;
2938 *cert_count = len;
2939 rv = 0;
2940 done:
2941 return (rv);
2944 void
2945 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2947 int i;
2949 for (i = 0; i < cert_count; i++)
2950 gnutls_x509_crt_deinit(certs[i]);
2951 g_free(certs);
2954 void
2955 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2956 size_t cert_count, char *domain)
2958 size_t cert_buf_sz;
2959 char cert_buf[64 * 1024], file[PATH_MAX];
2960 int i;
2961 FILE *f;
2962 GdkColor color;
2964 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2965 return;
2967 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2968 if ((f = fopen(file, "w")) == NULL) {
2969 show_oops(t, "Can't create cert file %s %s",
2970 file, strerror(errno));
2971 return;
2974 for (i = 0; i < cert_count; i++) {
2975 cert_buf_sz = sizeof cert_buf;
2976 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2977 cert_buf, &cert_buf_sz)) {
2978 show_oops(t, "gnutls_x509_crt_export failed");
2979 goto done;
2981 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2982 show_oops(t, "Can't write certs: %s", strerror(errno));
2983 goto done;
2987 /* not the best spot but oh well */
2988 gdk_color_parse("lightblue", &color);
2989 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2990 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2991 gdk_color_parse(XT_COLOR_BLACK, &color);
2992 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2993 done:
2994 fclose(f);
2998 load_compare_cert(struct tab *t, struct karg *args)
3000 const gchar *uri;
3001 char domain[8182], file[PATH_MAX];
3002 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3003 int s = -1, rv = 1, i;
3004 size_t cert_count;
3005 FILE *f = NULL;
3006 size_t cert_buf_sz;
3007 gnutls_session_t gsession;
3008 gnutls_x509_crt_t *certs;
3009 gnutls_certificate_credentials_t xcred;
3011 if (t == NULL)
3012 return (1);
3014 if ((uri = get_uri(t->wv)) == NULL)
3015 return (1);
3017 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3018 return (1);
3020 /* go ssl/tls */
3021 if (start_tls(t, s, &gsession, &xcred)) {
3022 show_oops(t, "Start TLS failed");
3023 goto done;
3026 /* get certs */
3027 if (get_connection_certs(gsession, &certs, &cert_count)) {
3028 show_oops(t, "Can't get connection certificates");
3029 goto done;
3032 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3033 if ((f = fopen(file, "r")) == NULL)
3034 goto freeit;
3036 for (i = 0; i < cert_count; i++) {
3037 cert_buf_sz = sizeof cert_buf;
3038 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3039 cert_buf, &cert_buf_sz)) {
3040 goto freeit;
3042 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3043 rv = -1; /* critical */
3044 goto freeit;
3046 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3047 rv = -1; /* critical */
3048 goto freeit;
3052 rv = 0;
3053 freeit:
3054 if (f)
3055 fclose(f);
3056 free_connection_certs(certs, cert_count);
3057 done:
3058 /* we close the socket first for speed */
3059 if (s != -1)
3060 close(s);
3061 stop_tls(gsession, xcred);
3063 return (rv);
3067 cert_cmd(struct tab *t, struct karg *args)
3069 const gchar *uri;
3070 char domain[8182];
3071 int s = -1;
3072 size_t cert_count;
3073 gnutls_session_t gsession;
3074 gnutls_x509_crt_t *certs;
3075 gnutls_certificate_credentials_t xcred;
3077 if (t == NULL)
3078 return (1);
3080 if ((uri = get_uri(t->wv)) == NULL) {
3081 show_oops(t, "Invalid URI");
3082 return (1);
3085 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3086 show_oops(t, "Invalid certidicate URI: %s", uri);
3087 return (1);
3090 /* go ssl/tls */
3091 if (start_tls(t, s, &gsession, &xcred)) {
3092 show_oops(t, "Start TLS failed");
3093 goto done;
3096 /* get certs */
3097 if (get_connection_certs(gsession, &certs, &cert_count)) {
3098 show_oops(t, "get_connection_certs failed");
3099 goto done;
3102 if (args->i & XT_SHOW)
3103 show_certs(t, certs, cert_count, "Certificate Chain");
3104 else if (args->i & XT_SAVE)
3105 save_certs(t, certs, cert_count, domain);
3107 free_connection_certs(certs, cert_count);
3108 done:
3109 /* we close the socket first for speed */
3110 if (s != -1)
3111 close(s);
3112 stop_tls(gsession, xcred);
3114 return (0);
3118 remove_cookie(int index)
3120 int i, rv = 1;
3121 GSList *cf;
3122 SoupCookie *c;
3124 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3126 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3128 for (i = 1; cf; cf = cf->next, i++) {
3129 if (i != index)
3130 continue;
3131 c = cf->data;
3132 print_cookie("remove cookie", c);
3133 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3134 rv = 0;
3135 break;
3138 soup_cookies_free(cf);
3140 return (rv);
3144 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3146 struct domain *d;
3147 char *tmp, *header, *body, *footer;
3149 /* we set this to indicate we want to manually do navaction */
3150 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3152 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3153 title, title);
3154 footer = g_strdup("</body></html>");
3155 body = g_strdup("");
3157 /* p list */
3158 if (args->i & XT_WL_PERSISTENT) {
3159 tmp = body;
3160 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3161 g_free(tmp);
3162 RB_FOREACH(d, domain_list, wl) {
3163 if (d->handy == 0)
3164 continue;
3165 tmp = body;
3166 body = g_strdup_printf("%s%s<br>", body, d->d);
3167 g_free(tmp);
3171 /* s list */
3172 if (args->i & XT_WL_SESSION) {
3173 tmp = body;
3174 body = g_strdup_printf("%s<h2>Session</h2>", body);
3175 g_free(tmp);
3176 RB_FOREACH(d, domain_list, wl) {
3177 if (d->handy == 1)
3178 continue;
3179 tmp = body;
3180 body = g_strdup_printf("%s%s<br>", body, d->d);
3181 g_free(tmp);
3185 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3186 g_free(header);
3187 g_free(body);
3188 g_free(footer);
3189 if (wl == &js_wl)
3190 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3191 else
3192 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3193 g_free(tmp);
3194 return (0);
3198 wl_save(struct tab *t, struct karg *args, int js)
3200 char file[PATH_MAX];
3201 FILE *f;
3202 char *line = NULL, *lt = NULL;
3203 size_t linelen;
3204 const gchar *uri;
3205 char *dom = NULL, *dom_save = NULL;
3206 struct karg a;
3207 struct domain *d;
3208 GSList *cf;
3209 SoupCookie *ci, *c;
3211 if (t == NULL || args == NULL)
3212 return (1);
3214 if (runtime_settings[0] == '\0')
3215 return (1);
3217 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3218 if ((f = fopen(file, "r+")) == NULL)
3219 return (1);
3221 uri = get_uri(t->wv);
3222 dom = find_domain(uri, 1);
3223 if (uri == NULL || dom == NULL) {
3224 show_oops(t, "Can't add domain to %s white list",
3225 js ? "JavaScript" : "cookie");
3226 goto done;
3229 if (args->i & XT_WL_TOPLEVEL) {
3230 /* save domain */
3231 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3232 show_oops(t, "invalid domain: %s", dom);
3233 goto done;
3235 } else if (args->i & XT_WL_FQDN) {
3236 /* save fqdn */
3237 dom_save = dom;
3238 } else
3239 goto done;
3241 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3243 while (!feof(f)) {
3244 line = fparseln(f, &linelen, NULL, NULL, 0);
3245 if (line == NULL)
3246 continue;
3247 if (!strcmp(line, lt))
3248 goto done;
3249 free(line);
3250 line = NULL;
3253 fprintf(f, "%s\n", lt);
3255 a.i = XT_WL_ENABLE;
3256 a.i |= args->i;
3257 if (js) {
3258 d = wl_find(dom_save, &js_wl);
3259 if (!d) {
3260 settings_add("js_wl", dom_save);
3261 d = wl_find(dom_save, &js_wl);
3263 toggle_js(t, &a);
3264 } else {
3265 d = wl_find(dom_save, &c_wl);
3266 if (!d) {
3267 settings_add("cookie_wl", dom_save);
3268 d = wl_find(dom_save, &c_wl);
3270 toggle_cwl(t, &a);
3272 /* find and add to persistent jar */
3273 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3274 for (;cf; cf = cf->next) {
3275 ci = cf->data;
3276 if (!strcmp(dom_save, ci->domain) ||
3277 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3278 c = soup_cookie_copy(ci);
3279 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3282 soup_cookies_free(cf);
3284 if (d)
3285 d->handy = 1;
3287 done:
3288 if (line)
3289 free(line);
3290 if (dom)
3291 g_free(dom);
3292 if (lt)
3293 g_free(lt);
3294 fclose(f);
3296 return (0);
3300 js_show_wl(struct tab *t, struct karg *args)
3302 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3303 wl_show(t, args, "JavaScript White List", &js_wl);
3305 return (0);
3309 cookie_show_wl(struct tab *t, struct karg *args)
3311 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3312 wl_show(t, args, "Cookie White List", &c_wl);
3314 return (0);
3318 cookie_cmd(struct tab *t, struct karg *args)
3320 if (args->i & XT_SHOW)
3321 wl_show(t, args, "Cookie White List", &c_wl);
3322 else if (args->i & XT_WL_TOGGLE)
3323 toggle_cwl(t, args);
3324 else if (args->i & XT_SAVE)
3325 wl_save(t, args, 0);
3326 else if (args->i & XT_DELETE)
3327 show_oops(t, "'cookie delete' currently unimplemented");
3329 return (0);
3333 js_cmd(struct tab *t, struct karg *args)
3335 if (args->i & XT_SHOW)
3336 wl_show(t, args, "JavaScript White List", &js_wl);
3337 else if (args->i & XT_SAVE)
3338 wl_save(t, args, 1);
3339 else if (args->i & XT_WL_TOGGLE)
3340 toggle_js(t, args);
3341 else if (args->i & XT_DELETE)
3342 show_oops(t, "'js delete' currently unimplemented");
3344 return (0);
3348 add_favorite(struct tab *t, struct karg *args)
3350 char file[PATH_MAX];
3351 FILE *f;
3352 char *line = NULL;
3353 size_t urilen, linelen;
3354 const gchar *uri, *title;
3356 if (t == NULL)
3357 return (1);
3359 /* don't allow adding of xtp pages to favorites */
3360 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3361 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3362 return (1);
3365 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3366 if ((f = fopen(file, "r+")) == NULL) {
3367 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3368 return (1);
3371 title = webkit_web_view_get_title(t->wv);
3372 uri = get_uri(t->wv);
3374 if (title == NULL)
3375 title = uri;
3377 if (title == NULL || uri == NULL) {
3378 show_oops(t, "can't add page to favorites");
3379 goto done;
3382 urilen = strlen(uri);
3384 for (;;) {
3385 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3386 if (feof(f) || ferror(f))
3387 break;
3389 if (linelen == urilen && !strcmp(line, uri))
3390 goto done;
3392 free(line);
3393 line = NULL;
3396 fprintf(f, "\n%s\n%s", title, uri);
3397 done:
3398 if (line)
3399 free(line);
3400 fclose(f);
3402 update_favorite_tabs(NULL);
3404 return (0);
3408 navaction(struct tab *t, struct karg *args)
3410 WebKitWebHistoryItem *item;
3412 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3413 t->tab_id, args->i);
3415 if (t->item) {
3416 if (args->i == XT_NAV_BACK)
3417 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3418 else
3419 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3420 if (item == NULL)
3421 return (XT_CB_PASSTHROUGH);
3422 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3423 t->item = NULL;
3424 return (XT_CB_PASSTHROUGH);
3427 switch (args->i) {
3428 case XT_NAV_BACK:
3429 webkit_web_view_go_back(t->wv);
3430 break;
3431 case XT_NAV_FORWARD:
3432 webkit_web_view_go_forward(t->wv);
3433 break;
3434 case XT_NAV_RELOAD:
3435 webkit_web_view_reload(t->wv);
3436 break;
3437 case XT_NAV_RELOAD_CACHE:
3438 webkit_web_view_reload_bypass_cache(t->wv);
3439 break;
3441 return (XT_CB_PASSTHROUGH);
3445 move(struct tab *t, struct karg *args)
3447 GtkAdjustment *adjust;
3448 double pi, si, pos, ps, upper, lower, max;
3450 switch (args->i) {
3451 case XT_MOVE_DOWN:
3452 case XT_MOVE_UP:
3453 case XT_MOVE_BOTTOM:
3454 case XT_MOVE_TOP:
3455 case XT_MOVE_PAGEDOWN:
3456 case XT_MOVE_PAGEUP:
3457 case XT_MOVE_HALFDOWN:
3458 case XT_MOVE_HALFUP:
3459 adjust = t->adjust_v;
3460 break;
3461 default:
3462 adjust = t->adjust_h;
3463 break;
3466 pos = gtk_adjustment_get_value(adjust);
3467 ps = gtk_adjustment_get_page_size(adjust);
3468 upper = gtk_adjustment_get_upper(adjust);
3469 lower = gtk_adjustment_get_lower(adjust);
3470 si = gtk_adjustment_get_step_increment(adjust);
3471 pi = gtk_adjustment_get_page_increment(adjust);
3472 max = upper - ps;
3474 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3475 "max %f si %f pi %f\n",
3476 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3477 pos, ps, upper, lower, max, si, pi);
3479 switch (args->i) {
3480 case XT_MOVE_DOWN:
3481 case XT_MOVE_RIGHT:
3482 pos += si;
3483 gtk_adjustment_set_value(adjust, MIN(pos, max));
3484 break;
3485 case XT_MOVE_UP:
3486 case XT_MOVE_LEFT:
3487 pos -= si;
3488 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3489 break;
3490 case XT_MOVE_BOTTOM:
3491 case XT_MOVE_FARRIGHT:
3492 gtk_adjustment_set_value(adjust, max);
3493 break;
3494 case XT_MOVE_TOP:
3495 case XT_MOVE_FARLEFT:
3496 gtk_adjustment_set_value(adjust, lower);
3497 break;
3498 case XT_MOVE_PAGEDOWN:
3499 pos += pi;
3500 gtk_adjustment_set_value(adjust, MIN(pos, max));
3501 break;
3502 case XT_MOVE_PAGEUP:
3503 pos -= pi;
3504 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3505 break;
3506 case XT_MOVE_HALFDOWN:
3507 pos += pi / 2;
3508 gtk_adjustment_set_value(adjust, MIN(pos, max));
3509 break;
3510 case XT_MOVE_HALFUP:
3511 pos -= pi / 2;
3512 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3513 break;
3514 default:
3515 return (XT_CB_PASSTHROUGH);
3518 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3520 return (XT_CB_HANDLED);
3523 void
3524 url_set_visibility(void)
3526 struct tab *t;
3528 TAILQ_FOREACH(t, &tabs, entry) {
3529 if (show_url == 0) {
3530 gtk_widget_hide(t->toolbar);
3531 focus_webview(t);
3532 } else
3533 gtk_widget_show(t->toolbar);
3537 void
3538 notebook_tab_set_visibility(GtkNotebook *notebook)
3540 if (show_tabs == 0)
3541 gtk_notebook_set_show_tabs(notebook, FALSE);
3542 else
3543 gtk_notebook_set_show_tabs(notebook, TRUE);
3546 void
3547 statusbar_set_visibility(void)
3549 struct tab *t;
3551 TAILQ_FOREACH(t, &tabs, entry) {
3552 if (show_statusbar == 0) {
3553 gtk_widget_hide(t->statusbar);
3554 focus_webview(t);
3555 } else
3556 gtk_widget_show(t->statusbar);
3560 void
3561 url_set(struct tab *t, int enable_url_entry)
3563 GdkPixbuf *pixbuf;
3564 int progress;
3566 show_url = enable_url_entry;
3568 if (enable_url_entry) {
3569 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3570 GTK_ENTRY_ICON_PRIMARY, NULL);
3571 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3572 } else {
3573 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3574 GTK_ENTRY_ICON_PRIMARY);
3575 progress =
3576 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3577 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3578 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3579 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3580 progress);
3585 fullscreen(struct tab *t, struct karg *args)
3587 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3589 if (t == NULL)
3590 return (XT_CB_PASSTHROUGH);
3592 if (show_url == 0) {
3593 url_set(t, 1);
3594 show_tabs = 1;
3595 } else {
3596 url_set(t, 0);
3597 show_tabs = 0;
3600 url_set_visibility();
3601 notebook_tab_set_visibility(notebook);
3603 return (XT_CB_HANDLED);
3607 statusaction(struct tab *t, struct karg *args)
3609 int rv = XT_CB_HANDLED;
3611 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3613 if (t == NULL)
3614 return (XT_CB_PASSTHROUGH);
3616 switch (args->i) {
3617 case XT_STATUSBAR_SHOW:
3618 if (show_statusbar == 0) {
3619 show_statusbar = 1;
3620 statusbar_set_visibility();
3622 break;
3623 case XT_STATUSBAR_HIDE:
3624 if (show_statusbar == 1) {
3625 show_statusbar = 0;
3626 statusbar_set_visibility();
3628 break;
3630 return (rv);
3634 urlaction(struct tab *t, struct karg *args)
3636 int rv = XT_CB_HANDLED;
3638 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3640 if (t == NULL)
3641 return (XT_CB_PASSTHROUGH);
3643 switch (args->i) {
3644 case XT_URL_SHOW:
3645 if (show_url == 0) {
3646 url_set(t, 1);
3647 url_set_visibility();
3649 break;
3650 case XT_URL_HIDE:
3651 if (show_url == 1) {
3652 url_set(t, 0);
3653 url_set_visibility();
3655 break;
3657 return (rv);
3661 tabaction(struct tab *t, struct karg *args)
3663 int rv = XT_CB_HANDLED;
3664 char *url = args->s;
3665 struct undo *u;
3667 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3669 if (t == NULL)
3670 return (XT_CB_PASSTHROUGH);
3672 switch (args->i) {
3673 case XT_TAB_NEW:
3674 if (strlen(url) > 0)
3675 create_new_tab(url, NULL, 1);
3676 else
3677 create_new_tab(NULL, NULL, 1);
3678 break;
3679 case XT_TAB_DELETE:
3680 delete_tab(t);
3681 break;
3682 case XT_TAB_DELQUIT:
3683 if (gtk_notebook_get_n_pages(notebook) > 1)
3684 delete_tab(t);
3685 else
3686 quit(t, args);
3687 break;
3688 case XT_TAB_OPEN:
3689 if (strlen(url) > 0)
3691 else {
3692 rv = XT_CB_PASSTHROUGH;
3693 goto done;
3695 load_uri(t, url);
3696 break;
3697 case XT_TAB_SHOW:
3698 if (show_tabs == 0) {
3699 show_tabs = 1;
3700 notebook_tab_set_visibility(notebook);
3702 break;
3703 case XT_TAB_HIDE:
3704 if (show_tabs == 1) {
3705 show_tabs = 0;
3706 notebook_tab_set_visibility(notebook);
3708 break;
3709 case XT_TAB_UNDO_CLOSE:
3710 if (undo_count == 0) {
3711 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3712 goto done;
3713 } else {
3714 undo_count--;
3715 u = TAILQ_FIRST(&undos);
3716 create_new_tab(u->uri, u, 1);
3718 TAILQ_REMOVE(&undos, u, entry);
3719 g_free(u->uri);
3720 /* u->history is freed in create_new_tab() */
3721 g_free(u);
3723 break;
3724 default:
3725 rv = XT_CB_PASSTHROUGH;
3726 goto done;
3729 done:
3730 if (args->s) {
3731 g_free(args->s);
3732 args->s = NULL;
3735 return (rv);
3739 resizetab(struct tab *t, struct karg *args)
3741 if (t == NULL || args == NULL) {
3742 show_oops_s("resizetab invalid parameters");
3743 return (XT_CB_PASSTHROUGH);
3746 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3747 t->tab_id, args->i);
3749 adjustfont_webkit(t, args->i);
3751 return (XT_CB_HANDLED);
3755 movetab(struct tab *t, struct karg *args)
3757 struct tab *tt;
3758 int x;
3760 if (t == NULL || args == NULL) {
3761 show_oops_s("movetab invalid parameters");
3762 return (XT_CB_PASSTHROUGH);
3765 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3766 t->tab_id, args->i);
3768 if (args->i == XT_TAB_INVALID)
3769 return (XT_CB_PASSTHROUGH);
3771 if (args->i < XT_TAB_INVALID) {
3772 /* next or previous tab */
3773 if (TAILQ_EMPTY(&tabs))
3774 return (XT_CB_PASSTHROUGH);
3776 switch (args->i) {
3777 case XT_TAB_NEXT:
3778 if (strlen(args->s) == 0) {
3779 /* if at the last page, loop around to the first */
3780 if (gtk_notebook_get_current_page(notebook) ==
3781 gtk_notebook_get_n_pages(notebook) - 1)
3782 gtk_notebook_set_current_page(notebook, 0);
3783 else
3784 gtk_notebook_next_page(notebook);
3785 } else {
3786 x = atoi(args->s) - 1;
3787 if (x < 0)
3788 return (XT_CB_PASSTHROUGH);
3790 if (t->tab_id == x) {
3791 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3792 return (XT_CB_HANDLED);
3794 TAILQ_FOREACH(tt, &tabs, entry) {
3795 if (tt->tab_id == x) {
3796 gtk_notebook_set_current_page(notebook, x);
3797 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3798 break;
3803 break;
3804 case XT_TAB_PREV:
3805 /* if at the first page, loop around to the last */
3806 if (gtk_notebook_current_page(notebook) == 0)
3807 gtk_notebook_set_current_page(notebook,
3808 gtk_notebook_get_n_pages(notebook) - 1);
3809 else
3810 gtk_notebook_prev_page(notebook);
3811 break;
3812 case XT_TAB_FIRST:
3813 gtk_notebook_set_current_page(notebook, 0);
3814 break;
3815 case XT_TAB_LAST:
3816 gtk_notebook_set_current_page(notebook, -1);
3817 break;
3818 default:
3819 return (XT_CB_PASSTHROUGH);
3822 return (XT_CB_HANDLED);
3825 return (XT_CB_HANDLED);
3829 command(struct tab *t, struct karg *args)
3831 char *s = NULL, *ss = NULL;
3832 GdkColor color;
3833 const gchar *uri;
3835 if (t == NULL || args == NULL) {
3836 show_oops_s("command invalid parameters");
3837 return (XT_CB_PASSTHROUGH);
3840 switch (args->i) {
3841 case '/':
3842 s = "/";
3843 break;
3844 case '?':
3845 s = "?";
3846 break;
3847 case ':':
3848 s = ":";
3849 break;
3850 case XT_CMD_OPEN:
3851 s = ":open ";
3852 break;
3853 case XT_CMD_TABNEW:
3854 s = ":tabnew ";
3855 break;
3856 case XT_CMD_OPEN_CURRENT:
3857 s = ":open ";
3858 /* FALL THROUGH */
3859 case XT_CMD_TABNEW_CURRENT:
3860 if (!s) /* FALL THROUGH? */
3861 s = ":tabnew ";
3862 if ((uri = get_uri(t->wv)) != NULL) {
3863 ss = g_strdup_printf("%s%s", s, uri);
3864 s = ss;
3866 break;
3867 default:
3868 show_oops(t, "command: invalid opcode %d", args->i);
3869 return (XT_CB_PASSTHROUGH);
3872 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3874 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3875 gdk_color_parse(XT_COLOR_WHITE, &color);
3876 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3877 show_cmd(t);
3878 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3879 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3881 if (ss)
3882 g_free(ss);
3884 return (XT_CB_HANDLED);
3888 * Return a new string with a download row (in html)
3889 * appended. Old string is freed.
3891 char *
3892 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3895 WebKitDownloadStatus stat;
3896 char *status_html = NULL, *cmd_html = NULL, *new_html;
3897 gdouble progress;
3898 char cur_sz[FMT_SCALED_STRSIZE];
3899 char tot_sz[FMT_SCALED_STRSIZE];
3900 char *xtp_prefix;
3902 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3904 /* All actions wil take this form:
3905 * xxxt://class/seskey
3907 xtp_prefix = g_strdup_printf("%s%d/%s/",
3908 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3910 stat = webkit_download_get_status(dl->download);
3912 switch (stat) {
3913 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3914 status_html = g_strdup_printf("Finished");
3915 cmd_html = g_strdup_printf(
3916 "<a href='%s%d/%d'>Remove</a>",
3917 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3918 break;
3919 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3920 /* gather size info */
3921 progress = 100 * webkit_download_get_progress(dl->download);
3923 fmt_scaled(
3924 webkit_download_get_current_size(dl->download), cur_sz);
3925 fmt_scaled(
3926 webkit_download_get_total_size(dl->download), tot_sz);
3928 status_html = g_strdup_printf(
3929 "<div style='width: 100%%' align='center'>"
3930 "<div class='progress-outer'>"
3931 "<div class='progress-inner' style='width: %.2f%%'>"
3932 "</div></div></div>"
3933 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3934 progress, cur_sz, tot_sz, progress);
3936 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3937 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3939 break;
3940 /* LLL */
3941 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3942 status_html = g_strdup_printf("Cancelled");
3943 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3944 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3945 break;
3946 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3947 status_html = g_strdup_printf("Error!");
3948 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3949 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3950 break;
3951 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3952 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3953 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3954 status_html = g_strdup_printf("Starting");
3955 break;
3956 default:
3957 show_oops(t, "%s: unknown download status", __func__);
3960 new_html = g_strdup_printf(
3961 "%s\n<tr><td>%s</td><td>%s</td>"
3962 "<td style='text-align:center'>%s</td></tr>\n",
3963 html, basename(webkit_download_get_destination_uri(dl->download)),
3964 status_html, cmd_html);
3965 g_free(html);
3967 if (status_html)
3968 g_free(status_html);
3970 if (cmd_html)
3971 g_free(cmd_html);
3973 g_free(xtp_prefix);
3975 return new_html;
3979 * update all download tabs apart from one. Pass NULL if
3980 * you want to update all.
3982 void
3983 update_download_tabs(struct tab *apart_from)
3985 struct tab *t;
3986 if (!updating_dl_tabs) {
3987 updating_dl_tabs = 1; /* stop infinite recursion */
3988 TAILQ_FOREACH(t, &tabs, entry)
3989 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3990 && (t != apart_from))
3991 xtp_page_dl(t, NULL);
3992 updating_dl_tabs = 0;
3997 * update all cookie tabs apart from one. Pass NULL if
3998 * you want to update all.
4000 void
4001 update_cookie_tabs(struct tab *apart_from)
4003 struct tab *t;
4004 if (!updating_cl_tabs) {
4005 updating_cl_tabs = 1; /* stop infinite recursion */
4006 TAILQ_FOREACH(t, &tabs, entry)
4007 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4008 && (t != apart_from))
4009 xtp_page_cl(t, NULL);
4010 updating_cl_tabs = 0;
4015 * update all history tabs apart from one. Pass NULL if
4016 * you want to update all.
4018 void
4019 update_history_tabs(struct tab *apart_from)
4021 struct tab *t;
4023 if (!updating_hl_tabs) {
4024 updating_hl_tabs = 1; /* stop infinite recursion */
4025 TAILQ_FOREACH(t, &tabs, entry)
4026 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4027 && (t != apart_from))
4028 xtp_page_hl(t, NULL);
4029 updating_hl_tabs = 0;
4033 /* cookie management XTP page */
4035 xtp_page_cl(struct tab *t, struct karg *args)
4037 char *header, *body, *footer, *page, *tmp;
4038 int i = 1; /* all ids start 1 */
4039 GSList *sc, *pc, *pc_start;
4040 SoupCookie *c;
4041 char *type, *table_headers;
4042 char *last_domain = strdup("");
4044 DNPRINTF(XT_D_CMD, "%s", __func__);
4046 if (t == NULL) {
4047 show_oops_s("%s invalid parameters", __func__);
4048 return (1);
4050 /* mark this tab as cookie jar */
4051 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4053 /* Generate a new session key */
4054 if (!updating_cl_tabs)
4055 generate_xtp_session_key(&cl_session_key);
4057 /* header */
4058 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4059 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4060 "</head><body><h1>Cookie Jar</h1>\n");
4062 /* table headers */
4063 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4064 "<th>Type</th>"
4065 "<th>Name</th>"
4066 "<th>Value</th>"
4067 "<th>Path</th>"
4068 "<th>Expires</th>"
4069 "<th>Secure</th>"
4070 "<th>HTTP<br />only</th>"
4071 "<th>Rm</th></tr>\n");
4073 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4074 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4075 pc_start = pc;
4077 body = NULL;
4078 for (; sc; sc = sc->next) {
4079 c = sc->data;
4081 if (strcmp(last_domain, c->domain) != 0) {
4082 /* new domain */
4083 free(last_domain);
4084 last_domain = strdup(c->domain);
4086 if (body != NULL) {
4087 tmp = body;
4088 body = g_strdup_printf("%s</table></div>"
4089 "<h2>%s</h2>%s\n",
4090 body, c->domain, table_headers);
4091 g_free(tmp);
4092 } else {
4093 /* first domain */
4094 body = g_strdup_printf("<h2>%s</h2>%s\n",
4095 c->domain, table_headers);
4099 type = "Session";
4100 for (pc = pc_start; pc; pc = pc->next)
4101 if (soup_cookie_equal(pc->data, c)) {
4102 type = "Session + Persistent";
4103 break;
4106 tmp = body;
4107 body = g_strdup_printf(
4108 "%s\n<tr>"
4109 "<td style='width: text-align: center'>%s</td>"
4110 "<td style='width: 1px'>%s</td>"
4111 "<td style='width=70%%;overflow: visible'>"
4112 " <textarea rows='4'>%s</textarea>"
4113 "</td>"
4114 "<td>%s</td>"
4115 "<td>%s</td>"
4116 "<td style='width: 1px; text-align: center'>%d</td>"
4117 "<td style='width: 1px; text-align: center'>%d</td>"
4118 "<td style='width: 1px; text-align: center'>"
4119 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4120 body,
4121 type,
4122 c->name,
4123 c->value,
4124 c->path,
4125 c->expires ?
4126 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4127 c->secure,
4128 c->http_only,
4130 XT_XTP_STR,
4131 XT_XTP_CL,
4132 cl_session_key,
4133 XT_XTP_CL_REMOVE,
4137 g_free(tmp);
4138 i++;
4141 soup_cookies_free(sc);
4142 soup_cookies_free(pc);
4144 /* small message if there are none */
4145 if (i == 1) {
4146 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4147 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4150 /* footer */
4151 footer = g_strdup_printf("</table></div></body></html>");
4153 page = g_strdup_printf("%s%s%s", header, body, footer);
4155 g_free(header);
4156 g_free(body);
4157 g_free(footer);
4158 g_free(table_headers);
4159 g_free(last_domain);
4161 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4162 update_cookie_tabs(t);
4164 g_free(page);
4166 return (0);
4170 xtp_page_hl(struct tab *t, struct karg *args)
4172 char *header, *body, *footer, *page, *tmp;
4173 struct history *h;
4174 int i = 1; /* all ids start 1 */
4176 DNPRINTF(XT_D_CMD, "%s", __func__);
4178 if (t == NULL) {
4179 show_oops_s("%s invalid parameters", __func__);
4180 return (1);
4183 /* mark this tab as history manager */
4184 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4186 /* Generate a new session key */
4187 if (!updating_hl_tabs)
4188 generate_xtp_session_key(&hl_session_key);
4190 /* header */
4191 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4192 "<title>History</title>\n"
4193 "%s"
4194 "</head>"
4195 "<h1>History</h1>\n",
4196 XT_PAGE_STYLE);
4198 /* body */
4199 body = g_strdup_printf("<div align='center'><table><tr>"
4200 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4202 RB_FOREACH_REVERSE(h, history_list, &hl) {
4203 tmp = body;
4204 body = g_strdup_printf(
4205 "%s\n<tr>"
4206 "<td><a href='%s'>%s</a></td>"
4207 "<td>%s</td>"
4208 "<td style='text-align: center'>"
4209 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4210 body, h->uri, h->uri, h->title,
4211 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4212 XT_XTP_HL_REMOVE, i);
4214 g_free(tmp);
4215 i++;
4218 /* small message if there are none */
4219 if (i == 1) {
4220 tmp = body;
4221 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4222 "colspan='3'>No History</td></tr>\n", body);
4223 g_free(tmp);
4226 /* footer */
4227 footer = g_strdup_printf("</table></div></body></html>");
4229 page = g_strdup_printf("%s%s%s", header, body, footer);
4232 * update all history manager tabs as the xtp session
4233 * key has now changed. No need to update the current tab.
4234 * Already did that above.
4236 update_history_tabs(t);
4238 g_free(header);
4239 g_free(body);
4240 g_free(footer);
4242 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4243 g_free(page);
4245 return (0);
4249 * Generate a web page detailing the status of any downloads
4252 xtp_page_dl(struct tab *t, struct karg *args)
4254 struct download *dl;
4255 char *header, *body, *footer, *page, *tmp;
4256 char *ref;
4257 int n_dl = 1;
4259 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4261 if (t == NULL) {
4262 show_oops_s("%s invalid parameters", __func__);
4263 return (1);
4265 /* mark as a download manager tab */
4266 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4269 * Generate a new session key for next page instance.
4270 * This only happens for the top level call to xtp_page_dl()
4271 * in which case updating_dl_tabs is 0.
4273 if (!updating_dl_tabs)
4274 generate_xtp_session_key(&dl_session_key);
4276 /* header - with refresh so as to update */
4277 if (refresh_interval >= 1)
4278 ref = g_strdup_printf(
4279 "<meta http-equiv='refresh' content='%u"
4280 ";url=%s%d/%s/%d' />\n",
4281 refresh_interval,
4282 XT_XTP_STR,
4283 XT_XTP_DL,
4284 dl_session_key,
4285 XT_XTP_DL_LIST);
4286 else
4287 ref = g_strdup("");
4290 header = g_strdup_printf(
4291 "%s\n<head>"
4292 "<title>Downloads</title>\n%s%s</head>\n",
4293 XT_DOCTYPE XT_HTML_TAG,
4294 ref,
4295 XT_PAGE_STYLE);
4297 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4298 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4299 "</p><table><tr><th style='width: 60%%'>"
4300 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4301 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4303 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4304 body = xtp_page_dl_row(t, body, dl);
4305 n_dl++;
4308 /* message if no downloads in list */
4309 if (n_dl == 1) {
4310 tmp = body;
4311 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4312 " style='text-align: center'>"
4313 "No downloads</td></tr>\n", body);
4314 g_free(tmp);
4317 /* footer */
4318 footer = g_strdup_printf("</table></div></body></html>");
4320 page = g_strdup_printf("%s%s%s", header, body, footer);
4324 * update all download manager tabs as the xtp session
4325 * key has now changed. No need to update the current tab.
4326 * Already did that above.
4328 update_download_tabs(t);
4330 g_free(ref);
4331 g_free(header);
4332 g_free(body);
4333 g_free(footer);
4335 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4336 g_free(page);
4338 return (0);
4342 search(struct tab *t, struct karg *args)
4344 gboolean d;
4346 if (t == NULL || args == NULL) {
4347 show_oops_s("search invalid parameters");
4348 return (1);
4350 if (t->search_text == NULL) {
4351 if (global_search == NULL)
4352 return (XT_CB_PASSTHROUGH);
4353 else {
4354 t->search_text = g_strdup(global_search);
4355 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4356 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4360 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4361 t->tab_id, args->i, t->search_forward, t->search_text);
4363 switch (args->i) {
4364 case XT_SEARCH_NEXT:
4365 d = t->search_forward;
4366 break;
4367 case XT_SEARCH_PREV:
4368 d = !t->search_forward;
4369 break;
4370 default:
4371 return (XT_CB_PASSTHROUGH);
4374 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4376 return (XT_CB_HANDLED);
4379 struct settings_args {
4380 char **body;
4381 int i;
4384 void
4385 print_setting(struct settings *s, char *val, void *cb_args)
4387 char *tmp, *color;
4388 struct settings_args *sa = cb_args;
4390 if (sa == NULL)
4391 return;
4393 if (s->flags & XT_SF_RUNTIME)
4394 color = "#22cc22";
4395 else
4396 color = "#cccccc";
4398 tmp = *sa->body;
4399 *sa->body = g_strdup_printf(
4400 "%s\n<tr>"
4401 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4402 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4403 *sa->body,
4404 color,
4405 s->name,
4406 color,
4409 g_free(tmp);
4410 sa->i++;
4414 set(struct tab *t, struct karg *args)
4416 char *header, *body, *footer, *page, *tmp;
4417 int i = 1;
4418 struct settings_args sa;
4420 bzero(&sa, sizeof sa);
4421 sa.body = &body;
4423 /* header */
4424 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4425 "\n<head><title>Settings</title>\n"
4426 "</head><body><h1>Settings</h1>\n");
4428 /* body */
4429 body = g_strdup_printf("<div align='center'><table><tr>"
4430 "<th align='left'>Setting</th>"
4431 "<th align='left'>Value</th></tr>\n");
4433 settings_walk(print_setting, &sa);
4434 i = sa.i;
4436 /* small message if there are none */
4437 if (i == 1) {
4438 tmp = body;
4439 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4440 "colspan='2'>No settings</td></tr>\n", body);
4441 g_free(tmp);
4444 /* footer */
4445 footer = g_strdup_printf("</table></div></body></html>");
4447 page = g_strdup_printf("%s%s%s", header, body, footer);
4449 g_free(header);
4450 g_free(body);
4451 g_free(footer);
4453 load_webkit_string(t, page, XT_URI_ABOUT_SET);
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 { "tabclose", CTRL, 1, GDK_w },
4716 { "tabundoclose", 0, 0, GDK_U },
4717 { "tabnext 1", CTRL, 0, GDK_1 },
4718 { "tabnext 2", CTRL, 0, GDK_2 },
4719 { "tabnext 3", CTRL, 0, GDK_3 },
4720 { "tabnext 4", CTRL, 0, GDK_4 },
4721 { "tabnext 5", CTRL, 0, GDK_5 },
4722 { "tabnext 6", CTRL, 0, GDK_6 },
4723 { "tabnext 7", CTRL, 0, GDK_7 },
4724 { "tabnext 8", CTRL, 0, GDK_8 },
4725 { "tabnext 9", CTRL, 0, GDK_9 },
4726 { "tabnext 10", CTRL, 0, GDK_0 },
4727 { "tabfirst", CTRL, 0, GDK_less },
4728 { "tablast", CTRL, 0, GDK_greater },
4729 { "tabprevious", CTRL, 0, GDK_Left },
4730 { "tabnext", CTRL, 0, GDK_Right },
4731 { "focusout", CTRL, 0, GDK_minus },
4732 { "focusin", CTRL, 0, GDK_plus },
4733 { "focusin", CTRL, 0, GDK_equal },
4735 /* command aliases (handy when -S flag is used) */
4736 { "promptopen", 0, 0, GDK_F9 },
4737 { "promptopencurrent", 0, 0, GDK_F10 },
4738 { "prompttabnew", 0, 0, GDK_F11 },
4739 { "prompttabnewcurrent",0, 0, GDK_F12 },
4741 TAILQ_HEAD(keybinding_list, key_binding);
4743 void
4744 walk_kb(struct settings *s,
4745 void (*cb)(struct settings *, char *, void *), void *cb_args)
4747 struct key_binding *k;
4748 char str[1024];
4750 if (s == NULL || cb == NULL) {
4751 show_oops_s("walk_kb invalid parameters");
4752 return;
4755 TAILQ_FOREACH(k, &kbl, entry) {
4756 if (k->cmd == NULL)
4757 continue;
4758 str[0] = '\0';
4760 /* sanity */
4761 if (gdk_keyval_name(k->key) == NULL)
4762 continue;
4764 strlcat(str, k->cmd, sizeof str);
4765 strlcat(str, ",", sizeof str);
4767 if (k->mask & GDK_SHIFT_MASK)
4768 strlcat(str, "S-", sizeof str);
4769 if (k->mask & GDK_CONTROL_MASK)
4770 strlcat(str, "C-", sizeof str);
4771 if (k->mask & GDK_MOD1_MASK)
4772 strlcat(str, "M1-", sizeof str);
4773 if (k->mask & GDK_MOD2_MASK)
4774 strlcat(str, "M2-", sizeof str);
4775 if (k->mask & GDK_MOD3_MASK)
4776 strlcat(str, "M3-", sizeof str);
4777 if (k->mask & GDK_MOD4_MASK)
4778 strlcat(str, "M4-", sizeof str);
4779 if (k->mask & GDK_MOD5_MASK)
4780 strlcat(str, "M5-", sizeof str);
4782 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4783 cb(s, str, cb_args);
4787 void
4788 init_keybindings(void)
4790 int i;
4791 struct key_binding *k;
4793 for (i = 0; i < LENGTH(keys); i++) {
4794 k = g_malloc0(sizeof *k);
4795 k->cmd = keys[i].cmd;
4796 k->mask = keys[i].mask;
4797 k->use_in_entry = keys[i].use_in_entry;
4798 k->key = keys[i].key;
4799 TAILQ_INSERT_HEAD(&kbl, k, entry);
4801 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4802 k->cmd ? k->cmd : "unnamed key");
4806 void
4807 keybinding_clearall(void)
4809 struct key_binding *k, *next;
4811 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4812 next = TAILQ_NEXT(k, entry);
4813 if (k->cmd == NULL)
4814 continue;
4816 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4817 k->cmd ? k->cmd : "unnamed key");
4818 TAILQ_REMOVE(&kbl, k, entry);
4819 g_free(k);
4824 keybinding_add(char *cmd, char *key, int use_in_entry)
4826 struct key_binding *k;
4827 guint keyval, mask = 0;
4828 int i;
4830 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4832 /* Keys which are to be used in entry have been prefixed with an
4833 * exclamation mark. */
4834 if (use_in_entry)
4835 key++;
4837 /* find modifier keys */
4838 if (strstr(key, "S-"))
4839 mask |= GDK_SHIFT_MASK;
4840 if (strstr(key, "C-"))
4841 mask |= GDK_CONTROL_MASK;
4842 if (strstr(key, "M1-"))
4843 mask |= GDK_MOD1_MASK;
4844 if (strstr(key, "M2-"))
4845 mask |= GDK_MOD2_MASK;
4846 if (strstr(key, "M3-"))
4847 mask |= GDK_MOD3_MASK;
4848 if (strstr(key, "M4-"))
4849 mask |= GDK_MOD4_MASK;
4850 if (strstr(key, "M5-"))
4851 mask |= GDK_MOD5_MASK;
4853 /* find keyname */
4854 for (i = strlen(key) - 1; i > 0; i--)
4855 if (key[i] == '-')
4856 key = &key[i + 1];
4858 /* validate keyname */
4859 keyval = gdk_keyval_from_name(key);
4860 if (keyval == GDK_VoidSymbol) {
4861 warnx("invalid keybinding name %s", key);
4862 return (1);
4864 /* must run this test too, gtk+ doesn't handle 10 for example */
4865 if (gdk_keyval_name(keyval) == NULL) {
4866 warnx("invalid keybinding name %s", key);
4867 return (1);
4870 /* Remove eventual dupes. */
4871 TAILQ_FOREACH(k, &kbl, entry)
4872 if (k->key == keyval && k->mask == mask) {
4873 TAILQ_REMOVE(&kbl, k, entry);
4874 g_free(k);
4875 break;
4878 /* add keyname */
4879 k = g_malloc0(sizeof *k);
4880 k->cmd = g_strdup(cmd);
4881 k->mask = mask;
4882 k->use_in_entry = use_in_entry;
4883 k->key = keyval;
4885 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4886 k->cmd,
4887 k->mask,
4888 k->use_in_entry,
4889 k->key);
4890 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4891 k->cmd, gdk_keyval_name(keyval));
4893 TAILQ_INSERT_HEAD(&kbl, k, entry);
4895 return (0);
4899 add_kb(struct settings *s, char *entry)
4901 char *kb, *key;
4903 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4905 /* clearall is special */
4906 if (!strcmp(entry, "clearall")) {
4907 keybinding_clearall();
4908 return (0);
4911 kb = strstr(entry, ",");
4912 if (kb == NULL)
4913 return (1);
4914 *kb = '\0';
4915 key = kb + 1;
4917 return (keybinding_add(entry, key, key[0] == '!'));
4920 struct cmd {
4921 char *cmd;
4922 int level;
4923 int (*func)(struct tab *, struct karg *);
4924 struct karg arg;
4925 bool userarg; /* allow free text arg */
4926 } cmds[] = {
4927 { "command", 0, command, {.i = ':'}, FALSE },
4928 { "search", 0, command, {.i = '/'}, FALSE },
4929 { "searchb", 0, command, {.i = '?'}, FALSE },
4930 { "togglesrc", 0, toggle_src, {0}, FALSE },
4932 /* yanking and pasting */
4933 { "yankuri", 0, yank_uri, {0}, FALSE },
4934 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4935 { "pasteuricur", 0, paste_uri, {.i = XT_PASTE_CURRENT_TAB}, FALSE },
4936 { "pasteurinew", 0, paste_uri, {.i = XT_PASTE_NEW_TAB}, FALSE },
4938 /* search */
4939 { "searchnext", 0, search, {.i = XT_SEARCH_NEXT}, FALSE },
4940 { "searchprevious", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
4942 /* focus */
4943 { "focusaddress", 0, focus, {.i = XT_FOCUS_URI}, FALSE },
4944 { "focussearch", 0, focus, {.i = XT_FOCUS_SEARCH}, FALSE },
4946 /* hinting */
4947 { "hinting", 0, hint, {.i = 0}, FALSE },
4949 /* custom stylesheet */
4950 { "userstyle", 0, userstyle, {.i = 0 }, FALSE },
4952 /* navigation */
4953 { "goback", 0, navaction, {.i = XT_NAV_BACK}, FALSE },
4954 { "goforward", 0, navaction, {.i = XT_NAV_FORWARD}, FALSE },
4955 { "reload", 0, navaction, {.i = XT_NAV_RELOAD}, FALSE },
4956 { "reloadforce", 0, navaction, {.i = XT_NAV_RELOAD_CACHE}, FALSE },
4958 /* vertical movement */
4959 { "scrolldown", 0, move, {.i = XT_MOVE_DOWN}, FALSE },
4960 { "scrollup", 0, move, {.i = XT_MOVE_UP}, FALSE },
4961 { "scrollbottom", 0, move, {.i = XT_MOVE_BOTTOM}, FALSE },
4962 { "scrolltop", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4963 { "1", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4964 { "scrollhalfdown", 0, move, {.i = XT_MOVE_HALFDOWN},FALSE },
4965 { "scrollhalfup", 0, move, {.i = XT_MOVE_HALFUP}, FALSE },
4966 { "scrollpagedown", 0, move, {.i = XT_MOVE_PAGEDOWN},FALSE },
4967 { "scrollpageup", 0, move, {.i = XT_MOVE_PAGEUP}, FALSE },
4968 /* horizontal movement */
4969 { "scrollright", 0, move, {.i = XT_MOVE_RIGHT}, FALSE },
4970 { "scrollleft", 0, move, {.i = XT_MOVE_LEFT}, FALSE },
4971 { "scrollfarright", 0, move, {.i = XT_MOVE_FARRIGHT},FALSE },
4972 { "scrollfarleft", 0, move, {.i = XT_MOVE_FARLEFT}, FALSE },
4975 { "favorites", 0, xtp_page_fl, {0}, FALSE },
4976 { "fav", 0, xtp_page_fl, {0}, FALSE },
4977 { "favadd", 0, add_favorite, {0}, FALSE },
4979 { "qall", 0, quit, {0}, FALSE },
4980 { "quitall", 0, quit, {0}, FALSE },
4981 { "w", 0, save_tabs, {0}, FALSE },
4982 { "wq", 0, save_tabs_and_quit, {0}, FALSE },
4983 { "help", 0, help, {0}, FALSE },
4984 { "about", 0, about, {0}, FALSE },
4985 { "stats", 0, stats, {0}, FALSE },
4986 { "version", 0, about, {0}, FALSE },
4987 { "cookiejar", 0, xtp_page_cl, {0}, FALSE },
4989 /* js command */
4990 { "js", 0, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4991 { "save", 1, js_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
4992 { "domain", 2, js_cmd, {.i = XT_SAVE | XT_WL_TOPLEVEL}, FALSE },
4993 { "fqdn", 2, js_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
4994 { "show", 1, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4995 { "all", 2, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4996 { "persistent", 2, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT}, FALSE },
4997 { "session", 2, js_cmd, {.i = XT_SHOW | XT_WL_SESSION}, FALSE },
4998 { "toggle", 1, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
4999 { "domain", 2, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL}, FALSE },
5000 { "fqdn", 2, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5002 /* cookie command */
5003 { "cookie", 0, cookie_cmd, {0}, FALSE },
5004 { "save", 1, cookie_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
5005 { "domain", 2, cookie_cmd, {.i = XT_SAVE | XT_WL_TOPLEVEL}, FALSE },
5006 { "fqdn", 2, cookie_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
5007 { "show", 1, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
5008 { "all", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
5009 { "persistent", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT}, FALSE },
5010 { "session", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_SESSION}, FALSE },
5011 { "toggle", 1, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5012 { "domain", 2, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL}, FALSE },
5013 { "fqdn", 2, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5015 /* cert command */
5016 { "cert", 0, cert_cmd, {.i = XT_SHOW}, FALSE },
5017 { "save", 1, cert_cmd, {.i = XT_SAVE}, FALSE },
5018 { "show", 1, cert_cmd, {.i = XT_SHOW}, FALSE },
5020 { "ca", 0, ca_cmd, {0}, FALSE },
5021 { "downloadmgr", 0, xtp_page_dl, {0}, FALSE },
5022 { "dl", 0, xtp_page_dl, {0}, FALSE },
5023 { "h", 0, xtp_page_hl, {0}, FALSE },
5024 { "history", 0, xtp_page_hl, {0}, FALSE },
5025 { "home", 0, go_home, {0}, FALSE },
5026 { "restart", 0, restart, {0}, FALSE },
5027 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5028 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5029 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5030 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5032 { "print", 0, print_page, {0}, FALSE },
5034 /* tabs */
5035 { "open", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5036 { "tabnew", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5037 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5038 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5039 { "tabundoclose", 0, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
5040 { "tabshow", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5041 { "tabhide", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5042 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5043 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5045 /* XXX add count to these commands */
5046 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5047 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5048 { "tablast", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5049 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5050 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT}, TRUE },
5051 { "focusout", 0, resizetab, {.i = -1}, FALSE },
5052 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5054 /* command aliases (handy when -S flag is used) */
5055 { "promptopen", 0, command, {.i = XT_CMD_OPEN}, FALSE },
5056 { "promptopencurrent", 0, command, {.i = XT_CMD_OPEN_CURRENT}, FALSE },
5057 { "prompttabnew", 0, command, {.i = XT_CMD_TABNEW}, FALSE },
5058 { "prompttabnewcurrent",0, command, {.i = XT_CMD_TABNEW_CURRENT}, FALSE },
5060 /* settings */
5061 { "set", 0, set, {0}, FALSE },
5062 { "fullscreen", 0, fullscreen, {0}, FALSE },
5063 { "f", 0, fullscreen, {0}, FALSE },
5065 /* sessions */
5066 { "session", 0, session_cmd, {.i = XT_SHOW}, FALSE },
5067 { "delete", 1, session_cmd, {.i = XT_DELETE}, TRUE },
5068 { "open", 1, session_cmd, {.i = XT_OPEN}, TRUE },
5069 { "save", 1, session_cmd, {.i = XT_SAVE}, TRUE },
5070 { "show", 1, session_cmd, {.i = XT_SHOW}, FALSE },
5073 struct {
5074 int index;
5075 int len;
5076 gchar *list[256];
5077 } cmd_status = {-1, 0};
5079 gboolean
5080 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5082 struct karg a;
5084 hide_oops(t);
5086 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5087 /* go backward */
5088 a.i = XT_NAV_BACK;
5089 navaction(t, &a);
5091 return (TRUE);
5092 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5093 /* go forward */
5094 a.i = XT_NAV_FORWARD;
5095 navaction(t, &a);
5097 return (TRUE);
5100 return (FALSE);
5103 gboolean
5104 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5106 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5108 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5109 delete_tab(t);
5111 return (FALSE);
5115 * cancel, remove, etc. downloads
5117 void
5118 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5120 struct download find, *d = NULL;
5122 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5124 /* some commands require a valid download id */
5125 if (cmd != XT_XTP_DL_LIST) {
5126 /* lookup download in question */
5127 find.id = id;
5128 d = RB_FIND(download_list, &downloads, &find);
5130 if (d == NULL) {
5131 show_oops(t, "%s: no such download", __func__);
5132 return;
5136 /* decide what to do */
5137 switch (cmd) {
5138 case XT_XTP_DL_CANCEL:
5139 webkit_download_cancel(d->download);
5140 break;
5141 case XT_XTP_DL_REMOVE:
5142 webkit_download_cancel(d->download); /* just incase */
5143 g_object_unref(d->download);
5144 RB_REMOVE(download_list, &downloads, d);
5145 break;
5146 case XT_XTP_DL_LIST:
5147 /* Nothing */
5148 break;
5149 default:
5150 show_oops(t, "%s: unknown command", __func__);
5151 break;
5153 xtp_page_dl(t, NULL);
5157 * Actions on history, only does one thing for now, but
5158 * we provide the function for future actions
5160 void
5161 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5163 struct history *h, *next;
5164 int i = 1;
5166 switch (cmd) {
5167 case XT_XTP_HL_REMOVE:
5168 /* walk backwards, as listed in reverse */
5169 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5170 next = RB_PREV(history_list, &hl, h);
5171 if (id == i) {
5172 RB_REMOVE(history_list, &hl, h);
5173 g_free((gpointer) h->title);
5174 g_free((gpointer) h->uri);
5175 g_free(h);
5176 break;
5178 i++;
5180 break;
5181 case XT_XTP_HL_LIST:
5182 /* Nothing - just xtp_page_hl() below */
5183 break;
5184 default:
5185 show_oops(t, "%s: unknown command", __func__);
5186 break;
5189 xtp_page_hl(t, NULL);
5192 /* remove a favorite */
5193 void
5194 remove_favorite(struct tab *t, int index)
5196 char file[PATH_MAX], *title, *uri = NULL;
5197 char *new_favs, *tmp;
5198 FILE *f;
5199 int i;
5200 size_t len, lineno;
5202 /* open favorites */
5203 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5205 if ((f = fopen(file, "r")) == NULL) {
5206 show_oops(t, "%s: can't open favorites: %s",
5207 __func__, strerror(errno));
5208 return;
5211 /* build a string which will become the new favroites file */
5212 new_favs = g_strdup_printf("%s", "");
5214 for (i = 1;;) {
5215 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5216 if (feof(f) || ferror(f))
5217 break;
5218 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5219 if (len == 0) {
5220 free(title);
5221 title = NULL;
5222 continue;
5225 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5226 if (feof(f) || ferror(f)) {
5227 show_oops(t, "%s: can't parse favorites %s",
5228 __func__, strerror(errno));
5229 goto clean;
5233 /* as long as this isn't the one we are deleting add to file */
5234 if (i != index) {
5235 tmp = new_favs;
5236 new_favs = g_strdup_printf("%s%s\n%s\n",
5237 new_favs, title, uri);
5238 g_free(tmp);
5241 free(uri);
5242 uri = NULL;
5243 free(title);
5244 title = NULL;
5245 i++;
5247 fclose(f);
5249 /* write back new favorites file */
5250 if ((f = fopen(file, "w")) == NULL) {
5251 show_oops(t, "%s: can't open favorites: %s",
5252 __func__, strerror(errno));
5253 goto clean;
5256 fwrite(new_favs, strlen(new_favs), 1, f);
5257 fclose(f);
5259 clean:
5260 if (uri)
5261 free(uri);
5262 if (title)
5263 free(title);
5265 g_free(new_favs);
5268 void
5269 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5271 switch (cmd) {
5272 case XT_XTP_FL_LIST:
5273 /* nothing, just the below call to xtp_page_fl() */
5274 break;
5275 case XT_XTP_FL_REMOVE:
5276 remove_favorite(t, arg);
5277 break;
5278 default:
5279 show_oops(t, "%s: invalid favorites command", __func__);
5280 break;
5283 xtp_page_fl(t, NULL);
5286 void
5287 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5289 switch (cmd) {
5290 case XT_XTP_CL_LIST:
5291 /* nothing, just xtp_page_cl() */
5292 break;
5293 case XT_XTP_CL_REMOVE:
5294 remove_cookie(arg);
5295 break;
5296 default:
5297 show_oops(t, "%s: unknown cookie xtp command", __func__);
5298 break;
5301 xtp_page_cl(t, NULL);
5304 /* link an XTP class to it's session key and handler function */
5305 struct xtp_despatch {
5306 uint8_t xtp_class;
5307 char **session_key;
5308 void (*handle_func)(struct tab *, uint8_t, int);
5311 struct xtp_despatch xtp_despatches[] = {
5312 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5313 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5314 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5315 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5316 { NULL, NULL, NULL }
5320 * is the url xtp protocol? (xxxt://)
5321 * if so, parse and despatch correct bahvior
5324 parse_xtp_url(struct tab *t, const char *url)
5326 char *dup = NULL, *p, *last;
5327 uint8_t n_tokens = 0;
5328 char *tokens[4] = {NULL, NULL, NULL, ""};
5329 struct xtp_despatch *dsp, *dsp_match = NULL;
5330 uint8_t req_class;
5331 int ret = FALSE;
5334 * tokens array meaning:
5335 * tokens[0] = class
5336 * tokens[1] = session key
5337 * tokens[2] = action
5338 * tokens[3] = optional argument
5341 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5343 /*xtp tab meaning is normal unless proven special */
5344 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5346 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5347 goto clean;
5349 dup = g_strdup(url + strlen(XT_XTP_STR));
5351 /* split out the url */
5352 for ((p = strtok_r(dup, "/", &last)); p;
5353 (p = strtok_r(NULL, "/", &last))) {
5354 if (n_tokens < 4)
5355 tokens[n_tokens++] = p;
5358 /* should be atleast three fields 'class/seskey/command/arg' */
5359 if (n_tokens < 3)
5360 goto clean;
5362 dsp = xtp_despatches;
5363 req_class = atoi(tokens[0]);
5364 while (dsp->xtp_class != NULL) {
5365 if (dsp->xtp_class == req_class) {
5366 dsp_match = dsp;
5367 break;
5369 dsp++;
5372 /* did we find one atall? */
5373 if (dsp_match == NULL) {
5374 show_oops(t, "%s: no matching xtp despatch found", __func__);
5375 goto clean;
5378 /* check session key and call despatch function */
5379 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5380 ret = TRUE; /* all is well, this was a valid xtp request */
5381 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5384 clean:
5385 if (dup)
5386 g_free(dup);
5388 return (ret);
5393 void
5394 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5396 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5398 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5400 if (t == NULL) {
5401 show_oops_s("activate_uri_entry_cb invalid parameters");
5402 return;
5405 if (uri == NULL) {
5406 show_oops(t, "activate_uri_entry_cb no uri");
5407 return;
5410 uri += strspn(uri, "\t ");
5412 /* if xxxt:// treat specially */
5413 if (parse_xtp_url(t, uri))
5414 return;
5416 /* otherwise continue to load page normally */
5417 load_uri(t, (gchar *)uri);
5418 focus_webview(t);
5421 void
5422 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5424 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5425 char *newuri = NULL;
5426 gchar *enc_search;
5428 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5430 if (t == NULL) {
5431 show_oops_s("activate_search_entry_cb invalid parameters");
5432 return;
5435 if (search_string == NULL) {
5436 show_oops(t, "no search_string");
5437 return;
5440 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5441 newuri = g_strdup_printf(search_string, enc_search);
5442 g_free(enc_search);
5444 webkit_web_view_load_uri(t->wv, newuri);
5445 focus_webview(t);
5447 if (newuri)
5448 g_free(newuri);
5451 void
5452 check_and_set_js(const gchar *uri, struct tab *t)
5454 struct domain *d = NULL;
5455 int es = 0;
5457 if (uri == NULL || t == NULL)
5458 return;
5460 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5461 es = 0;
5462 else
5463 es = 1;
5465 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5466 es ? "enable" : "disable", uri);
5468 g_object_set(G_OBJECT(t->settings),
5469 "enable-scripts", es, (char *)NULL);
5470 g_object_set(G_OBJECT(t->settings),
5471 "javascript-can-open-windows-automatically", es, (char *)NULL);
5472 webkit_web_view_set_settings(t->wv, t->settings);
5474 button_set_stockid(t->js_toggle,
5475 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5478 void
5479 show_ca_status(struct tab *t, const char *uri)
5481 WebKitWebFrame *frame;
5482 WebKitWebDataSource *source;
5483 WebKitNetworkRequest *request;
5484 SoupMessage *message;
5485 GdkColor color;
5486 gchar *col_str = XT_COLOR_WHITE;
5487 int r;
5489 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5490 ssl_strict_certs, ssl_ca_file, uri);
5492 if (uri == NULL)
5493 goto done;
5494 if (ssl_ca_file == NULL) {
5495 if (g_str_has_prefix(uri, "http://"))
5496 goto done;
5497 if (g_str_has_prefix(uri, "https://")) {
5498 col_str = XT_COLOR_RED;
5499 goto done;
5501 return;
5503 if (g_str_has_prefix(uri, "http://") ||
5504 !g_str_has_prefix(uri, "https://"))
5505 goto done;
5507 frame = webkit_web_view_get_main_frame(t->wv);
5508 source = webkit_web_frame_get_data_source(frame);
5509 request = webkit_web_data_source_get_request(source);
5510 message = webkit_network_request_get_message(request);
5512 if (message && (soup_message_get_flags(message) &
5513 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5514 col_str = XT_COLOR_GREEN;
5515 goto done;
5516 } else {
5517 r = load_compare_cert(t, NULL);
5518 if (r == 0)
5519 col_str = XT_COLOR_BLUE;
5520 else if (r == 1)
5521 col_str = XT_COLOR_YELLOW;
5522 else
5523 col_str = XT_COLOR_RED;
5524 goto done;
5526 done:
5527 if (col_str) {
5528 gdk_color_parse(col_str, &color);
5529 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5531 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5532 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5533 &color);
5534 gdk_color_parse(XT_COLOR_BLACK, &color);
5535 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5536 &color);
5537 } else {
5538 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5539 &color);
5540 gdk_color_parse(XT_COLOR_BLACK, &color);
5541 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5542 &color);
5547 void
5548 free_favicon(struct tab *t)
5550 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5551 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5553 if (t->icon_request)
5554 g_object_unref(t->icon_request);
5555 if (t->icon_pixbuf)
5556 g_object_unref(t->icon_pixbuf);
5557 if (t->icon_dest_uri)
5558 g_free(t->icon_dest_uri);
5560 t->icon_pixbuf = NULL;
5561 t->icon_request = NULL;
5562 t->icon_dest_uri = NULL;
5565 void
5566 xt_icon_from_name(struct tab *t, gchar *name)
5568 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5569 GTK_ENTRY_ICON_PRIMARY, "text-html");
5570 if (show_url == 0)
5571 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5572 GTK_ENTRY_ICON_PRIMARY, "text-html");
5573 else
5574 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5575 GTK_ENTRY_ICON_PRIMARY, NULL);
5578 void
5579 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5581 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5582 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5583 if (show_url == 0)
5584 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5585 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5586 else
5587 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5588 GTK_ENTRY_ICON_PRIMARY, NULL);
5591 gboolean
5592 is_valid_icon(char *file)
5594 gboolean valid = 0;
5595 const char *mime_type;
5596 GFileInfo *fi;
5597 GFile *gf;
5599 gf = g_file_new_for_path(file);
5600 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5601 NULL, NULL);
5602 mime_type = g_file_info_get_content_type(fi);
5603 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5604 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5605 g_strcmp0(mime_type, "image/png") == 0 ||
5606 g_strcmp0(mime_type, "image/gif") == 0 ||
5607 g_strcmp0(mime_type, "application/octet-stream") == 0;
5608 g_object_unref(fi);
5609 g_object_unref(gf);
5611 return (valid);
5614 void
5615 set_favicon_from_file(struct tab *t, char *file)
5617 gint width, height;
5618 GdkPixbuf *pixbuf, *scaled;
5619 struct stat sb;
5621 if (t == NULL || file == NULL)
5622 return;
5623 if (t->icon_pixbuf) {
5624 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5625 return;
5628 if (g_str_has_prefix(file, "file://"))
5629 file += strlen("file://");
5630 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5632 if (!stat(file, &sb)) {
5633 if (sb.st_size == 0 || !is_valid_icon(file)) {
5634 /* corrupt icon so trash it */
5635 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5636 __func__, file);
5637 unlink(file);
5638 /* no need to set icon to default here */
5639 return;
5643 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5644 if (pixbuf == NULL) {
5645 xt_icon_from_name(t, "text-html");
5646 return;
5649 g_object_get(pixbuf, "width", &width, "height", &height,
5650 (char *)NULL);
5651 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5652 __func__, t->tab_id, width, height);
5654 if (width > 16 || height > 16) {
5655 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5656 GDK_INTERP_BILINEAR);
5657 g_object_unref(pixbuf);
5658 } else
5659 scaled = pixbuf;
5661 if (scaled == NULL) {
5662 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5663 GDK_INTERP_BILINEAR);
5664 return;
5667 t->icon_pixbuf = scaled;
5668 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5671 void
5672 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5673 WebKitWebView *wv)
5675 WebKitDownloadStatus status = webkit_download_get_status(download);
5676 struct tab *tt = NULL, *t = NULL;
5679 * find the webview instead of passing in the tab as it could have been
5680 * deleted from underneath us.
5682 TAILQ_FOREACH(tt, &tabs, entry) {
5683 if (tt->wv == wv) {
5684 t = tt;
5685 break;
5688 if (t == NULL)
5689 return;
5691 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5692 __func__, t->tab_id, status);
5694 switch (status) {
5695 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5696 /* -1 */
5697 t->icon_download = NULL;
5698 free_favicon(t);
5699 break;
5700 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5701 /* 0 */
5702 break;
5703 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5704 /* 1 */
5705 break;
5706 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5707 /* 2 */
5708 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5709 __func__, t->tab_id);
5710 t->icon_download = NULL;
5711 free_favicon(t);
5712 break;
5713 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5714 /* 3 */
5716 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5717 __func__, t->icon_dest_uri);
5718 set_favicon_from_file(t, t->icon_dest_uri);
5719 /* these will be freed post callback */
5720 t->icon_request = NULL;
5721 t->icon_download = NULL;
5722 break;
5723 default:
5724 break;
5728 void
5729 abort_favicon_download(struct tab *t)
5731 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5733 if (t->icon_download) {
5734 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5735 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5736 webkit_download_cancel(t->icon_download);
5737 t->icon_download = NULL;
5738 } else
5739 free_favicon(t);
5741 xt_icon_from_name(t, "text-html");
5744 void
5745 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5747 gchar *name_hash, file[PATH_MAX];
5748 struct stat sb;
5750 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5752 if (uri == NULL || t == NULL)
5753 return;
5755 if (t->icon_request) {
5756 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5757 return;
5760 /* check to see if we got the icon in cache */
5761 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5762 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5763 g_free(name_hash);
5765 if (!stat(file, &sb)) {
5766 if (sb.st_size > 0) {
5767 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5768 __func__, file);
5769 set_favicon_from_file(t, file);
5770 return;
5773 /* corrupt icon so trash it */
5774 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5775 __func__, file);
5776 unlink(file);
5779 /* create download for icon */
5780 t->icon_request = webkit_network_request_new(uri);
5781 if (t->icon_request == NULL) {
5782 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5783 __func__, uri);
5784 return;
5787 t->icon_download = webkit_download_new(t->icon_request);
5788 if (t->icon_download == NULL) {
5789 fprintf(stderr, "%s: icon_download", __func__);
5790 return;
5793 /* we have to free icon_dest_uri later */
5794 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5795 webkit_download_set_destination_uri(t->icon_download,
5796 t->icon_dest_uri);
5798 if (webkit_download_get_status(t->icon_download) ==
5799 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5800 fprintf(stderr, "%s: download failed to start", __func__);
5801 g_object_unref(t->icon_request);
5802 g_free(t->icon_dest_uri);
5803 t->icon_request = NULL;
5804 t->icon_dest_uri = NULL;
5805 return;
5808 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5809 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5811 webkit_download_start(t->icon_download);
5814 void
5815 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5817 const gchar *set = NULL, *uri = NULL, *title = NULL;
5818 struct history *h, find;
5819 const gchar *s_loading;
5820 struct karg a;
5822 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5823 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5825 if (t == NULL) {
5826 show_oops_s("notify_load_status_cb invalid paramters");
5827 return;
5830 switch (webkit_web_view_get_load_status(wview)) {
5831 case WEBKIT_LOAD_PROVISIONAL:
5832 /* 0 */
5833 abort_favicon_download(t);
5834 #if GTK_CHECK_VERSION(2, 20, 0)
5835 gtk_widget_show(t->spinner);
5836 gtk_spinner_start(GTK_SPINNER(t->spinner));
5837 #endif
5838 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5840 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5842 t->focus_wv = 1;
5844 break;
5846 case WEBKIT_LOAD_COMMITTED:
5847 /* 1 */
5848 if ((uri = get_uri(wview)) != NULL) {
5849 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5850 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5852 if (t->status) {
5853 g_free(t->status);
5854 t->status = NULL;
5856 set_status(t, (char *)uri, XT_STATUS_LOADING);
5859 /* check if js white listing is enabled */
5860 if (enable_js_whitelist) {
5861 uri = get_uri(wview);
5862 check_and_set_js(uri, t);
5865 if (t->styled)
5866 apply_style(t);
5868 show_ca_status(t, uri);
5870 /* we know enough to autosave the session */
5871 if (session_autosave) {
5872 a.s = NULL;
5873 save_tabs(t, &a);
5875 break;
5877 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5878 /* 3 */
5879 break;
5881 case WEBKIT_LOAD_FINISHED:
5882 /* 2 */
5883 uri = get_uri(wview);
5884 if (uri == NULL)
5885 return;
5887 if (!strncmp(uri, "http://", strlen("http://")) ||
5888 !strncmp(uri, "https://", strlen("https://")) ||
5889 !strncmp(uri, "file://", strlen("file://"))) {
5890 find.uri = uri;
5891 h = RB_FIND(history_list, &hl, &find);
5892 if (!h) {
5893 title = webkit_web_view_get_title(wview);
5894 set = title ? title: uri;
5895 h = g_malloc(sizeof *h);
5896 h->uri = g_strdup(uri);
5897 h->title = g_strdup(set);
5898 RB_INSERT(history_list, &hl, h);
5899 completion_add_uri(h->uri);
5900 update_history_tabs(NULL);
5904 set_status(t, (char *)uri, XT_STATUS_URI);
5905 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5906 case WEBKIT_LOAD_FAILED:
5907 /* 4 */
5908 #endif
5909 #if GTK_CHECK_VERSION(2, 20, 0)
5910 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5911 gtk_widget_hide(t->spinner);
5912 #endif
5913 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5914 if (s_loading && !strcmp(s_loading, "Loading"))
5915 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5916 default:
5917 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5918 break;
5921 if (t->item)
5922 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5923 else
5924 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5925 webkit_web_view_can_go_back(wview));
5927 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5928 webkit_web_view_can_go_forward(wview));
5930 /* take focus if we are visible */
5931 focus_webview(t);
5934 void
5935 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5937 const gchar *set = NULL, *title = NULL;
5939 title = webkit_web_view_get_title(wview);
5940 set = title ? title: get_uri(wview);
5941 gtk_label_set_text(GTK_LABEL(t->label), set);
5942 gtk_window_set_title(GTK_WINDOW(main_window), set);
5945 void
5946 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5948 run_script(t, JS_HINTING);
5951 void
5952 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5954 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5955 progress == 100 ? 0 : (double)progress / 100);
5956 if (show_url == 0) {
5957 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5958 progress == 100 ? 0 : (double)progress / 100);
5963 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5964 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5965 WebKitWebPolicyDecision *pd, struct tab *t)
5967 char *uri;
5968 WebKitWebNavigationReason reason;
5969 struct domain *d = NULL;
5971 if (t == NULL) {
5972 show_oops_s("webview_npd_cb invalid parameters");
5973 return (FALSE);
5976 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5977 t->ctrl_click,
5978 webkit_network_request_get_uri(request));
5980 uri = (char *)webkit_network_request_get_uri(request);
5982 /* if this is an xtp url, we don't load anything else */
5983 if (parse_xtp_url(t, uri))
5984 return (TRUE);
5986 if (t->ctrl_click) {
5987 t->ctrl_click = 0;
5988 create_new_tab(uri, NULL, ctrl_click_focus);
5989 webkit_web_policy_decision_ignore(pd);
5990 return (TRUE); /* we made the decission */
5994 * This is a little hairy but it comes down to this:
5995 * when we run in whitelist mode we have to assist the browser in
5996 * opening the URL that it would have opened in a new tab.
5998 reason = webkit_web_navigation_action_get_reason(na);
5999 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6000 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6001 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6002 load_uri(t, uri);
6004 webkit_web_policy_decision_use(pd);
6005 return (TRUE); /* we made the decission */
6008 return (FALSE);
6011 WebKitWebView *
6012 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6014 struct tab *tt;
6015 struct domain *d = NULL;
6016 const gchar *uri;
6017 WebKitWebView *webview = NULL;
6019 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6020 webkit_web_view_get_uri(wv));
6022 if (tabless) {
6023 /* open in current tab */
6024 webview = t->wv;
6025 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6026 uri = webkit_web_view_get_uri(wv);
6027 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6028 return (NULL);
6030 tt = create_new_tab(NULL, NULL, 1);
6031 webview = tt->wv;
6032 } else if (enable_scripts == 1) {
6033 tt = create_new_tab(NULL, NULL, 1);
6034 webview = tt->wv;
6037 return (webview);
6040 gboolean
6041 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6043 const gchar *uri;
6044 struct domain *d = NULL;
6046 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6048 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6049 uri = webkit_web_view_get_uri(wv);
6050 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6051 return (FALSE);
6053 delete_tab(t);
6054 } else if (enable_scripts == 1)
6055 delete_tab(t);
6057 return (TRUE);
6061 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6063 /* we can not eat the event without throwing gtk off so defer it */
6065 /* catch middle click */
6066 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6067 t->ctrl_click = 1;
6068 goto done;
6071 /* catch ctrl click */
6072 if (e->type == GDK_BUTTON_RELEASE &&
6073 CLEAN(e->state) == GDK_CONTROL_MASK)
6074 t->ctrl_click = 1;
6075 else
6076 t->ctrl_click = 0;
6077 done:
6078 return (XT_CB_PASSTHROUGH);
6082 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6084 struct mime_type *m;
6086 m = find_mime_type(mime_type);
6087 if (m == NULL)
6088 return (1);
6089 if (m->mt_download)
6090 return (1);
6092 switch (fork()) {
6093 case -1:
6094 show_oops(t, "can't fork mime handler");
6095 /* NOTREACHED */
6096 case 0:
6097 break;
6098 default:
6099 return (0);
6102 /* child */
6103 execlp(m->mt_action, m->mt_action,
6104 webkit_network_request_get_uri(request), (void *)NULL);
6106 _exit(0);
6108 /* NOTREACHED */
6109 return (0);
6112 const gchar *
6113 get_mime_type(char *file)
6115 const char *mime_type;
6116 GFileInfo *fi;
6117 GFile *gf;
6119 if (g_str_has_prefix(file, "file://"))
6120 file += strlen("file://");
6122 gf = g_file_new_for_path(file);
6123 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6124 NULL, NULL);
6125 mime_type = g_file_info_get_content_type(fi);
6126 g_object_unref(fi);
6127 g_object_unref(gf);
6129 return (mime_type);
6133 run_download_mimehandler(char *mime_type, char *file)
6135 struct mime_type *m;
6137 m = find_mime_type(mime_type);
6138 if (m == NULL)
6139 return (1);
6141 switch (fork()) {
6142 case -1:
6143 show_oops_s("can't fork download mime handler");
6144 /* NOTREACHED */
6145 case 0:
6146 break;
6147 default:
6148 return (0);
6151 /* child */
6152 if (g_str_has_prefix(file, "file://"))
6153 file += strlen("file://");
6154 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6156 _exit(0);
6158 /* NOTREACHED */
6159 return (0);
6162 void
6163 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6164 WebKitWebView *wv)
6166 WebKitDownloadStatus status;
6167 const gchar *file = NULL, *mime = NULL;
6169 if (download == NULL)
6170 return;
6171 status = webkit_download_get_status(download);
6172 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6173 return;
6175 file = webkit_download_get_destination_uri(download);
6176 if (file == NULL)
6177 return;
6178 mime = get_mime_type((char *)file);
6179 if (mime == NULL)
6180 return;
6182 run_download_mimehandler((char *)mime, (char *)file);
6186 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6187 WebKitNetworkRequest *request, char *mime_type,
6188 WebKitWebPolicyDecision *decision, struct tab *t)
6190 if (t == NULL) {
6191 show_oops_s("webview_mimetype_cb invalid parameters");
6192 return (FALSE);
6195 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6196 t->tab_id, mime_type);
6198 if (run_mimehandler(t, mime_type, request) == 0) {
6199 webkit_web_policy_decision_ignore(decision);
6200 focus_webview(t);
6201 return (TRUE);
6204 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6205 webkit_web_policy_decision_download(decision);
6206 return (TRUE);
6209 return (FALSE);
6213 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6214 struct tab *t)
6216 const gchar *filename;
6217 char *uri = NULL;
6218 struct download *download_entry;
6219 int ret = TRUE;
6221 if (wk_download == NULL || t == NULL) {
6222 show_oops_s("%s invalid parameters", __func__);
6223 return (FALSE);
6226 filename = webkit_download_get_suggested_filename(wk_download);
6227 if (filename == NULL)
6228 return (FALSE); /* abort download */
6230 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6232 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6233 "local %s\n", __func__, t->tab_id, filename, uri);
6235 webkit_download_set_destination_uri(wk_download, uri);
6237 if (webkit_download_get_status(wk_download) ==
6238 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6239 show_oops(t, "%s: download failed to start", __func__);
6240 ret = FALSE;
6241 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6242 } else {
6243 /* connect "download first" mime handler */
6244 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6245 G_CALLBACK(download_status_changed_cb), NULL);
6247 download_entry = g_malloc(sizeof(struct download));
6248 download_entry->download = wk_download;
6249 download_entry->tab = t;
6250 download_entry->id = next_download_id++;
6251 RB_INSERT(download_list, &downloads, download_entry);
6252 /* get from history */
6253 g_object_ref(wk_download);
6254 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6255 show_oops(t, "Download of '%s' started...",
6256 basename(webkit_download_get_destination_uri(wk_download)));
6259 if (uri)
6260 g_free(uri);
6262 /* sync other download manager tabs */
6263 update_download_tabs(NULL);
6266 * NOTE: never redirect/render the current tab before this
6267 * function returns. This will cause the download to never start.
6269 return (ret); /* start download */
6272 void
6273 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6275 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6277 if (t == NULL) {
6278 show_oops_s("webview_hover_cb");
6279 return;
6282 if (uri)
6283 set_status(t, uri, XT_STATUS_LINK);
6284 else {
6285 if (t->status)
6286 set_status(t, t->status, XT_STATUS_NOTHING);
6290 gboolean
6291 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6293 struct key_binding *k;
6295 TAILQ_FOREACH(k, &kbl, entry)
6296 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6297 if (k->mask == 0) {
6298 if ((e->state & (CTRL | MOD1)) == 0)
6299 return (cmd_execute(t, k->cmd));
6300 } else if ((e->state & k->mask) == k->mask) {
6301 return (cmd_execute(t, k->cmd));
6305 return (XT_CB_PASSTHROUGH);
6309 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6311 char s[2], buf[128];
6312 const char *errstr = NULL;
6313 long long link;
6315 /* don't use w directly; use t->whatever instead */
6317 if (t == NULL) {
6318 show_oops_s("wv_keypress_after_cb");
6319 return (XT_CB_PASSTHROUGH);
6322 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6323 e->keyval, e->state, t);
6325 if (t->hints_on) {
6326 /* ESC */
6327 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6328 disable_hints(t);
6329 return (XT_CB_HANDLED);
6332 /* RETURN */
6333 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6334 link = strtonum(t->hint_num, 1, 1000, &errstr);
6335 if (errstr) {
6336 /* we have a string */
6337 } else {
6338 /* we have a number */
6339 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6340 t->hint_num);
6341 run_script(t, buf);
6343 disable_hints(t);
6346 /* BACKSPACE */
6347 /* XXX unfuck this */
6348 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6349 if (t->hint_mode == XT_HINT_NUMERICAL) {
6350 /* last input was numerical */
6351 int l;
6352 l = strlen(t->hint_num);
6353 if (l > 0) {
6354 l--;
6355 if (l == 0) {
6356 disable_hints(t);
6357 enable_hints(t);
6358 } else {
6359 t->hint_num[l] = '\0';
6360 goto num;
6363 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6364 /* last input was alphanumerical */
6365 int l;
6366 l = strlen(t->hint_buf);
6367 if (l > 0) {
6368 l--;
6369 if (l == 0) {
6370 disable_hints(t);
6371 enable_hints(t);
6372 } else {
6373 t->hint_buf[l] = '\0';
6374 goto anum;
6377 } else {
6378 /* bogus */
6379 disable_hints(t);
6383 /* numerical input */
6384 if (CLEAN(e->state) == 0 &&
6385 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6386 snprintf(s, sizeof s, "%c", e->keyval);
6387 strlcat(t->hint_num, s, sizeof t->hint_num);
6388 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6389 t->hint_num);
6390 num:
6391 link = strtonum(t->hint_num, 1, 1000, &errstr);
6392 if (errstr) {
6393 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6394 disable_hints(t);
6395 } else {
6396 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6397 t->hint_num);
6398 t->hint_mode = XT_HINT_NUMERICAL;
6399 run_script(t, buf);
6402 /* empty the counter buffer */
6403 bzero(t->hint_buf, sizeof t->hint_buf);
6404 return (XT_CB_HANDLED);
6407 /* alphanumerical input */
6408 if (
6409 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6410 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6411 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6412 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6413 snprintf(s, sizeof s, "%c", e->keyval);
6414 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6415 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6416 t->hint_buf);
6417 anum:
6418 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6419 run_script(t, buf);
6421 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6422 t->hint_buf);
6423 t->hint_mode = XT_HINT_ALPHANUM;
6424 run_script(t, buf);
6426 /* empty the counter buffer */
6427 bzero(t->hint_num, sizeof t->hint_num);
6428 return (XT_CB_HANDLED);
6431 return (XT_CB_HANDLED);
6434 return (handle_keypress(t, e, 0));
6438 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6440 hide_oops(t);
6442 return (XT_CB_PASSTHROUGH);
6446 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6448 const gchar *c = gtk_entry_get_text(w);
6449 GdkColor color;
6450 int forward = TRUE;
6452 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6453 e->keyval, e->state, t);
6455 if (t == NULL) {
6456 show_oops_s("cmd_keyrelease_cb invalid parameters");
6457 return (XT_CB_PASSTHROUGH);
6460 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6461 e->keyval, e->state, t);
6463 if (c[0] == ':')
6464 goto done;
6465 if (strlen(c) == 1) {
6466 webkit_web_view_unmark_text_matches(t->wv);
6467 goto done;
6470 if (c[0] == '/')
6471 forward = TRUE;
6472 else if (c[0] == '?')
6473 forward = FALSE;
6474 else
6475 goto done;
6477 /* search */
6478 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6479 FALSE) {
6480 /* not found, mark red */
6481 gdk_color_parse(XT_COLOR_RED, &color);
6482 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6483 /* unmark and remove selection */
6484 webkit_web_view_unmark_text_matches(t->wv);
6485 /* my kingdom for a way to unselect text in webview */
6486 } else {
6487 /* found, highlight all */
6488 webkit_web_view_unmark_text_matches(t->wv);
6489 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6490 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6491 gdk_color_parse(XT_COLOR_WHITE, &color);
6492 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6494 done:
6495 return (XT_CB_PASSTHROUGH);
6498 gboolean
6499 match_uri(const gchar *uri, const gchar *key) {
6500 gchar *voffset;
6501 size_t len;
6502 gboolean match = FALSE;
6504 len = strlen(key);
6506 if (!strncmp(key, uri, len))
6507 match = TRUE;
6508 else {
6509 voffset = strstr(uri, "/") + 2;
6510 if (!strncmp(key, voffset, len))
6511 match = TRUE;
6512 else if (g_str_has_prefix(voffset, "www.")) {
6513 voffset = voffset + strlen("www.");
6514 if (!strncmp(key, voffset, len))
6515 match = TRUE;
6519 return (match);
6522 void
6523 cmd_getlist(int id, char *key)
6525 int i, dep, c = 0;
6526 struct history *h;
6528 if (id >= 0 && (cmds[id].userarg && (cmds[id].arg.i == XT_TAB_OPEN || cmds[id].arg.i == XT_TAB_NEW))) {
6529 RB_FOREACH_REVERSE(h, history_list, &hl)
6530 if (match_uri(h->uri, key)) {
6531 cmd_status.list[c] = (char *)h->uri;
6532 if (++c > 255)
6533 break;
6536 cmd_status.len = c;
6537 return;
6540 dep = (id == -1) ? 0 : cmds[id].level + 1;
6542 for (i = id + 1; i < LENGTH(cmds); i++) {
6543 if(cmds[i].level < dep)
6544 break;
6545 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6546 cmd_status.list[c++] = cmds[i].cmd;
6550 cmd_status.len = c;
6553 char *
6554 cmd_getnext(int dir)
6556 cmd_status.index += dir;
6558 if (cmd_status.index < 0)
6559 cmd_status.index = cmd_status.len - 1;
6560 else if (cmd_status.index >= cmd_status.len)
6561 cmd_status.index = 0;
6563 return cmd_status.list[cmd_status.index];
6567 cmd_tokenize(char *s, char *tokens[])
6569 int i = 0;
6570 char *tok, *last;
6571 size_t len = strlen(s);
6572 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6574 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6575 tokens[i] = tok;
6577 if (blank && i < 3)
6578 tokens[i++] = "";
6580 return (i);
6583 void
6584 cmd_complete(struct tab *t, char *str, int dir)
6586 GtkEntry *w = GTK_ENTRY(t->cmd);
6587 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6588 char *tok, *match, *s = g_strdup(str);
6589 char *tokens[3];
6590 char res[XT_MAX_URL_LENGTH + 32] = ":";
6592 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", str);
6594 levels = cmd_tokenize(s, tokens);
6595 g_free(s);
6597 for (i = 0; i < levels - 1; i++) {
6598 tok = tokens[i];
6599 matchcount = 0;
6600 for (j = c; j < LENGTH(cmds); j++) {
6601 if (cmds[j].level < dep)
6602 break;
6603 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6604 matchcount++;
6605 c = j + 1;
6606 if (strlen(tok) == strlen(cmds[j].cmd)) {
6607 matchcount = 1;
6608 break;
6613 if (matchcount == 1) {
6614 strlcat(res, tok, sizeof res);
6615 strlcat(res, " ", sizeof res);
6616 dep++;
6617 } else
6618 return;
6620 parent = c - 1;
6623 if (cmd_status.index == -1)
6624 cmd_getlist(parent, tokens[i]);
6626 if (cmd_status.len > 0) {
6627 match = cmd_getnext(dir);
6628 strlcat(res, match, sizeof res);
6629 gtk_entry_set_text(w, res);
6630 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6634 gboolean
6635 cmd_execute(struct tab *t, char *str)
6637 struct cmd *cmd = NULL;
6638 char *tok, *last, *s = g_strdup(str);
6639 int j, len, c = 0, dep = 0, matchcount = 0;
6641 for (tok = strtok_r(s, " ", &last); tok;
6642 tok = strtok_r(NULL, " ", &last)) {
6643 matchcount = 0;
6644 for (j = c; j < LENGTH(cmds); j++) {
6645 if (cmds[j].level < dep)
6646 break;
6647 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6648 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6649 matchcount++;
6650 c = j + 1;
6651 cmd = &cmds[j];
6652 if (len == strlen(cmds[j].cmd)) {
6653 matchcount = 1;
6654 break;
6658 if (matchcount == 1) {
6659 if (cmd->userarg)
6660 goto execute_cmd;
6661 dep++;
6662 } else {
6663 show_oops(t, "Invalid command: %s", str);
6664 g_free(s);
6665 return (XT_CB_PASSTHROUGH);
6668 execute_cmd:
6669 if (cmd->userarg)
6670 cmd->arg.s = last ? g_strdup(last) : g_strdup("");
6671 else
6672 cmd->arg.s = g_strdup(tok);
6674 /* arg->s contains last token */
6675 cmd->func(t, &cmd->arg);
6676 g_free(s);
6677 if (cmd->arg.s)
6678 g_free(cmd->arg.s);
6680 return (XT_CB_HANDLED);
6684 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6686 if (t == NULL) {
6687 show_oops_s("entry_key_cb invalid parameters");
6688 return (XT_CB_PASSTHROUGH);
6691 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6692 e->keyval, e->state, t);
6694 hide_oops(t);
6696 if (e->keyval == GDK_Escape) {
6697 /* don't use focus_webview(t) because we want to type :cmds */
6698 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6701 return (handle_keypress(t, e, 1));
6705 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6707 int rv = XT_CB_HANDLED;
6708 const gchar *c = gtk_entry_get_text(w);
6710 if (t == NULL) {
6711 show_oops_s("cmd_keypress_cb parameters");
6712 return (XT_CB_PASSTHROUGH);
6715 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6716 e->keyval, e->state, t);
6718 /* sanity */
6719 if (c == NULL)
6720 e->keyval = GDK_Escape;
6721 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6722 e->keyval = GDK_Escape;
6724 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6725 cmd_status.index = -1;
6727 switch (e->keyval) {
6728 case GDK_Tab:
6729 if (c[0] == ':')
6730 cmd_complete(t, (char *)&c[1], 1);
6731 goto done;
6732 case GDK_ISO_Left_Tab:
6733 if (c[0] == ':')
6734 cmd_complete(t, (char *)&c[1], -1);
6736 goto done;
6737 case GDK_BackSpace:
6738 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6739 break;
6740 /* FALLTHROUGH */
6741 case GDK_Escape:
6742 hide_cmd(t);
6743 focus_webview(t);
6745 /* cancel search */
6746 if (c[0] == '/' || c[0] == '?')
6747 webkit_web_view_unmark_text_matches(t->wv);
6748 goto done;
6751 rv = XT_CB_PASSTHROUGH;
6752 done:
6753 return (rv);
6757 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6759 if (t == NULL) {
6760 show_oops_s("cmd_focusout_cb invalid parameters");
6761 return (XT_CB_PASSTHROUGH);
6763 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6765 hide_cmd(t);
6766 hide_oops(t);
6768 if (show_url == 0 || t->focus_wv)
6769 focus_webview(t);
6770 else
6771 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6773 return (XT_CB_PASSTHROUGH);
6776 void
6777 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6779 char *s;
6780 const gchar *c = gtk_entry_get_text(entry);
6782 if (t == NULL) {
6783 show_oops_s("cmd_activate_cb invalid parameters");
6784 return;
6787 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6789 hide_cmd(t);
6791 /* sanity */
6792 if (c == NULL)
6793 goto done;
6794 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6795 goto done;
6796 if (strlen(c) < 2)
6797 goto done;
6798 s = (char *)&c[1];
6800 if (c[0] == '/' || c[0] == '?') {
6801 if (t->search_text) {
6802 g_free(t->search_text);
6803 t->search_text = NULL;
6806 t->search_text = g_strdup(s);
6807 if (global_search)
6808 g_free(global_search);
6809 global_search = g_strdup(s);
6810 t->search_forward = c[0] == '/';
6812 goto done;
6815 cmd_execute(t, s);
6817 done:
6818 return;
6821 void
6822 backward_cb(GtkWidget *w, struct tab *t)
6824 struct karg a;
6826 if (t == NULL) {
6827 show_oops_s("backward_cb invalid parameters");
6828 return;
6831 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6833 a.i = XT_NAV_BACK;
6834 navaction(t, &a);
6837 void
6838 forward_cb(GtkWidget *w, struct tab *t)
6840 struct karg a;
6842 if (t == NULL) {
6843 show_oops_s("forward_cb invalid parameters");
6844 return;
6847 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6849 a.i = XT_NAV_FORWARD;
6850 navaction(t, &a);
6853 void
6854 home_cb(GtkWidget *w, struct tab *t)
6856 if (t == NULL) {
6857 show_oops_s("home_cb invalid parameters");
6858 return;
6861 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6863 load_uri(t, home);
6866 void
6867 stop_cb(GtkWidget *w, struct tab *t)
6869 WebKitWebFrame *frame;
6871 if (t == NULL) {
6872 show_oops_s("stop_cb invalid parameters");
6873 return;
6876 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6878 frame = webkit_web_view_get_main_frame(t->wv);
6879 if (frame == NULL) {
6880 show_oops(t, "stop_cb: no frame");
6881 return;
6884 webkit_web_frame_stop_loading(frame);
6885 abort_favicon_download(t);
6888 void
6889 setup_webkit(struct tab *t)
6891 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6892 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6893 FALSE, (char *)NULL);
6894 else
6895 warnx("webkit does not have \"enable-dns-prefetching\" property");
6896 g_object_set(G_OBJECT(t->settings),
6897 "user-agent", t->user_agent, (char *)NULL);
6898 g_object_set(G_OBJECT(t->settings),
6899 "enable-scripts", enable_scripts, (char *)NULL);
6900 g_object_set(G_OBJECT(t->settings),
6901 "enable-plugins", enable_plugins, (char *)NULL);
6902 g_object_set(G_OBJECT(t->settings),
6903 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6904 g_object_set(G_OBJECT(t->wv),
6905 "full-content-zoom", TRUE, (char *)NULL);
6906 adjustfont_webkit(t, XT_FONT_SET);
6908 webkit_web_view_set_settings(t->wv, t->settings);
6911 GtkWidget *
6912 create_browser(struct tab *t)
6914 GtkWidget *w;
6915 gchar *strval;
6917 if (t == NULL) {
6918 show_oops_s("create_browser invalid parameters");
6919 return (NULL);
6922 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6923 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6924 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6925 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6927 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6928 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6929 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6931 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6932 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6934 /* set defaults */
6935 t->settings = webkit_web_settings_new();
6937 if (user_agent == NULL) {
6938 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6939 (char *)NULL);
6940 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6941 g_free(strval);
6942 } else
6943 t->user_agent = g_strdup(user_agent);
6945 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
6947 setup_webkit(t);
6949 return (w);
6952 GtkWidget *
6953 create_window(void)
6955 GtkWidget *w;
6957 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6958 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6959 gtk_widget_set_name(w, "xxxterm");
6960 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6961 g_signal_connect(G_OBJECT(w), "delete_event",
6962 G_CALLBACK (gtk_main_quit), NULL);
6964 return (w);
6967 GtkWidget *
6968 create_kiosk_toolbar(struct tab *t)
6970 GtkWidget *toolbar = NULL, *b;
6972 b = gtk_hbox_new(FALSE, 0);
6973 toolbar = b;
6974 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6976 /* backward button */
6977 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
6978 gtk_widget_set_sensitive(t->backward, FALSE);
6979 g_signal_connect(G_OBJECT(t->backward), "clicked",
6980 G_CALLBACK(backward_cb), t);
6981 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
6983 /* forward button */
6984 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
6985 gtk_widget_set_sensitive(t->forward, FALSE);
6986 g_signal_connect(G_OBJECT(t->forward), "clicked",
6987 G_CALLBACK(forward_cb), t);
6988 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
6990 /* home button */
6991 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
6992 gtk_widget_set_sensitive(t->gohome, true);
6993 g_signal_connect(G_OBJECT(t->gohome), "clicked",
6994 G_CALLBACK(home_cb), t);
6995 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
6997 /* create widgets but don't use them */
6998 t->uri_entry = gtk_entry_new();
6999 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7000 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7001 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7003 return (toolbar);
7006 GtkWidget *
7007 create_toolbar(struct tab *t)
7009 GtkWidget *toolbar = NULL, *b, *eb1;
7011 b = gtk_hbox_new(FALSE, 0);
7012 toolbar = b;
7013 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7015 if (fancy_bar) {
7016 /* backward button */
7017 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7018 gtk_widget_set_sensitive(t->backward, FALSE);
7019 g_signal_connect(G_OBJECT(t->backward), "clicked",
7020 G_CALLBACK(backward_cb), t);
7021 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7023 /* forward button */
7024 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7025 gtk_widget_set_sensitive(t->forward, FALSE);
7026 g_signal_connect(G_OBJECT(t->forward), "clicked",
7027 G_CALLBACK(forward_cb), t);
7028 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7029 FALSE, 0);
7031 /* stop button */
7032 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7033 gtk_widget_set_sensitive(t->stop, FALSE);
7034 g_signal_connect(G_OBJECT(t->stop), "clicked",
7035 G_CALLBACK(stop_cb), t);
7036 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7037 FALSE, 0);
7039 /* JS button */
7040 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7041 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7042 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7043 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7044 G_CALLBACK(js_toggle_cb), t);
7045 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7048 t->uri_entry = gtk_entry_new();
7049 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7050 G_CALLBACK(activate_uri_entry_cb), t);
7051 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7052 G_CALLBACK(entry_key_cb), t);
7053 completion_add(t);
7054 eb1 = gtk_hbox_new(FALSE, 0);
7055 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7056 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7057 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7059 /* search entry */
7060 if (fancy_bar && search_string) {
7061 GtkWidget *eb2;
7062 t->search_entry = gtk_entry_new();
7063 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7064 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7065 G_CALLBACK(activate_search_entry_cb), t);
7066 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7067 G_CALLBACK(entry_key_cb), t);
7068 gtk_widget_set_size_request(t->search_entry, -1, -1);
7069 eb2 = gtk_hbox_new(FALSE, 0);
7070 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7071 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7073 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7075 return (toolbar);
7078 void
7079 recalc_tabs(void)
7081 struct tab *t;
7083 TAILQ_FOREACH(t, &tabs, entry)
7084 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7088 undo_close_tab_save(struct tab *t)
7090 int m, n;
7091 const gchar *uri;
7092 struct undo *u1, *u2;
7093 GList *items;
7094 WebKitWebHistoryItem *item;
7096 if ((uri = get_uri(t->wv)) == NULL)
7097 return (1);
7099 u1 = g_malloc0(sizeof(struct undo));
7100 u1->uri = g_strdup(uri);
7102 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7104 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7105 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7106 u1->back = n;
7108 /* forward history */
7109 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7111 while (items) {
7112 item = items->data;
7113 u1->history = g_list_prepend(u1->history,
7114 webkit_web_history_item_copy(item));
7115 items = g_list_next(items);
7118 /* current item */
7119 if (m) {
7120 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7121 u1->history = g_list_prepend(u1->history,
7122 webkit_web_history_item_copy(item));
7125 /* back history */
7126 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7128 while (items) {
7129 item = items->data;
7130 u1->history = g_list_prepend(u1->history,
7131 webkit_web_history_item_copy(item));
7132 items = g_list_next(items);
7135 TAILQ_INSERT_HEAD(&undos, u1, entry);
7137 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7138 u2 = TAILQ_LAST(&undos, undo_tailq);
7139 TAILQ_REMOVE(&undos, u2, entry);
7140 g_free(u2->uri);
7141 g_list_free(u2->history);
7142 g_free(u2);
7143 } else
7144 undo_count++;
7146 return (0);
7149 void
7150 delete_tab(struct tab *t)
7152 struct karg a;
7154 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7156 if (t == NULL)
7157 return;
7159 TAILQ_REMOVE(&tabs, t, entry);
7161 /* halt all webkit activity */
7162 abort_favicon_download(t);
7163 webkit_web_view_stop_loading(t->wv);
7164 undo_close_tab_save(t);
7166 if (browser_mode == XT_BM_KIOSK) {
7167 gtk_widget_destroy(t->uri_entry);
7168 gtk_widget_destroy(t->stop);
7169 gtk_widget_destroy(t->js_toggle);
7172 gtk_widget_destroy(t->vbox);
7173 g_free(t->user_agent);
7174 g_free(t->stylesheet);
7175 g_free(t);
7177 recalc_tabs();
7178 if (TAILQ_EMPTY(&tabs)) {
7179 if (browser_mode == XT_BM_KIOSK)
7180 create_new_tab(home, NULL, 1);
7181 else
7182 create_new_tab(NULL, NULL, 1);
7185 /* recreate session */
7186 if (session_autosave) {
7187 a.s = NULL;
7188 save_tabs(t, &a);
7192 void
7193 adjustfont_webkit(struct tab *t, int adjust)
7195 gfloat zoom;
7197 if (t == NULL) {
7198 show_oops_s("adjustfont_webkit invalid parameters");
7199 return;
7202 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7203 if (adjust == XT_FONT_SET) {
7204 t->font_size = default_font_size;
7205 zoom = default_zoom_level;
7206 t->font_size += adjust;
7207 g_object_set(G_OBJECT(t->settings), "default-font-size",
7208 t->font_size, (char *)NULL);
7209 g_object_get(G_OBJECT(t->settings), "default-font-size",
7210 &t->font_size, (char *)NULL);
7211 } else {
7212 t->font_size += adjust;
7213 zoom += adjust/25.0;
7214 if (zoom < 0.0) {
7215 zoom = 0.04;
7218 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7219 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7222 void
7223 append_tab(struct tab *t)
7225 if (t == NULL)
7226 return;
7228 TAILQ_INSERT_TAIL(&tabs, t, entry);
7229 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7232 struct tab *
7233 create_new_tab(char *title, struct undo *u, int focus)
7235 struct tab *t, *tt;
7236 int load = 1, id, notfound;
7237 GtkWidget *b, *bb;
7238 WebKitWebHistoryItem *item;
7239 GList *items;
7240 GdkColor color;
7242 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7244 if (tabless && !TAILQ_EMPTY(&tabs)) {
7245 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7246 return (NULL);
7249 t = g_malloc0(sizeof *t);
7251 if (title == NULL) {
7252 title = "(untitled)";
7253 load = 0;
7256 t->vbox = gtk_vbox_new(FALSE, 0);
7258 /* label + button for tab */
7259 b = gtk_hbox_new(FALSE, 0);
7260 t->tab_content = b;
7262 #if GTK_CHECK_VERSION(2, 20, 0)
7263 t->spinner = gtk_spinner_new ();
7264 #endif
7265 t->label = gtk_label_new(title);
7266 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7267 gtk_widget_set_size_request(t->label, 100, 0);
7268 gtk_widget_set_size_request(b, 130, 0);
7270 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7271 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7272 #if GTK_CHECK_VERSION(2, 20, 0)
7273 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7274 #endif
7276 /* toolbar */
7277 if (browser_mode == XT_BM_KIOSK)
7278 t->toolbar = create_kiosk_toolbar(t);
7279 else
7280 t->toolbar = create_toolbar(t);
7282 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7284 /* browser */
7285 t->browser_win = create_browser(t);
7286 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7288 /* oops message for user feedback */
7289 t->oops = gtk_entry_new();
7290 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7291 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7292 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7293 gdk_color_parse(XT_COLOR_RED, &color);
7294 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7295 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7297 /* command entry */
7298 t->cmd = gtk_entry_new();
7299 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7300 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7301 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7303 /* status bar */
7304 t->statusbar = gtk_entry_new();
7305 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7306 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7307 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7308 gdk_color_parse(XT_COLOR_BLACK, &color);
7309 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7310 gdk_color_parse(XT_COLOR_WHITE, &color);
7311 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7312 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7314 /* xtp meaning is normal by default */
7315 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7317 /* set empty favicon */
7318 xt_icon_from_name(t, "text-html");
7320 /* and show it all */
7321 gtk_widget_show_all(b);
7322 gtk_widget_show_all(t->vbox);
7324 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7325 append_tab(t);
7326 else {
7327 notfound = 1;
7328 id = gtk_notebook_get_current_page(notebook);
7329 TAILQ_FOREACH(tt, &tabs, entry) {
7330 if (tt->tab_id == id) {
7331 notfound = 0;
7332 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7333 gtk_notebook_insert_page(notebook, t->vbox, b,
7334 id + 1);
7335 recalc_tabs();
7336 break;
7339 if (notfound)
7340 append_tab(t);
7343 #if GTK_CHECK_VERSION(2, 20, 0)
7344 /* turn spinner off if we are a new tab without uri */
7345 if (!load) {
7346 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7347 gtk_widget_hide(t->spinner);
7349 #endif
7350 /* make notebook tabs reorderable */
7351 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7353 g_object_connect(G_OBJECT(t->cmd),
7354 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7355 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7356 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7357 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7358 (char *)NULL);
7360 /* reuse wv_button_cb to hide oops */
7361 g_object_connect(G_OBJECT(t->oops),
7362 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7363 (char *)NULL);
7365 g_object_connect(G_OBJECT(t->wv),
7366 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7367 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7368 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7369 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7370 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7371 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7372 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7373 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7374 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7375 "signal::event", G_CALLBACK(webview_event_cb), t,
7376 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7377 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7378 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7379 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7380 #endif
7381 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7382 (char *)NULL);
7383 g_signal_connect(t->wv,
7384 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7385 g_signal_connect(t->wv,
7386 "notify::title", G_CALLBACK(notify_title_cb), t);
7388 /* hijack the unused keys as if we were the browser */
7389 g_object_connect(G_OBJECT(t->toolbar),
7390 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7391 (char *)NULL);
7393 g_signal_connect(G_OBJECT(bb), "button_press_event",
7394 G_CALLBACK(tab_close_cb), t);
7396 /* hide stuff */
7397 hide_cmd(t);
7398 hide_oops(t);
7399 url_set_visibility();
7400 statusbar_set_visibility();
7402 if (focus) {
7403 gtk_notebook_set_current_page(notebook, t->tab_id);
7404 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7405 t->tab_id);
7408 if (load) {
7409 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7410 load_uri(t, title);
7411 } else {
7412 if (show_url == 1)
7413 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7414 else
7415 focus_webview(t);
7418 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7419 /* restore the tab's history */
7420 if (u && u->history) {
7421 items = u->history;
7422 while (items) {
7423 item = items->data;
7424 webkit_web_back_forward_list_add_item(t->bfl, item);
7425 items = g_list_next(items);
7428 item = g_list_nth_data(u->history, u->back);
7429 if (item)
7430 webkit_web_view_go_to_back_forward_item(t->wv, item);
7432 g_list_free(items);
7433 g_list_free(u->history);
7434 } else
7435 webkit_web_back_forward_list_clear(t->bfl);
7437 return (t);
7440 void
7441 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7442 gpointer *udata)
7444 struct tab *t;
7445 const gchar *uri;
7447 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7449 TAILQ_FOREACH(t, &tabs, entry) {
7450 if (t->tab_id == pn) {
7451 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7452 "%d\n", pn);
7454 uri = webkit_web_view_get_title(t->wv);
7455 if (uri == NULL)
7456 uri = XT_NAME;
7457 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7459 hide_cmd(t);
7460 hide_oops(t);
7462 if (t->focus_wv)
7463 focus_webview(t);
7468 void
7469 menuitem_response(struct tab *t)
7471 gtk_notebook_set_current_page(notebook, t->tab_id);
7474 gboolean
7475 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7477 GtkWidget *menu, *menu_items;
7478 GdkEventButton *bevent;
7479 const gchar *uri;
7480 struct tab *ti;
7482 if (event->type == GDK_BUTTON_PRESS) {
7483 bevent = (GdkEventButton *) event;
7484 menu = gtk_menu_new();
7486 TAILQ_FOREACH(ti, &tabs, entry) {
7487 if ((uri = get_uri(ti->wv)) == NULL)
7488 /* XXX make sure there is something to print */
7489 /* XXX add gui pages in here to look purdy */
7490 uri = "(untitled)";
7491 menu_items = gtk_menu_item_new_with_label(uri);
7492 gtk_menu_append(GTK_MENU (menu), menu_items);
7493 gtk_widget_show(menu_items);
7495 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7496 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7497 (gpointer)ti);
7500 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7501 bevent->button, bevent->time);
7503 /* unref object so it'll free itself when popped down */
7504 g_object_ref_sink(menu);
7505 g_object_unref(menu);
7507 return (TRUE /* eat event */);
7510 return (FALSE /* propagate */);
7514 icon_size_map(int icon_size)
7516 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7517 icon_size > GTK_ICON_SIZE_DIALOG)
7518 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7520 return (icon_size);
7523 GtkWidget *
7524 create_button(char *name, char *stockid, int size)
7526 GtkWidget *button, *image;
7527 gchar *rcstring;
7528 int gtk_icon_size;
7530 rcstring = g_strdup_printf(
7531 "style \"%s-style\"\n"
7532 "{\n"
7533 " GtkWidget::focus-padding = 0\n"
7534 " GtkWidget::focus-line-width = 0\n"
7535 " xthickness = 0\n"
7536 " ythickness = 0\n"
7537 "}\n"
7538 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7539 gtk_rc_parse_string(rcstring);
7540 g_free(rcstring);
7541 button = gtk_button_new();
7542 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7543 gtk_icon_size = icon_size_map(size ? size : icon_size);
7545 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7546 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7547 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7548 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7549 gtk_widget_set_name(button, name);
7550 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7552 return (button);
7555 void
7556 button_set_stockid(GtkWidget *button, char *stockid)
7558 GtkWidget *image;
7560 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7561 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7562 gtk_button_set_image(GTK_BUTTON(button), image);
7565 void
7566 create_canvas(void)
7568 GtkWidget *vbox;
7569 GList *l = NULL;
7570 GdkPixbuf *pb;
7571 char file[PATH_MAX];
7572 int i;
7574 vbox = gtk_vbox_new(FALSE, 0);
7575 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7576 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7577 gtk_notebook_set_tab_hborder(notebook, 0);
7578 gtk_notebook_set_tab_vborder(notebook, 0);
7579 gtk_notebook_set_scrollable(notebook, TRUE);
7580 notebook_tab_set_visibility(notebook);
7581 gtk_notebook_set_show_border(notebook, FALSE);
7582 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7584 abtn = gtk_button_new();
7585 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7586 gtk_widget_set_size_request(arrow, -1, -1);
7587 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7588 gtk_widget_set_size_request(abtn, -1, 20);
7590 #if GTK_CHECK_VERSION(2, 20, 0)
7591 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7592 #endif
7593 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7594 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7595 gtk_widget_set_size_request(vbox, -1, -1);
7597 g_object_connect(G_OBJECT(notebook),
7598 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7599 (char *)NULL);
7600 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7601 G_CALLBACK(arrow_cb), NULL);
7603 main_window = create_window();
7604 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7605 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7607 /* icons */
7608 for (i = 0; i < LENGTH(icons); i++) {
7609 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7610 pb = gdk_pixbuf_new_from_file(file, NULL);
7611 l = g_list_append(l, pb);
7613 gtk_window_set_default_icon_list(l);
7615 gtk_widget_show_all(abtn);
7616 gtk_widget_show_all(main_window);
7619 void
7620 set_hook(void **hook, char *name)
7622 if (hook == NULL)
7623 errx(1, "set_hook");
7625 if (*hook == NULL) {
7626 *hook = dlsym(RTLD_NEXT, name);
7627 if (*hook == NULL)
7628 errx(1, "can't hook %s", name);
7632 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7633 gboolean
7634 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7636 g_return_val_if_fail(cookie1, FALSE);
7637 g_return_val_if_fail(cookie2, FALSE);
7639 return (!strcmp (cookie1->name, cookie2->name) &&
7640 !strcmp (cookie1->value, cookie2->value) &&
7641 !strcmp (cookie1->path, cookie2->path) &&
7642 !strcmp (cookie1->domain, cookie2->domain));
7645 void
7646 transfer_cookies(void)
7648 GSList *cf;
7649 SoupCookie *sc, *pc;
7651 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7653 for (;cf; cf = cf->next) {
7654 pc = cf->data;
7655 sc = soup_cookie_copy(pc);
7656 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7659 soup_cookies_free(cf);
7662 void
7663 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7665 GSList *cf;
7666 SoupCookie *ci;
7668 print_cookie("soup_cookie_jar_delete_cookie", c);
7670 if (cookies_enabled == 0)
7671 return;
7673 if (jar == NULL || c == NULL)
7674 return;
7676 /* find and remove from persistent jar */
7677 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7679 for (;cf; cf = cf->next) {
7680 ci = cf->data;
7681 if (soup_cookie_equal(ci, c)) {
7682 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7683 break;
7687 soup_cookies_free(cf);
7689 /* delete from session jar */
7690 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7693 void
7694 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7696 struct domain *d = NULL;
7697 SoupCookie *c;
7698 FILE *r_cookie_f;
7700 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7701 jar, p_cookiejar, s_cookiejar);
7703 if (cookies_enabled == 0)
7704 return;
7706 /* see if we are up and running */
7707 if (p_cookiejar == NULL) {
7708 _soup_cookie_jar_add_cookie(jar, cookie);
7709 return;
7711 /* disallow p_cookiejar adds, shouldn't happen */
7712 if (jar == p_cookiejar)
7713 return;
7715 /* sanity */
7716 if (jar == NULL || cookie == NULL)
7717 return;
7719 if (enable_cookie_whitelist &&
7720 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7721 blocked_cookies++;
7722 DNPRINTF(XT_D_COOKIE,
7723 "soup_cookie_jar_add_cookie: reject %s\n",
7724 cookie->domain);
7725 if (save_rejected_cookies) {
7726 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7727 show_oops_s("can't open reject cookie file");
7728 return;
7730 fseek(r_cookie_f, 0, SEEK_END);
7731 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7732 cookie->http_only ? "#HttpOnly_" : "",
7733 cookie->domain,
7734 *cookie->domain == '.' ? "TRUE" : "FALSE",
7735 cookie->path,
7736 cookie->secure ? "TRUE" : "FALSE",
7737 cookie->expires ?
7738 (gulong)soup_date_to_time_t(cookie->expires) :
7740 cookie->name,
7741 cookie->value);
7742 fflush(r_cookie_f);
7743 fclose(r_cookie_f);
7745 if (!allow_volatile_cookies)
7746 return;
7749 if (cookie->expires == NULL && session_timeout) {
7750 soup_cookie_set_expires(cookie,
7751 soup_date_new_from_now(session_timeout));
7752 print_cookie("modified add cookie", cookie);
7755 /* see if we are white listed for persistence */
7756 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7757 /* add to persistent jar */
7758 c = soup_cookie_copy(cookie);
7759 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7760 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7763 /* add to session jar */
7764 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7765 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7768 void
7769 setup_cookies(void)
7771 char file[PATH_MAX];
7773 set_hook((void *)&_soup_cookie_jar_add_cookie,
7774 "soup_cookie_jar_add_cookie");
7775 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7776 "soup_cookie_jar_delete_cookie");
7778 if (cookies_enabled == 0)
7779 return;
7782 * the following code is intricate due to overriding several libsoup
7783 * functions.
7784 * do not alter order of these operations.
7787 /* rejected cookies */
7788 if (save_rejected_cookies)
7789 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7791 /* persistent cookies */
7792 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7793 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7795 /* session cookies */
7796 s_cookiejar = soup_cookie_jar_new();
7797 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7798 cookie_policy, (void *)NULL);
7799 transfer_cookies();
7801 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7804 void
7805 setup_proxy(char *uri)
7807 if (proxy_uri) {
7808 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7809 soup_uri_free(proxy_uri);
7810 proxy_uri = NULL;
7812 if (http_proxy) {
7813 if (http_proxy != uri) {
7814 g_free(http_proxy);
7815 http_proxy = NULL;
7819 if (uri) {
7820 http_proxy = g_strdup(uri);
7821 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7822 proxy_uri = soup_uri_new(http_proxy);
7823 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7828 send_cmd_to_socket(char *cmd)
7830 int s, len, rv = 1;
7831 struct sockaddr_un sa;
7833 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7834 warnx("%s: socket", __func__);
7835 return (rv);
7838 sa.sun_family = AF_UNIX;
7839 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7840 work_dir, XT_SOCKET_FILE);
7841 len = SUN_LEN(&sa);
7843 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7844 warnx("%s: connect", __func__);
7845 goto done;
7848 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7849 warnx("%s: send", __func__);
7850 goto done;
7853 rv = 0;
7854 done:
7855 close(s);
7856 return (rv);
7859 void
7860 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7862 int s, n;
7863 char str[XT_MAX_URL_LENGTH];
7864 socklen_t t = sizeof(struct sockaddr_un);
7865 struct sockaddr_un sa;
7866 struct passwd *p;
7867 uid_t uid;
7868 gid_t gid;
7869 struct tab *tt;
7871 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7872 warn("accept");
7873 return;
7876 if (getpeereid(s, &uid, &gid) == -1) {
7877 warn("getpeereid");
7878 return;
7880 if (uid != getuid() || gid != getgid()) {
7881 warnx("unauthorized user");
7882 return;
7885 p = getpwuid(uid);
7886 if (p == NULL) {
7887 warnx("not a valid user");
7888 return;
7891 n = recv(s, str, sizeof(str), 0);
7892 if (n <= 0)
7893 return;
7895 tt = TAILQ_LAST(&tabs, tab_list);
7896 cmd_execute(tt, str);
7900 is_running(void)
7902 int s, len, rv = 1;
7903 struct sockaddr_un sa;
7905 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7906 warn("is_running: socket");
7907 return (-1);
7910 sa.sun_family = AF_UNIX;
7911 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7912 work_dir, XT_SOCKET_FILE);
7913 len = SUN_LEN(&sa);
7915 /* connect to see if there is a listener */
7916 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7917 rv = 0; /* not running */
7918 else
7919 rv = 1; /* already running */
7921 close(s);
7923 return (rv);
7927 build_socket(void)
7929 int s, len;
7930 struct sockaddr_un sa;
7932 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7933 warn("build_socket: socket");
7934 return (-1);
7937 sa.sun_family = AF_UNIX;
7938 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7939 work_dir, XT_SOCKET_FILE);
7940 len = SUN_LEN(&sa);
7942 /* connect to see if there is a listener */
7943 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7944 /* no listener so we will */
7945 unlink(sa.sun_path);
7947 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7948 warn("build_socket: bind");
7949 goto done;
7952 if (listen(s, 1) == -1) {
7953 warn("build_socket: listen");
7954 goto done;
7957 return (s);
7960 done:
7961 close(s);
7962 return (-1);
7965 static gboolean
7966 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7967 GtkTreeIter *iter, struct tab *t)
7969 gchar *value;
7971 gtk_tree_model_get(model, iter, 0, &value, -1);
7972 load_uri(t, value);
7974 return (FALSE);
7977 void
7978 completion_add_uri(const gchar *uri)
7980 GtkTreeIter iter;
7982 /* add uri to list_store */
7983 gtk_list_store_append(completion_model, &iter);
7984 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
7987 gboolean
7988 completion_match(GtkEntryCompletion *completion, const gchar *key,
7989 GtkTreeIter *iter, gpointer user_data)
7991 gchar *value;
7992 gboolean match = FALSE;
7994 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
7995 -1);
7997 if (value == NULL)
7998 return FALSE;
8000 match = match_uri(value, key);
8002 g_free(value);
8003 return (match);
8006 void
8007 completion_add(struct tab *t)
8009 /* enable completion for tab */
8010 t->completion = gtk_entry_completion_new();
8011 gtk_entry_completion_set_text_column(t->completion, 0);
8012 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8013 gtk_entry_completion_set_model(t->completion,
8014 GTK_TREE_MODEL(completion_model));
8015 gtk_entry_completion_set_match_func(t->completion, completion_match,
8016 NULL, NULL);
8017 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8018 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8019 G_CALLBACK(completion_select_cb), t);
8022 void
8023 usage(void)
8025 fprintf(stderr,
8026 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8027 exit(0);
8031 main(int argc, char *argv[])
8033 struct stat sb;
8034 int c, s, optn = 0, opte = 0, focus = 1;
8035 char conf[PATH_MAX] = { '\0' };
8036 char file[PATH_MAX];
8037 char *env_proxy = NULL;
8038 FILE *f = NULL;
8039 struct karg a;
8040 struct sigaction sact;
8041 gchar *priority = g_strdup("NORMAL");
8043 start_argv = argv;
8045 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8047 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8048 switch (c) {
8049 case 'S':
8050 show_url = 0;
8051 break;
8052 case 'T':
8053 show_tabs = 0;
8054 break;
8055 case 'V':
8056 errx(0 , "Version: %s", version);
8057 break;
8058 case 'f':
8059 strlcpy(conf, optarg, sizeof(conf));
8060 break;
8061 case 's':
8062 strlcpy(named_session, optarg, sizeof(named_session));
8063 break;
8064 case 't':
8065 tabless = 1;
8066 break;
8067 case 'n':
8068 optn = 1;
8069 break;
8070 case 'e':
8071 opte = 1;
8072 break;
8073 default:
8074 usage();
8075 /* NOTREACHED */
8078 argc -= optind;
8079 argv += optind;
8081 RB_INIT(&hl);
8082 RB_INIT(&js_wl);
8083 RB_INIT(&downloads);
8085 TAILQ_INIT(&tabs);
8086 TAILQ_INIT(&mtl);
8087 TAILQ_INIT(&aliases);
8088 TAILQ_INIT(&undos);
8089 TAILQ_INIT(&kbl);
8091 init_keybindings();
8093 gnutls_global_init();
8095 /* generate session keys for xtp pages */
8096 generate_xtp_session_key(&dl_session_key);
8097 generate_xtp_session_key(&hl_session_key);
8098 generate_xtp_session_key(&cl_session_key);
8099 generate_xtp_session_key(&fl_session_key);
8101 /* prepare gtk */
8102 gtk_init(&argc, &argv);
8103 if (!g_thread_supported())
8104 g_thread_init(NULL);
8106 /* signals */
8107 bzero(&sact, sizeof(sact));
8108 sigemptyset(&sact.sa_mask);
8109 sact.sa_handler = sigchild;
8110 sact.sa_flags = SA_NOCLDSTOP;
8111 sigaction(SIGCHLD, &sact, NULL);
8113 /* set download dir */
8114 pwd = getpwuid(getuid());
8115 if (pwd == NULL)
8116 errx(1, "invalid user %d", getuid());
8117 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8119 /* set default string settings */
8120 home = g_strdup("http://www.peereboom.us");
8121 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8122 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8123 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8125 /* read config file */
8126 if (strlen(conf) == 0)
8127 snprintf(conf, sizeof conf, "%s/.%s",
8128 pwd->pw_dir, XT_CONF_FILE);
8129 config_parse(conf, 0);
8131 /* working directory */
8132 if (strlen(work_dir) == 0)
8133 snprintf(work_dir, sizeof work_dir, "%s/%s",
8134 pwd->pw_dir, XT_DIR);
8135 if (stat(work_dir, &sb)) {
8136 if (mkdir(work_dir, S_IRWXU) == -1)
8137 err(1, "mkdir work_dir");
8138 if (stat(work_dir, &sb))
8139 err(1, "stat work_dir");
8141 if (S_ISDIR(sb.st_mode) == 0)
8142 errx(1, "%s not a dir", work_dir);
8143 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8144 warnx("fixing invalid permissions on %s", work_dir);
8145 if (chmod(work_dir, S_IRWXU) == -1)
8146 err(1, "chmod");
8149 /* icon cache dir */
8150 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8151 if (stat(cache_dir, &sb)) {
8152 if (mkdir(cache_dir, S_IRWXU) == -1)
8153 err(1, "mkdir cache_dir");
8154 if (stat(cache_dir, &sb))
8155 err(1, "stat cache_dir");
8157 if (S_ISDIR(sb.st_mode) == 0)
8158 errx(1, "%s not a dir", cache_dir);
8159 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8160 warnx("fixing invalid permissions on %s", cache_dir);
8161 if (chmod(cache_dir, S_IRWXU) == -1)
8162 err(1, "chmod");
8165 /* certs dir */
8166 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8167 if (stat(certs_dir, &sb)) {
8168 if (mkdir(certs_dir, S_IRWXU) == -1)
8169 err(1, "mkdir certs_dir");
8170 if (stat(certs_dir, &sb))
8171 err(1, "stat certs_dir");
8173 if (S_ISDIR(sb.st_mode) == 0)
8174 errx(1, "%s not a dir", certs_dir);
8175 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8176 warnx("fixing invalid permissions on %s", certs_dir);
8177 if (chmod(certs_dir, S_IRWXU) == -1)
8178 err(1, "chmod");
8181 /* sessions dir */
8182 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8183 work_dir, XT_SESSIONS_DIR);
8184 if (stat(sessions_dir, &sb)) {
8185 if (mkdir(sessions_dir, S_IRWXU) == -1)
8186 err(1, "mkdir sessions_dir");
8187 if (stat(sessions_dir, &sb))
8188 err(1, "stat sessions_dir");
8190 if (S_ISDIR(sb.st_mode) == 0)
8191 errx(1, "%s not a dir", sessions_dir);
8192 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8193 warnx("fixing invalid permissions on %s", sessions_dir);
8194 if (chmod(sessions_dir, S_IRWXU) == -1)
8195 err(1, "chmod");
8197 /* runtime settings that can override config file */
8198 if (runtime_settings[0] != '\0')
8199 config_parse(runtime_settings, 1);
8201 /* download dir */
8202 if (!strcmp(download_dir, pwd->pw_dir))
8203 strlcat(download_dir, "/downloads", sizeof download_dir);
8204 if (stat(download_dir, &sb)) {
8205 if (mkdir(download_dir, S_IRWXU) == -1)
8206 err(1, "mkdir download_dir");
8207 if (stat(download_dir, &sb))
8208 err(1, "stat download_dir");
8210 if (S_ISDIR(sb.st_mode) == 0)
8211 errx(1, "%s not a dir", download_dir);
8212 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8213 warnx("fixing invalid permissions on %s", download_dir);
8214 if (chmod(download_dir, S_IRWXU) == -1)
8215 err(1, "chmod");
8218 /* favorites file */
8219 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8220 if (stat(file, &sb)) {
8221 warnx("favorites file doesn't exist, creating it");
8222 if ((f = fopen(file, "w")) == NULL)
8223 err(1, "favorites");
8224 fclose(f);
8227 /* cookies */
8228 session = webkit_get_default_session();
8229 /* XXX ssl-priority property not quite available yet */
8230 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8231 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8232 (char *)NULL);
8233 else
8234 warnx("session does not have \"ssl-priority\" property");
8235 setup_cookies();
8237 /* certs */
8238 if (ssl_ca_file) {
8239 if (stat(ssl_ca_file, &sb)) {
8240 warn("no CA file: %s", ssl_ca_file);
8241 g_free(ssl_ca_file);
8242 ssl_ca_file = NULL;
8243 } else
8244 g_object_set(session,
8245 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8246 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8247 (void *)NULL);
8250 /* proxy */
8251 env_proxy = getenv("http_proxy");
8252 if (env_proxy)
8253 setup_proxy(env_proxy);
8254 else
8255 setup_proxy(http_proxy);
8257 if (opte) {
8258 send_cmd_to_socket(argv[0]);
8259 exit(0);
8262 /* set some connection parameters */
8263 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8264 g_object_set(session, "max-conns-per-host", max_host_connections,
8265 (char *)NULL);
8267 /* see if there is already an xxxterm running */
8268 if (single_instance && is_running()) {
8269 optn = 1;
8270 warnx("already running");
8273 char *cmd = NULL;
8274 if (optn) {
8275 while (argc) {
8276 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8277 send_cmd_to_socket(cmd);
8278 if (cmd)
8279 g_free(cmd);
8281 argc--;
8282 argv++;
8284 exit(0);
8287 /* uri completion */
8288 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8290 /* go graphical */
8291 create_canvas();
8293 if (save_global_history)
8294 restore_global_history();
8296 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8297 restore_saved_tabs();
8298 else {
8299 a.s = named_session;
8300 a.i = XT_SES_DONOTHING;
8301 open_tabs(NULL, &a);
8304 while (argc) {
8305 create_new_tab(argv[0], NULL, focus);
8306 focus = 0;
8308 argc--;
8309 argv++;
8312 if (TAILQ_EMPTY(&tabs))
8313 create_new_tab(home, NULL, 1);
8315 if (enable_socket)
8316 if ((s = build_socket()) != -1)
8317 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8319 gtk_main();
8321 gnutls_global_deinit();
8323 return (0);