move cookiejar below cookie command so that autocompletion works as
[xxxterm.git] / xxxterm.c
blob8e08220005bc0a7f2ffeff9fcf291e1e39ff2fe8
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_INVALID 0 /* invalid */
334 #define XT_XTP_DL 1 /* downloads */
335 #define XT_XTP_HL 2 /* history */
336 #define XT_XTP_CL 3 /* cookies */
337 #define XT_XTP_FL 4 /* favorites */
339 /* XTP download actions */
340 #define XT_XTP_DL_LIST 1
341 #define XT_XTP_DL_CANCEL 2
342 #define XT_XTP_DL_REMOVE 3
344 /* XTP history actions */
345 #define XT_XTP_HL_LIST 1
346 #define XT_XTP_HL_REMOVE 2
348 /* XTP cookie actions */
349 #define XT_XTP_CL_LIST 1
350 #define XT_XTP_CL_REMOVE 2
352 /* XTP cookie actions */
353 #define XT_XTP_FL_LIST 1
354 #define XT_XTP_FL_REMOVE 2
356 /* xtp tab meanings - identifies which tabs have xtp pages in */
357 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
358 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
359 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
360 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
361 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
363 /* actions */
364 #define XT_MOVE_INVALID (0)
365 #define XT_MOVE_DOWN (1)
366 #define XT_MOVE_UP (2)
367 #define XT_MOVE_BOTTOM (3)
368 #define XT_MOVE_TOP (4)
369 #define XT_MOVE_PAGEDOWN (5)
370 #define XT_MOVE_PAGEUP (6)
371 #define XT_MOVE_HALFDOWN (7)
372 #define XT_MOVE_HALFUP (8)
373 #define XT_MOVE_LEFT (9)
374 #define XT_MOVE_FARLEFT (10)
375 #define XT_MOVE_RIGHT (11)
376 #define XT_MOVE_FARRIGHT (12)
378 #define XT_TAB_LAST (-4)
379 #define XT_TAB_FIRST (-3)
380 #define XT_TAB_PREV (-2)
381 #define XT_TAB_NEXT (-1)
382 #define XT_TAB_INVALID (0)
383 #define XT_TAB_NEW (1)
384 #define XT_TAB_DELETE (2)
385 #define XT_TAB_DELQUIT (3)
386 #define XT_TAB_OPEN (4)
387 #define XT_TAB_UNDO_CLOSE (5)
388 #define XT_TAB_SHOW (6)
389 #define XT_TAB_HIDE (7)
391 #define XT_NAV_INVALID (0)
392 #define XT_NAV_BACK (1)
393 #define XT_NAV_FORWARD (2)
394 #define XT_NAV_RELOAD (3)
395 #define XT_NAV_RELOAD_CACHE (4)
397 #define XT_FOCUS_INVALID (0)
398 #define XT_FOCUS_URI (1)
399 #define XT_FOCUS_SEARCH (2)
401 #define XT_SEARCH_INVALID (0)
402 #define XT_SEARCH_NEXT (1)
403 #define XT_SEARCH_PREV (2)
405 #define XT_PASTE_CURRENT_TAB (0)
406 #define XT_PASTE_NEW_TAB (1)
408 #define XT_FONT_SET (0)
410 #define XT_URL_SHOW (1)
411 #define XT_URL_HIDE (2)
413 #define XT_STATUSBAR_SHOW (1)
414 #define XT_STATUSBAR_HIDE (2)
416 #define XT_WL_TOGGLE (1<<0)
417 #define XT_WL_ENABLE (1<<1)
418 #define XT_WL_DISABLE (1<<2)
419 #define XT_WL_FQDN (1<<3) /* default */
420 #define XT_WL_TOPLEVEL (1<<4)
421 #define XT_WL_PERSISTENT (1<<5)
422 #define XT_WL_SESSION (1<<6)
424 #define XT_SHOW (1<<7)
425 #define XT_DELETE (1<<8)
426 #define XT_SAVE (1<<9)
427 #define XT_OPEN (1<<10)
429 #define XT_CMD_OPEN (0)
430 #define XT_CMD_OPEN_CURRENT (1)
431 #define XT_CMD_TABNEW (2)
432 #define XT_CMD_TABNEW_CURRENT (3)
434 #define XT_STATUS_NOTHING (0)
435 #define XT_STATUS_LINK (1)
436 #define XT_STATUS_URI (2)
437 #define XT_STATUS_LOADING (3)
439 #define XT_SES_DONOTHING (0)
440 #define XT_SES_CLOSETABS (1)
442 #define XT_BM_NORMAL (0)
443 #define XT_BM_WHITELIST (1)
444 #define XT_BM_KIOSK (2)
446 /* mime types */
447 struct mime_type {
448 char *mt_type;
449 char *mt_action;
450 int mt_default;
451 int mt_download;
452 TAILQ_ENTRY(mime_type) entry;
454 TAILQ_HEAD(mime_type_list, mime_type);
456 /* uri aliases */
457 struct alias {
458 char *a_name;
459 char *a_uri;
460 TAILQ_ENTRY(alias) entry;
462 TAILQ_HEAD(alias_list, alias);
464 /* settings that require restart */
465 int tabless = 0; /* allow only 1 tab */
466 int enable_socket = 0;
467 int single_instance = 0; /* only allow one xxxterm to run */
468 int fancy_bar = 1; /* fancy toolbar */
469 int browser_mode = XT_BM_NORMAL;
471 /* runtime settings */
472 int show_tabs = 1; /* show tabs on notebook */
473 int show_url = 1; /* show url toolbar on notebook */
474 int show_statusbar = 0; /* vimperator style status bar */
475 int ctrl_click_focus = 0; /* ctrl click gets focus */
476 int cookies_enabled = 1; /* enable cookies */
477 int read_only_cookies = 0; /* enable to not write cookies */
478 int enable_scripts = 1;
479 int enable_plugins = 0;
480 int default_font_size = 12;
481 gfloat default_zoom_level = 1.0;
482 int window_height = 768;
483 int window_width = 1024;
484 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
485 unsigned refresh_interval = 10; /* download refresh interval */
486 int enable_cookie_whitelist = 0;
487 int enable_js_whitelist = 0;
488 time_t session_timeout = 3600; /* cookie session timeout */
489 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
490 char *ssl_ca_file = NULL;
491 char *resource_dir = NULL;
492 gboolean ssl_strict_certs = FALSE;
493 int append_next = 1; /* append tab after current tab */
494 char *home = NULL;
495 char *search_string = NULL;
496 char *http_proxy = NULL;
497 char download_dir[PATH_MAX];
498 char runtime_settings[PATH_MAX]; /* override of settings */
499 int allow_volatile_cookies = 0;
500 int save_global_history = 0; /* save global history to disk */
501 char *user_agent = NULL;
502 int save_rejected_cookies = 0;
503 time_t session_autosave = 0;
504 int guess_search = 0;
505 int dns_prefetch = FALSE;
506 gint max_connections = 25;
507 gint max_host_connections = 5;
509 struct settings;
510 struct key_binding;
511 int set_download_dir(struct settings *, char *);
512 int set_work_dir(struct settings *, char *);
513 int set_runtime_dir(struct settings *, char *);
514 int set_browser_mode(struct settings *, char *);
515 int set_cookie_policy(struct settings *, char *);
516 int add_alias(struct settings *, char *);
517 int add_mime_type(struct settings *, char *);
518 int add_cookie_wl(struct settings *, char *);
519 int add_js_wl(struct settings *, char *);
520 int add_kb(struct settings *, char *);
521 void button_set_stockid(GtkWidget *, char *);
522 GtkWidget * create_button(char *, char *, int);
524 char *get_browser_mode(struct settings *);
525 char *get_cookie_policy(struct settings *);
527 char *get_download_dir(struct settings *);
528 char *get_work_dir(struct settings *);
529 char *get_runtime_dir(struct settings *);
531 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
532 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
533 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
534 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
535 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
537 struct special {
538 int (*set)(struct settings *, char *);
539 char *(*get)(struct settings *);
540 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
543 struct special s_browser_mode = {
544 set_browser_mode,
545 get_browser_mode,
546 NULL
549 struct special s_cookie = {
550 set_cookie_policy,
551 get_cookie_policy,
552 NULL
555 struct special s_alias = {
556 add_alias,
557 NULL,
558 walk_alias
561 struct special s_mime = {
562 add_mime_type,
563 NULL,
564 walk_mime_type
567 struct special s_js = {
568 add_js_wl,
569 NULL,
570 walk_js_wl
573 struct special s_kb = {
574 add_kb,
575 NULL,
576 walk_kb
579 struct special s_cookie_wl = {
580 add_cookie_wl,
581 NULL,
582 walk_cookie_wl
585 struct special s_download_dir = {
586 set_download_dir,
587 get_download_dir,
588 NULL
591 struct special s_work_dir = {
592 set_work_dir,
593 get_work_dir,
594 NULL
597 struct settings {
598 char *name;
599 int type;
600 #define XT_S_INVALID (0)
601 #define XT_S_INT (1)
602 #define XT_S_STR (2)
603 #define XT_S_FLOAT (3)
604 uint32_t flags;
605 #define XT_SF_RESTART (1<<0)
606 #define XT_SF_RUNTIME (1<<1)
607 int *ival;
608 char **sval;
609 struct special *s;
610 gfloat *fval;
611 } rs[] = {
612 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
613 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
614 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
615 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
616 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
617 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
618 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
619 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
620 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
621 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
622 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
623 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
624 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
625 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
626 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
627 { "home", XT_S_STR, 0, NULL, &home, NULL },
628 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
629 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
630 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
631 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
632 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
633 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
634 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
635 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
636 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
637 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
638 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
639 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
640 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
641 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
642 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
643 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
644 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
645 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
646 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
647 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
648 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
649 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
650 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
652 /* runtime settings */
653 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
654 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
655 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
656 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
657 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
660 int about(struct tab *, struct karg *);
661 int blank(struct tab *, struct karg *);
662 int cookie_show_wl(struct tab *, struct karg *);
663 int js_show_wl(struct tab *, struct karg *);
664 int help(struct tab *, struct karg *);
665 int set(struct tab *, struct karg *);
666 int stats(struct tab *, struct karg *);
667 int marco(struct tab *, struct karg *);
668 const char * marco_message(int *);
669 int xtp_page_cl(struct tab *, struct karg *);
670 int xtp_page_dl(struct tab *, struct karg *);
671 int xtp_page_fl(struct tab *, struct karg *);
672 int xtp_page_hl(struct tab *, struct karg *);
674 #define XT_URI_ABOUT ("about:")
675 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
676 #define XT_URI_ABOUT_ABOUT ("about")
677 #define XT_URI_ABOUT_BLANK ("blank")
678 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
679 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
680 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
681 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
682 #define XT_URI_ABOUT_FAVORITES ("favorites")
683 #define XT_URI_ABOUT_HELP ("help")
684 #define XT_URI_ABOUT_HISTORY ("history")
685 #define XT_URI_ABOUT_JSWL ("jswl")
686 #define XT_URI_ABOUT_SET ("set")
687 #define XT_URI_ABOUT_STATS ("stats")
688 #define XT_URI_ABOUT_MARCO ("marco")
690 struct about_type {
691 char *name;
692 int (*func)(struct tab *, struct karg *);
693 } about_list[] = {
694 { XT_URI_ABOUT_ABOUT, about },
695 { XT_URI_ABOUT_BLANK, blank },
696 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
697 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
698 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
699 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
700 { XT_URI_ABOUT_HELP, help },
701 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
702 { XT_URI_ABOUT_JSWL, js_show_wl },
703 { XT_URI_ABOUT_SET, set },
704 { XT_URI_ABOUT_STATS, stats },
705 { XT_URI_ABOUT_MARCO, marco },
708 /* globals */
709 extern char *__progname;
710 char **start_argv;
711 struct passwd *pwd;
712 GtkWidget *main_window;
713 GtkNotebook *notebook;
714 GtkWidget *arrow, *abtn;
715 struct tab_list tabs;
716 struct history_list hl;
717 struct download_list downloads;
718 struct domain_list c_wl;
719 struct domain_list js_wl;
720 struct undo_tailq undos;
721 struct keybinding_list kbl;
722 int undo_count;
723 int updating_dl_tabs = 0;
724 int updating_hl_tabs = 0;
725 int updating_cl_tabs = 0;
726 int updating_fl_tabs = 0;
727 char *global_search;
728 uint64_t blocked_cookies = 0;
729 char named_session[PATH_MAX];
730 void update_favicon(struct tab *);
731 int icon_size_map(int);
733 GtkListStore *completion_model;
734 void completion_add(struct tab *);
735 void completion_add_uri(const gchar *);
737 void
738 sigchild(int sig)
740 int saved_errno, status;
741 pid_t pid;
743 saved_errno = errno;
745 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
746 if (pid == -1) {
747 if (errno == EINTR)
748 continue;
749 if (errno != ECHILD) {
751 clog_warn("sigchild: waitpid:");
754 break;
757 if (WIFEXITED(status)) {
758 if (WEXITSTATUS(status) != 0) {
760 clog_warnx("sigchild: child exit status: %d",
761 WEXITSTATUS(status));
764 } else {
766 clog_warnx("sigchild: child is terminated abnormally");
771 errno = saved_errno;
775 is_g_object_setting(GObject *o, char *str)
777 guint n_props = 0, i;
778 GParamSpec **proplist;
780 if (! G_IS_OBJECT(o))
781 return (0);
783 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
784 &n_props);
786 for (i=0; i < n_props; i++) {
787 if (! strcmp(proplist[i]->name, str))
788 return (1);
790 return (0);
794 * Display a web page from a HTML string in memory, rather than from a URL
796 void
797 load_webkit_string(struct tab *t, const char *str, gchar *title)
799 gchar *uri;
800 char file[PATH_MAX];
801 GdkPixbuf *pb;
803 /* we set this to indicate we want to manually do navaction */
804 if (t->bfl)
805 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
807 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
808 #if GTK_CHECK_VERSION(2, 20, 0)
809 gtk_spinner_stop(GTK_SPINNER(t->spinner));
810 gtk_widget_hide(t->spinner);
811 #endif
813 if (title) {
814 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
815 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
816 g_free(uri);
818 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
819 pb = gdk_pixbuf_new_from_file(file, NULL);
820 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
821 GTK_ENTRY_ICON_PRIMARY, pb);
822 gdk_pixbuf_unref(pb);
826 void
827 set_status(struct tab *t, gchar *s, int status)
829 gchar *type = NULL;
831 if (s == NULL)
832 return;
834 switch (status) {
835 case XT_STATUS_LOADING:
836 type = g_strdup_printf("Loading: %s", s);
837 s = type;
838 break;
839 case XT_STATUS_LINK:
840 type = g_strdup_printf("Link: %s", s);
841 if (!t->status)
842 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
843 s = type;
844 break;
845 case XT_STATUS_URI:
846 type = g_strdup_printf("%s", s);
847 if (!t->status) {
848 t->status = g_strdup(type);
850 s = type;
851 if (!t->status)
852 t->status = g_strdup(s);
853 break;
854 case XT_STATUS_NOTHING:
855 /* FALL THROUGH */
856 default:
857 break;
859 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
860 if (type)
861 g_free(type);
864 void
865 hide_oops(struct tab *t)
867 gtk_widget_hide(t->oops);
870 void
871 hide_cmd(struct tab *t)
873 gtk_widget_hide(t->cmd);
876 void
877 show_cmd(struct tab *t)
879 gtk_widget_hide(t->oops);
880 gtk_widget_show(t->cmd);
883 void
884 show_oops(struct tab *t, const char *fmt, ...)
886 va_list ap;
887 char *msg;
889 if (fmt == NULL)
890 return;
892 va_start(ap, fmt);
893 if (vasprintf(&msg, fmt, ap) == -1)
894 errx(1, "show_oops failed");
895 va_end(ap);
897 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
898 gtk_widget_hide(t->cmd);
899 gtk_widget_show(t->oops);
902 /* XXX collapse with show_oops */
903 void
904 show_oops_s(const char *fmt, ...)
906 va_list ap;
907 char *msg;
908 struct tab *ti, *t = NULL;
910 if (fmt == NULL)
911 return;
913 TAILQ_FOREACH(ti, &tabs, entry)
914 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
915 t = ti;
916 break;
918 if (t == NULL)
919 return;
921 va_start(ap, fmt);
922 if (vasprintf(&msg, fmt, ap) == -1)
923 errx(1, "show_oops_s failed");
924 va_end(ap);
926 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
927 gtk_widget_hide(t->cmd);
928 gtk_widget_show(t->oops);
931 char *
932 get_as_string(struct settings *s)
934 char *r = NULL;
936 if (s == NULL)
937 return (NULL);
939 if (s->s) {
940 if (s->s->get)
941 r = s->s->get(s);
942 else
943 warnx("get_as_string skip %s\n", s->name);
944 } else if (s->type == XT_S_INT)
945 r = g_strdup_printf("%d", *s->ival);
946 else if (s->type == XT_S_STR)
947 r = g_strdup(*s->sval);
948 else if (s->type == XT_S_FLOAT)
949 r = g_strdup_printf("%f", *s->fval);
950 else
951 r = g_strdup_printf("INVALID TYPE");
953 return (r);
956 void
957 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
959 int i;
960 char *s;
962 for (i = 0; i < LENGTH(rs); i++) {
963 if (rs[i].s && rs[i].s->walk)
964 rs[i].s->walk(&rs[i], cb, cb_args);
965 else {
966 s = get_as_string(&rs[i]);
967 cb(&rs[i], s, cb_args);
968 g_free(s);
974 set_browser_mode(struct settings *s, char *val)
976 if (!strcmp(val, "whitelist")) {
977 browser_mode = XT_BM_WHITELIST;
978 allow_volatile_cookies = 0;
979 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
980 cookies_enabled = 1;
981 enable_cookie_whitelist = 1;
982 read_only_cookies = 0;
983 save_rejected_cookies = 0;
984 session_timeout = 3600;
985 enable_scripts = 0;
986 enable_js_whitelist = 1;
987 } else if (!strcmp(val, "normal")) {
988 browser_mode = XT_BM_NORMAL;
989 allow_volatile_cookies = 0;
990 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
991 cookies_enabled = 1;
992 enable_cookie_whitelist = 0;
993 read_only_cookies = 0;
994 save_rejected_cookies = 0;
995 session_timeout = 3600;
996 enable_scripts = 1;
997 enable_js_whitelist = 0;
998 } else if (!strcmp(val, "kiosk")) {
999 browser_mode = XT_BM_KIOSK;
1000 allow_volatile_cookies = 0;
1001 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1002 cookies_enabled = 1;
1003 enable_cookie_whitelist = 0;
1004 read_only_cookies = 0;
1005 save_rejected_cookies = 0;
1006 session_timeout = 3600;
1007 enable_scripts = 1;
1008 enable_js_whitelist = 0;
1009 show_tabs = 0;
1010 tabless = 1;
1011 } else
1012 return (1);
1014 return (0);
1017 char *
1018 get_browser_mode(struct settings *s)
1020 char *r = NULL;
1022 if (browser_mode == XT_BM_WHITELIST)
1023 r = g_strdup("whitelist");
1024 else if (browser_mode == XT_BM_NORMAL)
1025 r = g_strdup("normal");
1026 else if (browser_mode == XT_BM_KIOSK)
1027 r = g_strdup("kiosk");
1028 else
1029 return (NULL);
1031 return (r);
1035 set_cookie_policy(struct settings *s, char *val)
1037 if (!strcmp(val, "no3rdparty"))
1038 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1039 else if (!strcmp(val, "accept"))
1040 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1041 else if (!strcmp(val, "reject"))
1042 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1043 else
1044 return (1);
1046 return (0);
1049 char *
1050 get_cookie_policy(struct settings *s)
1052 char *r = NULL;
1054 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1055 r = g_strdup("no3rdparty");
1056 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1057 r = g_strdup("accept");
1058 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1059 r = g_strdup("reject");
1060 else
1061 return (NULL);
1063 return (r);
1066 char *
1067 get_download_dir(struct settings *s)
1069 if (download_dir[0] == '\0')
1070 return (0);
1071 return (g_strdup(download_dir));
1075 set_download_dir(struct settings *s, char *val)
1077 if (val[0] == '~')
1078 snprintf(download_dir, sizeof download_dir, "%s/%s",
1079 pwd->pw_dir, &val[1]);
1080 else
1081 strlcpy(download_dir, val, sizeof download_dir);
1083 return (0);
1087 * Session IDs.
1088 * We use these to prevent people putting xxxt:// URLs on
1089 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1091 #define XT_XTP_SES_KEY_SZ 8
1092 #define XT_XTP_SES_KEY_HEX_FMT \
1093 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1094 char *dl_session_key; /* downloads */
1095 char *hl_session_key; /* history list */
1096 char *cl_session_key; /* cookie list */
1097 char *fl_session_key; /* favorites list */
1099 char work_dir[PATH_MAX];
1100 char certs_dir[PATH_MAX];
1101 char cache_dir[PATH_MAX];
1102 char sessions_dir[PATH_MAX];
1103 char cookie_file[PATH_MAX];
1104 SoupURI *proxy_uri = NULL;
1105 SoupSession *session;
1106 SoupCookieJar *s_cookiejar;
1107 SoupCookieJar *p_cookiejar;
1108 char rc_fname[PATH_MAX];
1110 struct mime_type_list mtl;
1111 struct alias_list aliases;
1113 /* protos */
1114 struct tab *create_new_tab(char *, struct undo *, int);
1115 void delete_tab(struct tab *);
1116 void adjustfont_webkit(struct tab *, int);
1117 int run_script(struct tab *, char *);
1118 int download_rb_cmp(struct download *, struct download *);
1119 gboolean cmd_execute(struct tab *t, char *str);
1122 history_rb_cmp(struct history *h1, struct history *h2)
1124 return (strcmp(h1->uri, h2->uri));
1126 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1129 domain_rb_cmp(struct domain *d1, struct domain *d2)
1131 return (strcmp(d1->d, d2->d));
1133 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1135 char *
1136 get_work_dir(struct settings *s)
1138 if (work_dir[0] == '\0')
1139 return (0);
1140 return (g_strdup(work_dir));
1144 set_work_dir(struct settings *s, char *val)
1146 if (val[0] == '~')
1147 snprintf(work_dir, sizeof work_dir, "%s/%s",
1148 pwd->pw_dir, &val[1]);
1149 else
1150 strlcpy(work_dir, val, sizeof work_dir);
1152 return (0);
1156 * generate a session key to secure xtp commands.
1157 * pass in a ptr to the key in question and it will
1158 * be modified in place.
1160 void
1161 generate_xtp_session_key(char **key)
1163 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1165 /* free old key */
1166 if (*key)
1167 g_free(*key);
1169 /* make a new one */
1170 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1171 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1172 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1173 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1175 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1179 * validate a xtp session key.
1180 * return 1 if OK
1183 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1185 if (strcmp(trusted, untrusted) != 0) {
1186 show_oops(t, "%s: xtp session key mismatch possible spoof",
1187 __func__);
1188 return (0);
1191 return (1);
1195 download_rb_cmp(struct download *e1, struct download *e2)
1197 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1199 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1201 struct valid_url_types {
1202 char *type;
1203 } vut[] = {
1204 { "http://" },
1205 { "https://" },
1206 { "ftp://" },
1207 { "file://" },
1208 { XT_XTP_STR },
1212 valid_url_type(char *url)
1214 int i;
1216 for (i = 0; i < LENGTH(vut); i++)
1217 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1218 return (0);
1220 return (1);
1223 void
1224 print_cookie(char *msg, SoupCookie *c)
1226 if (c == NULL)
1227 return;
1229 if (msg)
1230 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1231 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1232 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1233 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1234 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1235 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1236 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1237 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1238 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1239 DNPRINTF(XT_D_COOKIE, "====================================\n");
1242 void
1243 walk_alias(struct settings *s,
1244 void (*cb)(struct settings *, char *, void *), void *cb_args)
1246 struct alias *a;
1247 char *str;
1249 if (s == NULL || cb == NULL) {
1250 show_oops_s("walk_alias invalid parameters");
1251 return;
1254 TAILQ_FOREACH(a, &aliases, entry) {
1255 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1256 cb(s, str, cb_args);
1257 g_free(str);
1261 char *
1262 match_alias(char *url_in)
1264 struct alias *a;
1265 char *arg;
1266 char *url_out = NULL, *search, *enc_arg;
1268 search = g_strdup(url_in);
1269 arg = search;
1270 if (strsep(&arg, " \t") == NULL) {
1271 show_oops_s("match_alias: NULL URL");
1272 goto done;
1275 TAILQ_FOREACH(a, &aliases, entry) {
1276 if (!strcmp(search, a->a_name))
1277 break;
1280 if (a != NULL) {
1281 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1282 a->a_name);
1283 if (arg != NULL) {
1284 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1285 url_out = g_strdup_printf(a->a_uri, enc_arg);
1286 g_free(enc_arg);
1287 } else
1288 url_out = g_strdup(a->a_uri);
1290 done:
1291 g_free(search);
1292 return (url_out);
1295 char *
1296 guess_url_type(char *url_in)
1298 struct stat sb;
1299 char *url_out = NULL, *enc_search = NULL;
1301 url_out = match_alias(url_in);
1302 if (url_out != NULL)
1303 return (url_out);
1305 if (guess_search) {
1307 * If there is no dot nor slash in the string and it isn't a
1308 * path to a local file and doesn't resolves to an IP, assume
1309 * that the user wants to search for the string.
1312 if (strchr(url_in, '.') == NULL &&
1313 strchr(url_in, '/') == NULL &&
1314 stat(url_in, &sb) != 0 &&
1315 gethostbyname(url_in) == NULL) {
1317 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1318 url_out = g_strdup_printf(search_string, enc_search);
1319 g_free(enc_search);
1320 return (url_out);
1324 /* XXX not sure about this heuristic */
1325 if (stat(url_in, &sb) == 0)
1326 url_out = g_strdup_printf("file://%s", url_in);
1327 else
1328 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1330 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1332 return (url_out);
1335 void
1336 load_uri(struct tab *t, gchar *uri)
1338 struct karg args;
1339 gchar *newuri = NULL;
1340 int i;
1342 if (uri == NULL)
1343 return;
1345 /* Strip leading spaces. */
1346 while(*uri && isspace(*uri))
1347 uri++;
1349 if (strlen(uri) == 0) {
1350 blank(t, NULL);
1351 return;
1354 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1355 for (i = 0; i < LENGTH(about_list); i++)
1356 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1357 bzero(&args, sizeof args);
1358 about_list[i].func(t, &args);
1359 return;
1361 show_oops(t, "invalid about page");
1362 return;
1365 if (valid_url_type(uri)) {
1366 newuri = guess_url_type(uri);
1367 uri = newuri;
1370 set_status(t, (char *)uri, XT_STATUS_LOADING);
1371 webkit_web_view_load_uri(t->wv, uri);
1373 if (newuri)
1374 g_free(newuri);
1377 const gchar *
1378 get_uri(WebKitWebView *wv)
1380 WebKitWebFrame *frame;
1381 const gchar *uri;
1383 frame = webkit_web_view_get_main_frame(wv);
1384 uri = webkit_web_frame_get_uri(frame);
1386 if (uri && strlen(uri) > 0)
1387 return (uri);
1388 else
1389 return (NULL);
1393 add_alias(struct settings *s, char *line)
1395 char *l, *alias;
1396 struct alias *a = NULL;
1398 if (s == NULL || line == NULL) {
1399 show_oops_s("add_alias invalid parameters");
1400 return (1);
1403 l = line;
1404 a = g_malloc(sizeof(*a));
1406 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1407 show_oops_s("add_alias: incomplete alias definition");
1408 goto bad;
1410 if (strlen(alias) == 0 || strlen(l) == 0) {
1411 show_oops_s("add_alias: invalid alias definition");
1412 goto bad;
1415 a->a_name = g_strdup(alias);
1416 a->a_uri = g_strdup(l);
1418 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1420 TAILQ_INSERT_TAIL(&aliases, a, entry);
1422 return (0);
1423 bad:
1424 if (a)
1425 g_free(a);
1426 return (1);
1430 add_mime_type(struct settings *s, char *line)
1432 char *mime_type;
1433 char *l;
1434 struct mime_type *m = NULL;
1435 int downloadfirst = 0;
1437 /* XXX this could be smarter */
1439 if (line == NULL && strlen(line) == 0) {
1440 show_oops_s("add_mime_type invalid parameters");
1441 return (1);
1444 l = line;
1445 if (*l == '@') {
1446 downloadfirst = 1;
1447 l++;
1449 m = g_malloc(sizeof(*m));
1451 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1452 show_oops_s("add_mime_type: invalid mime_type");
1453 goto bad;
1455 if (mime_type[strlen(mime_type) - 1] == '*') {
1456 mime_type[strlen(mime_type) - 1] = '\0';
1457 m->mt_default = 1;
1458 } else
1459 m->mt_default = 0;
1461 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1462 show_oops_s("add_mime_type: invalid mime_type");
1463 goto bad;
1466 m->mt_type = g_strdup(mime_type);
1467 m->mt_action = g_strdup(l);
1468 m->mt_download = downloadfirst;
1470 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1471 m->mt_type, m->mt_action, m->mt_default);
1473 TAILQ_INSERT_TAIL(&mtl, m, entry);
1475 return (0);
1476 bad:
1477 if (m)
1478 g_free(m);
1479 return (1);
1482 struct mime_type *
1483 find_mime_type(char *mime_type)
1485 struct mime_type *m, *def = NULL, *rv = NULL;
1487 TAILQ_FOREACH(m, &mtl, entry) {
1488 if (m->mt_default &&
1489 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1490 def = m;
1492 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1493 rv = m;
1494 break;
1498 if (rv == NULL)
1499 rv = def;
1501 return (rv);
1504 void
1505 walk_mime_type(struct settings *s,
1506 void (*cb)(struct settings *, char *, void *), void *cb_args)
1508 struct mime_type *m;
1509 char *str;
1511 if (s == NULL || cb == NULL)
1512 show_oops_s("walk_mime_type invalid parameters");
1514 TAILQ_FOREACH(m, &mtl, entry) {
1515 str = g_strdup_printf("%s%s --> %s",
1516 m->mt_type,
1517 m->mt_default ? "*" : "",
1518 m->mt_action);
1519 cb(s, str, cb_args);
1520 g_free(str);
1524 void
1525 wl_add(char *str, struct domain_list *wl, int handy)
1527 struct domain *d;
1528 int add_dot = 0;
1530 if (str == NULL || wl == NULL)
1531 return;
1532 if (strlen(str) < 2)
1533 return;
1535 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1537 /* treat *.moo.com the same as .moo.com */
1538 if (str[0] == '*' && str[1] == '.')
1539 str = &str[1];
1540 else if (str[0] == '.')
1541 str = &str[0];
1542 else
1543 add_dot = 1;
1545 d = g_malloc(sizeof *d);
1546 if (add_dot)
1547 d->d = g_strdup_printf(".%s", str);
1548 else
1549 d->d = g_strdup(str);
1550 d->handy = handy;
1552 if (RB_INSERT(domain_list, wl, d))
1553 goto unwind;
1555 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1556 return;
1557 unwind:
1558 if (d) {
1559 if (d->d)
1560 g_free(d->d);
1561 g_free(d);
1566 add_cookie_wl(struct settings *s, char *entry)
1568 wl_add(entry, &c_wl, 1);
1569 return (0);
1572 void
1573 walk_cookie_wl(struct settings *s,
1574 void (*cb)(struct settings *, char *, void *), void *cb_args)
1576 struct domain *d;
1578 if (s == NULL || cb == NULL) {
1579 show_oops_s("walk_cookie_wl invalid parameters");
1580 return;
1583 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1584 cb(s, d->d, cb_args);
1587 void
1588 walk_js_wl(struct settings *s,
1589 void (*cb)(struct settings *, char *, void *), void *cb_args)
1591 struct domain *d;
1593 if (s == NULL || cb == NULL) {
1594 show_oops_s("walk_js_wl invalid parameters");
1595 return;
1598 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1599 cb(s, d->d, cb_args);
1603 add_js_wl(struct settings *s, char *entry)
1605 wl_add(entry, &js_wl, 1 /* persistent */);
1606 return (0);
1609 struct domain *
1610 wl_find(const gchar *search, struct domain_list *wl)
1612 int i;
1613 struct domain *d = NULL, dfind;
1614 gchar *s = NULL;
1616 if (search == NULL || wl == NULL)
1617 return (NULL);
1618 if (strlen(search) < 2)
1619 return (NULL);
1621 if (search[0] != '.')
1622 s = g_strdup_printf(".%s", search);
1623 else
1624 s = g_strdup(search);
1626 for (i = strlen(s) - 1; i >= 0; i--) {
1627 if (s[i] == '.') {
1628 dfind.d = &s[i];
1629 d = RB_FIND(domain_list, wl, &dfind);
1630 if (d)
1631 goto done;
1635 done:
1636 if (s)
1637 g_free(s);
1639 return (d);
1642 struct domain *
1643 wl_find_uri(const gchar *s, struct domain_list *wl)
1645 int i;
1646 char *ss;
1647 struct domain *r;
1649 if (s == NULL || wl == NULL)
1650 return (NULL);
1652 if (!strncmp(s, "http://", strlen("http://")))
1653 s = &s[strlen("http://")];
1654 else if (!strncmp(s, "https://", strlen("https://")))
1655 s = &s[strlen("https://")];
1657 if (strlen(s) < 2)
1658 return (NULL);
1660 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1661 /* chop string at first slash */
1662 if (s[i] == '/' || s[i] == '\0') {
1663 ss = g_strdup(s);
1664 ss[i] = '\0';
1665 r = wl_find(ss, wl);
1666 g_free(ss);
1667 return (r);
1670 return (NULL);
1673 char *
1674 get_toplevel_domain(char *domain)
1676 char *s;
1677 int found = 0;
1679 if (domain == NULL)
1680 return (NULL);
1681 if (strlen(domain) < 2)
1682 return (NULL);
1684 s = &domain[strlen(domain) - 1];
1685 while (s != domain) {
1686 if (*s == '.') {
1687 found++;
1688 if (found == 2)
1689 return (s);
1691 s--;
1694 if (found)
1695 return (domain);
1697 return (NULL);
1701 settings_add(char *var, char *val)
1703 int i, rv, *p;
1704 gfloat *f;
1705 char **s;
1707 /* get settings */
1708 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1709 if (strcmp(var, rs[i].name))
1710 continue;
1712 if (rs[i].s) {
1713 if (rs[i].s->set(&rs[i], val))
1714 errx(1, "invalid value for %s: %s", var, val);
1715 rv = 1;
1716 break;
1717 } else
1718 switch (rs[i].type) {
1719 case XT_S_INT:
1720 p = rs[i].ival;
1721 *p = atoi(val);
1722 rv = 1;
1723 break;
1724 case XT_S_STR:
1725 s = rs[i].sval;
1726 if (s == NULL)
1727 errx(1, "invalid sval for %s",
1728 rs[i].name);
1729 if (*s)
1730 g_free(*s);
1731 *s = g_strdup(val);
1732 rv = 1;
1733 break;
1734 case XT_S_FLOAT:
1735 f = rs[i].fval;
1736 *f = atof(val);
1737 rv = 1;
1738 break;
1739 case XT_S_INVALID:
1740 default:
1741 errx(1, "invalid type for %s", var);
1743 break;
1745 return (rv);
1748 #define WS "\n= \t"
1749 void
1750 config_parse(char *filename, int runtime)
1752 FILE *config, *f;
1753 char *line, *cp, *var, *val;
1754 size_t len, lineno = 0;
1755 int handled;
1756 char file[PATH_MAX];
1757 struct stat sb;
1759 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1761 if (filename == NULL)
1762 return;
1764 if (runtime && runtime_settings[0] != '\0') {
1765 snprintf(file, sizeof file, "%s/%s",
1766 work_dir, runtime_settings);
1767 if (stat(file, &sb)) {
1768 warnx("runtime file doesn't exist, creating it");
1769 if ((f = fopen(file, "w")) == NULL)
1770 err(1, "runtime");
1771 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1772 fclose(f);
1774 } else
1775 strlcpy(file, filename, sizeof file);
1777 if ((config = fopen(file, "r")) == NULL) {
1778 warn("config_parse: cannot open %s", filename);
1779 return;
1782 for (;;) {
1783 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1784 if (feof(config) || ferror(config))
1785 break;
1787 cp = line;
1788 cp += (long)strspn(cp, WS);
1789 if (cp[0] == '\0') {
1790 /* empty line */
1791 free(line);
1792 continue;
1795 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1796 errx(1, "invalid config file entry: %s", line);
1798 cp += (long)strspn(cp, WS);
1800 if ((val = strsep(&cp, "\0")) == NULL)
1801 break;
1803 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1804 handled = settings_add(var, val);
1805 if (handled == 0)
1806 errx(1, "invalid conf file entry: %s=%s", var, val);
1808 free(line);
1811 fclose(config);
1814 char *
1815 js_ref_to_string(JSContextRef context, JSValueRef ref)
1817 char *s = NULL;
1818 size_t l;
1819 JSStringRef jsref;
1821 jsref = JSValueToStringCopy(context, ref, NULL);
1822 if (jsref == NULL)
1823 return (NULL);
1825 l = JSStringGetMaximumUTF8CStringSize(jsref);
1826 s = g_malloc(l);
1827 if (s)
1828 JSStringGetUTF8CString(jsref, s, l);
1829 JSStringRelease(jsref);
1831 return (s);
1834 void
1835 disable_hints(struct tab *t)
1837 bzero(t->hint_buf, sizeof t->hint_buf);
1838 bzero(t->hint_num, sizeof t->hint_num);
1839 run_script(t, "vimprobable_clear()");
1840 t->hints_on = 0;
1841 t->hint_mode = XT_HINT_NONE;
1844 void
1845 enable_hints(struct tab *t)
1847 bzero(t->hint_buf, sizeof t->hint_buf);
1848 run_script(t, "vimprobable_show_hints()");
1849 t->hints_on = 1;
1850 t->hint_mode = XT_HINT_NONE;
1853 #define XT_JS_OPEN ("open;")
1854 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1855 #define XT_JS_FIRE ("fire;")
1856 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1857 #define XT_JS_FOUND ("found;")
1858 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1861 run_script(struct tab *t, char *s)
1863 JSGlobalContextRef ctx;
1864 WebKitWebFrame *frame;
1865 JSStringRef str;
1866 JSValueRef val, exception;
1867 char *es, buf[128];
1869 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1870 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1872 frame = webkit_web_view_get_main_frame(t->wv);
1873 ctx = webkit_web_frame_get_global_context(frame);
1875 str = JSStringCreateWithUTF8CString(s);
1876 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1877 NULL, 0, &exception);
1878 JSStringRelease(str);
1880 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1881 if (val == NULL) {
1882 es = js_ref_to_string(ctx, exception);
1883 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1884 g_free(es);
1885 return (1);
1886 } else {
1887 es = js_ref_to_string(ctx, val);
1888 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1890 /* handle return value right here */
1891 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1892 disable_hints(t);
1893 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1896 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1897 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1898 &es[XT_JS_FIRE_LEN]);
1899 run_script(t, buf);
1900 disable_hints(t);
1903 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1904 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1905 disable_hints(t);
1908 g_free(es);
1911 return (0);
1915 hint(struct tab *t, struct karg *args)
1918 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1920 if (t->hints_on == 0)
1921 enable_hints(t);
1922 else
1923 disable_hints(t);
1925 return (0);
1928 void
1929 apply_style(struct tab *t)
1931 g_object_set(G_OBJECT(t->settings),
1932 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1936 userstyle(struct tab *t, struct karg *args)
1938 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1940 if (t->styled) {
1941 t->styled = 0;
1942 g_object_set(G_OBJECT(t->settings),
1943 "user-stylesheet-uri", NULL, (char *)NULL);
1944 } else {
1945 t->styled = 1;
1946 apply_style(t);
1948 return (0);
1952 * Doesn't work fully, due to the following bug:
1953 * https://bugs.webkit.org/show_bug.cgi?id=51747
1956 restore_global_history(void)
1958 char file[PATH_MAX];
1959 FILE *f;
1960 struct history *h;
1961 gchar *uri;
1962 gchar *title;
1964 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1966 if ((f = fopen(file, "r")) == NULL) {
1967 warnx("%s: fopen", __func__);
1968 return (1);
1971 for (;;) {
1972 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1973 if (feof(f) || ferror(f))
1974 break;
1976 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1977 if (feof(f) || ferror(f)) {
1978 free(uri);
1979 warnx("%s: broken history file\n", __func__);
1980 return (1);
1983 if (uri && strlen(uri) && title && strlen(title)) {
1984 webkit_web_history_item_new_with_data(uri, title);
1985 h = g_malloc(sizeof(struct history));
1986 h->uri = g_strdup(uri);
1987 h->title = g_strdup(title);
1988 RB_INSERT(history_list, &hl, h);
1989 completion_add_uri(h->uri);
1990 } else {
1991 warnx("%s: failed to restore history\n", __func__);
1992 free(uri);
1993 free(title);
1994 return (1);
1997 free(uri);
1998 free(title);
1999 uri = NULL;
2000 title = NULL;
2003 return (0);
2007 save_global_history_to_disk(struct tab *t)
2009 char file[PATH_MAX];
2010 FILE *f;
2011 struct history *h;
2013 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2015 if ((f = fopen(file, "w")) == NULL) {
2016 show_oops(t, "%s: global history file: %s",
2017 __func__, strerror(errno));
2018 return (1);
2021 RB_FOREACH_REVERSE(h, history_list, &hl) {
2022 if (h->uri && h->title)
2023 fprintf(f, "%s\n%s\n", h->uri, h->title);
2026 fclose(f);
2028 return (0);
2032 quit(struct tab *t, struct karg *args)
2034 if (save_global_history)
2035 save_global_history_to_disk(t);
2037 gtk_main_quit();
2039 return (1);
2043 open_tabs(struct tab *t, struct karg *a)
2045 char file[PATH_MAX];
2046 FILE *f = NULL;
2047 char *uri = NULL;
2048 int rv = 1;
2049 struct tab *ti, *tt;
2051 if (a == NULL)
2052 goto done;
2054 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2055 if ((f = fopen(file, "r")) == NULL)
2056 goto done;
2058 ti = TAILQ_LAST(&tabs, tab_list);
2060 for (;;) {
2061 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2062 if (feof(f) || ferror(f))
2063 break;
2065 /* retrieve session name */
2066 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2067 strlcpy(named_session,
2068 &uri[strlen(XT_SAVE_SESSION_ID)],
2069 sizeof named_session);
2070 continue;
2073 if (uri && strlen(uri))
2074 create_new_tab(uri, NULL, 1);
2076 free(uri);
2077 uri = NULL;
2080 /* close open tabs */
2081 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2082 for (;;) {
2083 tt = TAILQ_FIRST(&tabs);
2084 if (tt != ti) {
2085 delete_tab(tt);
2086 continue;
2088 delete_tab(tt);
2089 break;
2093 rv = 0;
2094 done:
2095 if (f)
2096 fclose(f);
2098 return (rv);
2102 restore_saved_tabs(void)
2104 char file[PATH_MAX];
2105 int unlink_file = 0;
2106 struct stat sb;
2107 struct karg a;
2108 int rv = 0;
2110 snprintf(file, sizeof file, "%s/%s",
2111 sessions_dir, XT_RESTART_TABS_FILE);
2112 if (stat(file, &sb) == -1)
2113 a.s = XT_SAVED_TABS_FILE;
2114 else {
2115 unlink_file = 1;
2116 a.s = XT_RESTART_TABS_FILE;
2119 a.i = XT_SES_DONOTHING;
2120 rv = open_tabs(NULL, &a);
2122 if (unlink_file)
2123 unlink(file);
2125 return (rv);
2129 save_tabs(struct tab *t, struct karg *a)
2131 char file[PATH_MAX];
2132 FILE *f;
2133 struct tab *ti;
2134 const gchar *uri;
2135 int len = 0, i;
2136 const gchar **arr = NULL;
2138 if (a == NULL)
2139 return (1);
2140 if (a->s == NULL)
2141 snprintf(file, sizeof file, "%s/%s",
2142 sessions_dir, named_session);
2143 else
2144 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2146 if ((f = fopen(file, "w")) == NULL) {
2147 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2148 return (1);
2151 /* save session name */
2152 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2154 /* save tabs, in the order they are arranged in the notebook */
2155 TAILQ_FOREACH(ti, &tabs, entry)
2156 len++;
2158 arr = g_malloc0(len * sizeof(gchar *));
2160 TAILQ_FOREACH(ti, &tabs, entry) {
2161 if ((uri = get_uri(ti->wv)) != NULL)
2162 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2165 for (i = 0; i < len; i++)
2166 if (arr[i])
2167 fprintf(f, "%s\n", arr[i]);
2169 g_free(arr);
2170 fclose(f);
2172 return (0);
2176 save_tabs_and_quit(struct tab *t, struct karg *args)
2178 struct karg a;
2180 a.s = NULL;
2181 save_tabs(t, &a);
2182 quit(t, NULL);
2184 return (1);
2188 yank_uri(struct tab *t, struct karg *args)
2190 const gchar *uri;
2191 GtkClipboard *clipboard;
2193 if ((uri = get_uri(t->wv)) == NULL)
2194 return (1);
2196 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2197 gtk_clipboard_set_text(clipboard, uri, -1);
2199 return (0);
2202 struct paste_args {
2203 struct tab *t;
2204 int i;
2207 void
2208 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2210 struct paste_args *pap;
2212 if (data == NULL || text == NULL || !strlen(text))
2213 return;
2215 pap = (struct paste_args *)data;
2217 switch(pap->i) {
2218 case XT_PASTE_CURRENT_TAB:
2219 load_uri(pap->t, (gchar *)text);
2220 break;
2221 case XT_PASTE_NEW_TAB:
2222 create_new_tab((gchar *)text, NULL, 1);
2223 break;
2226 g_free(pap);
2230 paste_uri(struct tab *t, struct karg *args)
2232 GtkClipboard *clipboard;
2233 struct paste_args *pap;
2235 pap = g_malloc(sizeof(struct paste_args));
2237 pap->t = t;
2238 pap->i = args->i;
2240 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2241 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2243 return (0);
2246 char *
2247 find_domain(const gchar *s, int add_dot)
2249 int i;
2250 char *r = NULL, *ss = NULL;
2252 if (s == NULL)
2253 return (NULL);
2255 if (!strncmp(s, "http://", strlen("http://")))
2256 s = &s[strlen("http://")];
2257 else if (!strncmp(s, "https://", strlen("https://")))
2258 s = &s[strlen("https://")];
2260 if (strlen(s) < 2)
2261 return (NULL);
2263 ss = g_strdup(s);
2264 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2265 /* chop string at first slash */
2266 if (ss[i] == '/' || ss[i] == '\0') {
2267 ss[i] = '\0';
2268 if (add_dot)
2269 r = g_strdup_printf(".%s", ss);
2270 else
2271 r = g_strdup(ss);
2272 break;
2274 g_free(ss);
2276 return (r);
2280 toggle_cwl(struct tab *t, struct karg *args)
2282 struct domain *d;
2283 const gchar *uri;
2284 char *dom = NULL, *dom_toggle = NULL;
2285 int es;
2287 if (args == NULL)
2288 return (1);
2290 uri = get_uri(t->wv);
2291 dom = find_domain(uri, 1);
2292 d = wl_find(dom, &c_wl);
2294 if (d == NULL)
2295 es = 0;
2296 else
2297 es = 1;
2299 if (args->i & XT_WL_TOGGLE)
2300 es = !es;
2301 else if ((args->i & XT_WL_ENABLE) && es != 1)
2302 es = 1;
2303 else if ((args->i & XT_WL_DISABLE) && es != 0)
2304 es = 0;
2306 if (args->i & XT_WL_TOPLEVEL)
2307 dom_toggle = get_toplevel_domain(dom);
2308 else
2309 dom_toggle = dom;
2311 if (es)
2312 /* enable cookies for domain */
2313 wl_add(dom_toggle, &c_wl, 0);
2314 else
2315 /* disable cookies for domain */
2316 RB_REMOVE(domain_list, &c_wl, d);
2318 webkit_web_view_reload(t->wv);
2320 g_free(dom);
2321 return (0);
2325 toggle_js(struct tab *t, struct karg *args)
2327 int es;
2328 const gchar *uri;
2329 struct domain *d;
2330 char *dom = NULL, *dom_toggle = NULL;
2332 if (args == NULL)
2333 return (1);
2335 g_object_get(G_OBJECT(t->settings),
2336 "enable-scripts", &es, (char *)NULL);
2337 if (args->i & XT_WL_TOGGLE)
2338 es = !es;
2339 else if ((args->i & XT_WL_ENABLE) && es != 1)
2340 es = 1;
2341 else if ((args->i & XT_WL_DISABLE) && es != 0)
2342 es = 0;
2343 else
2344 return (1);
2346 uri = get_uri(t->wv);
2347 dom = find_domain(uri, 1);
2349 if (uri == NULL || dom == NULL) {
2350 show_oops(t, "Can't toggle domain in JavaScript white list");
2351 goto done;
2354 if (args->i & XT_WL_TOPLEVEL)
2355 dom_toggle = get_toplevel_domain(dom);
2356 else
2357 dom_toggle = dom;
2359 if (es) {
2360 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2361 wl_add(dom_toggle, &js_wl, 0 /* session */);
2362 } else {
2363 d = wl_find(dom_toggle, &js_wl);
2364 if (d)
2365 RB_REMOVE(domain_list, &js_wl, d);
2366 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2368 g_object_set(G_OBJECT(t->settings),
2369 "enable-scripts", es, (char *)NULL);
2370 g_object_set(G_OBJECT(t->settings),
2371 "javascript-can-open-windows-automatically", es, (char *)NULL);
2372 webkit_web_view_set_settings(t->wv, t->settings);
2373 webkit_web_view_reload(t->wv);
2374 done:
2375 if (dom)
2376 g_free(dom);
2377 return (0);
2380 void
2381 js_toggle_cb(GtkWidget *w, struct tab *t)
2383 struct karg a;
2385 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2386 toggle_js(t, &a);
2390 toggle_src(struct tab *t, struct karg *args)
2392 gboolean mode;
2394 if (t == NULL)
2395 return (0);
2397 mode = webkit_web_view_get_view_source_mode(t->wv);
2398 webkit_web_view_set_view_source_mode(t->wv, !mode);
2399 webkit_web_view_reload(t->wv);
2401 return (0);
2404 void
2405 focus_webview(struct tab *t)
2407 if (t == NULL)
2408 return;
2410 /* only grab focus if we are visible */
2411 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2412 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2416 focus(struct tab *t, struct karg *args)
2418 if (t == NULL || args == NULL)
2419 return (1);
2421 if (show_url == 0)
2422 return (0);
2424 if (args->i == XT_FOCUS_URI)
2425 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2426 else if (args->i == XT_FOCUS_SEARCH)
2427 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2429 return (0);
2433 stats(struct tab *t, struct karg *args)
2435 char *stats, *s, line[64 * 1024];
2436 uint64_t line_count = 0;
2437 FILE *r_cookie_f;
2439 if (t == NULL)
2440 show_oops_s("stats invalid parameters");
2442 line[0] = '\0';
2443 if (save_rejected_cookies) {
2444 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2445 for (;;) {
2446 s = fgets(line, sizeof line, r_cookie_f);
2447 if (s == NULL || feof(r_cookie_f) ||
2448 ferror(r_cookie_f))
2449 break;
2450 line_count++;
2452 fclose(r_cookie_f);
2453 snprintf(line, sizeof line,
2454 "<br>Cookies blocked(*) total: %llu", line_count);
2455 } else
2456 show_oops(t, "Can't open blocked cookies file: %s",
2457 strerror(errno));
2460 stats = g_strdup_printf(XT_DOCTYPE
2461 "<html>"
2462 "<head>"
2463 "<title>Statistics</title>"
2464 "</head>"
2465 "<h1>Statistics</h1>"
2466 "<body>"
2467 "Cookies blocked(*) this session: %llu"
2468 "%s"
2469 "<p><small><b>*</b> results vary based on settings"
2470 "</body>"
2471 "</html>",
2472 blocked_cookies,
2473 line);
2475 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2476 g_free(stats);
2478 return (0);
2482 marco(struct tab *t, struct karg *args)
2484 char *message, line[64 * 1024];
2485 int len;
2487 if (t == NULL)
2488 show_oops_s("marco invalid parameters");
2490 line[0] = '\0';
2491 snprintf(line, sizeof line, "<br>%s", marco_message(&len));
2493 message = g_strdup_printf(XT_DOCTYPE
2494 "<html>"
2495 "<head>"
2496 "<title>Marco Sez...</title>"
2497 "</head>"
2498 "<h1>Moo</h1>"
2499 "<body>"
2500 "%s"
2501 "</body>"
2502 "</html>",
2503 line);
2505 load_webkit_string(t, message, XT_URI_ABOUT_MARCO);
2506 g_free(message);
2508 return (0);
2512 blank(struct tab *t, struct karg *args)
2514 if (t == NULL)
2515 show_oops_s("about invalid parameters");
2517 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2519 return (0);
2522 about(struct tab *t, struct karg *args)
2524 char *about;
2526 if (t == NULL)
2527 show_oops_s("about invalid parameters");
2529 about = g_strdup_printf(XT_DOCTYPE
2530 "<html>"
2531 "<head>"
2532 "<title>About</title>"
2533 "</head>"
2534 "<h1>About</h1>"
2535 "<body>"
2536 "<b>Version: %s</b><p>"
2537 "Authors:"
2538 "<ul>"
2539 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2540 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2541 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2542 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2543 "</ul>"
2544 "Copyrights and licenses can be found on the XXXterm "
2545 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2546 "</body>"
2547 "</html>",
2548 version
2551 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2552 g_free(about);
2554 return (0);
2558 help(struct tab *t, struct karg *args)
2560 char *help;
2562 if (t == NULL)
2563 show_oops_s("help invalid parameters");
2565 help = XT_DOCTYPE
2566 "<html>"
2567 "<head>"
2568 "<title>XXXterm</title>"
2569 "<meta http-equiv=\"REFRESH\" content=\"0;"
2570 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2571 "</head>"
2572 "<body>"
2573 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2574 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2575 "cgi-bin/man-cgi?xxxterm</a>"
2576 "</body>"
2577 "</html>"
2580 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2582 return (0);
2586 * update all favorite tabs apart from one. Pass NULL if
2587 * you want to update all.
2589 void
2590 update_favorite_tabs(struct tab *apart_from)
2592 struct tab *t;
2593 if (!updating_fl_tabs) {
2594 updating_fl_tabs = 1; /* stop infinite recursion */
2595 TAILQ_FOREACH(t, &tabs, entry)
2596 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2597 && (t != apart_from))
2598 xtp_page_fl(t, NULL);
2599 updating_fl_tabs = 0;
2603 /* show a list of favorites (bookmarks) */
2605 xtp_page_fl(struct tab *t, struct karg *args)
2607 char file[PATH_MAX];
2608 FILE *f;
2609 char *uri = NULL, *title = NULL;
2610 size_t len, lineno = 0;
2611 int i, failed = 0;
2612 char *header, *body, *tmp, *html = NULL;
2614 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2616 if (t == NULL)
2617 warn("%s: bad param", __func__);
2619 /* mark tab as favorite list */
2620 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2622 /* new session key */
2623 if (!updating_fl_tabs)
2624 generate_xtp_session_key(&fl_session_key);
2626 /* open favorites */
2627 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2628 if ((f = fopen(file, "r")) == NULL) {
2629 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2630 return (1);
2633 /* header */
2634 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2635 "<title>Favorites</title>\n"
2636 "%s"
2637 "</head>"
2638 "<h1>Favorites</h1>\n",
2639 XT_PAGE_STYLE);
2641 /* body */
2642 body = g_strdup_printf("<div align='center'><table><tr>"
2643 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2644 "<th style='width: 15%%'>Remove</th></tr>\n");
2646 for (i = 1;;) {
2647 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2648 if (feof(f) || ferror(f))
2649 break;
2650 if (len == 0) {
2651 free(title);
2652 title = NULL;
2653 continue;
2656 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2657 if (feof(f) || ferror(f)) {
2658 show_oops(t, "favorites file corrupt");
2659 failed = 1;
2660 break;
2663 tmp = body;
2664 body = g_strdup_printf("%s<tr>"
2665 "<td>%d</td>"
2666 "<td><a href='%s'>%s</a></td>"
2667 "<td style='text-align: center'>"
2668 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2669 "</tr>\n",
2670 body, i, uri, title,
2671 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2673 g_free(tmp);
2675 free(uri);
2676 uri = NULL;
2677 free(title);
2678 title = NULL;
2679 i++;
2681 fclose(f);
2683 /* if none, say so */
2684 if (i == 1) {
2685 tmp = body;
2686 body = g_strdup_printf("%s<tr>"
2687 "<td colspan='3' style='text-align: center'>"
2688 "No favorites - To add one use the 'favadd' command."
2689 "</td></tr>", body);
2690 g_free(tmp);
2693 if (uri)
2694 free(uri);
2695 if (title)
2696 free(title);
2698 /* render */
2699 if (!failed) {
2700 html = g_strdup_printf("%s%s</table></div></html>",
2701 header, body);
2702 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2705 update_favorite_tabs(t);
2707 if (header)
2708 g_free(header);
2709 if (body)
2710 g_free(body);
2711 if (html)
2712 g_free(html);
2714 return (failed);
2717 void
2718 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2719 size_t cert_count, char *title)
2721 gnutls_datum_t cinfo;
2722 char *tmp, *header, *body, *footer;
2723 int i;
2725 header = g_strdup_printf("<html><head><title>%s</title></head><body>", title);
2726 footer = g_strdup("</body></html>");
2727 body = g_strdup("");
2729 for (i = 0; i < cert_count; i++) {
2730 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2731 &cinfo))
2732 return;
2734 tmp = body;
2735 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2736 body, i, cinfo.data);
2737 gnutls_free(cinfo.data);
2738 g_free(tmp);
2741 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2742 g_free(header);
2743 g_free(body);
2744 g_free(footer);
2745 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2746 g_free(tmp);
2750 ca_cmd(struct tab *t, struct karg *args)
2752 FILE *f = NULL;
2753 int rv = 1, certs = 0, certs_read;
2754 struct stat sb;
2755 gnutls_datum dt;
2756 gnutls_x509_crt_t *c = NULL;
2757 char *certs_buf = NULL, *s;
2759 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2760 show_oops(t, "Can't open CA file: %s", strerror(errno));
2761 return (1);
2764 if (fstat(fileno(f), &sb) == -1) {
2765 show_oops(t, "Can't stat CA file: %s", strerror(errno));
2766 goto done;
2769 certs_buf = g_malloc(sb.st_size + 1);
2770 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2771 show_oops(t, "Can't read CA file: %s", strerror(errno));
2772 goto done;
2774 certs_buf[sb.st_size] = '\0';
2776 s = certs_buf;
2777 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2778 certs++;
2779 s += strlen("BEGIN CERTIFICATE");
2782 bzero(&dt, sizeof dt);
2783 dt.data = certs_buf;
2784 dt.size = sb.st_size;
2785 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2786 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2787 GNUTLS_X509_FMT_PEM, 0);
2788 if (certs_read <= 0) {
2789 show_oops(t, "No cert(s) available");
2790 goto done;
2792 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2793 done:
2794 if (c)
2795 g_free(c);
2796 if (certs_buf)
2797 g_free(certs_buf);
2798 if (f)
2799 fclose(f);
2801 return (rv);
2805 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2807 SoupURI *su = NULL;
2808 struct addrinfo hints, *res = NULL, *ai;
2809 int s = -1, on;
2810 char port[8];
2812 if (uri && !g_str_has_prefix(uri, "https://"))
2813 goto done;
2815 su = soup_uri_new(uri);
2816 if (su == NULL)
2817 goto done;
2818 if (!SOUP_URI_VALID_FOR_HTTP(su))
2819 goto done;
2821 snprintf(port, sizeof port, "%d", su->port);
2822 bzero(&hints, sizeof(struct addrinfo));
2823 hints.ai_flags = AI_CANONNAME;
2824 hints.ai_family = AF_UNSPEC;
2825 hints.ai_socktype = SOCK_STREAM;
2827 if (getaddrinfo(su->host, port, &hints, &res))
2828 goto done;
2830 for (ai = res; ai; ai = ai->ai_next) {
2831 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2832 continue;
2834 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2835 if (s < 0)
2836 goto done;
2837 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2838 sizeof(on)) == -1)
2839 goto done;
2841 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2842 goto done;
2845 if (domain)
2846 strlcpy(domain, su->host, domain_sz);
2847 done:
2848 if (su)
2849 soup_uri_free(su);
2850 if (res)
2851 freeaddrinfo(res);
2853 return (s);
2857 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2859 if (gsession)
2860 gnutls_deinit(gsession);
2861 if (xcred)
2862 gnutls_certificate_free_credentials(xcred);
2864 return (0);
2868 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2869 gnutls_certificate_credentials_t *xc)
2871 gnutls_certificate_credentials_t xcred;
2872 gnutls_session_t gsession;
2873 int rv = 1;
2875 if (gs == NULL || xc == NULL)
2876 goto done;
2878 *gs = NULL;
2879 *xc = NULL;
2881 gnutls_certificate_allocate_credentials(&xcred);
2882 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2883 GNUTLS_X509_FMT_PEM);
2884 gnutls_init(&gsession, GNUTLS_CLIENT);
2885 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2886 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2887 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2888 if ((rv = gnutls_handshake(gsession)) < 0) {
2889 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2891 gnutls_error_is_fatal(rv),
2892 gnutls_strerror_name(rv));
2893 stop_tls(gsession, xcred);
2894 goto done;
2897 gnutls_credentials_type_t cred;
2898 cred = gnutls_auth_get_type(gsession);
2899 if (cred != GNUTLS_CRD_CERTIFICATE) {
2900 stop_tls(gsession, xcred);
2901 goto done;
2904 *gs = gsession;
2905 *xc = xcred;
2906 rv = 0;
2907 done:
2908 return (rv);
2912 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2913 size_t *cert_count)
2915 unsigned int len;
2916 const gnutls_datum_t *cl;
2917 gnutls_x509_crt_t *all_certs;
2918 int i, rv = 1;
2920 if (certs == NULL || cert_count == NULL)
2921 goto done;
2922 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2923 goto done;
2924 cl = gnutls_certificate_get_peers(gsession, &len);
2925 if (len == 0)
2926 goto done;
2928 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2929 for (i = 0; i < len; i++) {
2930 gnutls_x509_crt_init(&all_certs[i]);
2931 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2932 GNUTLS_X509_FMT_PEM < 0)) {
2933 g_free(all_certs);
2934 goto done;
2938 *certs = all_certs;
2939 *cert_count = len;
2940 rv = 0;
2941 done:
2942 return (rv);
2945 void
2946 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2948 int i;
2950 for (i = 0; i < cert_count; i++)
2951 gnutls_x509_crt_deinit(certs[i]);
2952 g_free(certs);
2955 void
2956 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2957 size_t cert_count, char *domain)
2959 size_t cert_buf_sz;
2960 char cert_buf[64 * 1024], file[PATH_MAX];
2961 int i;
2962 FILE *f;
2963 GdkColor color;
2965 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2966 return;
2968 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2969 if ((f = fopen(file, "w")) == NULL) {
2970 show_oops(t, "Can't create cert file %s %s",
2971 file, strerror(errno));
2972 return;
2975 for (i = 0; i < cert_count; i++) {
2976 cert_buf_sz = sizeof cert_buf;
2977 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2978 cert_buf, &cert_buf_sz)) {
2979 show_oops(t, "gnutls_x509_crt_export failed");
2980 goto done;
2982 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2983 show_oops(t, "Can't write certs: %s", strerror(errno));
2984 goto done;
2988 /* not the best spot but oh well */
2989 gdk_color_parse("lightblue", &color);
2990 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2991 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2992 gdk_color_parse(XT_COLOR_BLACK, &color);
2993 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2994 done:
2995 fclose(f);
2999 load_compare_cert(struct tab *t, struct karg *args)
3001 const gchar *uri;
3002 char domain[8182], file[PATH_MAX];
3003 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3004 int s = -1, rv = 1, i;
3005 size_t cert_count;
3006 FILE *f = NULL;
3007 size_t cert_buf_sz;
3008 gnutls_session_t gsession;
3009 gnutls_x509_crt_t *certs;
3010 gnutls_certificate_credentials_t xcred;
3012 if (t == NULL)
3013 return (1);
3015 if ((uri = get_uri(t->wv)) == NULL)
3016 return (1);
3018 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3019 return (1);
3021 /* go ssl/tls */
3022 if (start_tls(t, s, &gsession, &xcred)) {
3023 show_oops(t, "Start TLS failed");
3024 goto done;
3027 /* get certs */
3028 if (get_connection_certs(gsession, &certs, &cert_count)) {
3029 show_oops(t, "Can't get connection certificates");
3030 goto done;
3033 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3034 if ((f = fopen(file, "r")) == NULL)
3035 goto freeit;
3037 for (i = 0; i < cert_count; i++) {
3038 cert_buf_sz = sizeof cert_buf;
3039 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3040 cert_buf, &cert_buf_sz)) {
3041 goto freeit;
3043 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3044 rv = -1; /* critical */
3045 goto freeit;
3047 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3048 rv = -1; /* critical */
3049 goto freeit;
3053 rv = 0;
3054 freeit:
3055 if (f)
3056 fclose(f);
3057 free_connection_certs(certs, cert_count);
3058 done:
3059 /* we close the socket first for speed */
3060 if (s != -1)
3061 close(s);
3062 stop_tls(gsession, xcred);
3064 return (rv);
3068 cert_cmd(struct tab *t, struct karg *args)
3070 const gchar *uri;
3071 char domain[8182];
3072 int s = -1;
3073 size_t cert_count;
3074 gnutls_session_t gsession;
3075 gnutls_x509_crt_t *certs;
3076 gnutls_certificate_credentials_t xcred;
3078 if (t == NULL)
3079 return (1);
3081 if ((uri = get_uri(t->wv)) == NULL) {
3082 show_oops(t, "Invalid URI");
3083 return (1);
3086 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3087 show_oops(t, "Invalid certidicate URI: %s", uri);
3088 return (1);
3091 /* go ssl/tls */
3092 if (start_tls(t, s, &gsession, &xcred)) {
3093 show_oops(t, "Start TLS failed");
3094 goto done;
3097 /* get certs */
3098 if (get_connection_certs(gsession, &certs, &cert_count)) {
3099 show_oops(t, "get_connection_certs failed");
3100 goto done;
3103 if (args->i & XT_SHOW)
3104 show_certs(t, certs, cert_count, "Certificate Chain");
3105 else if (args->i & XT_SAVE)
3106 save_certs(t, certs, cert_count, domain);
3108 free_connection_certs(certs, cert_count);
3109 done:
3110 /* we close the socket first for speed */
3111 if (s != -1)
3112 close(s);
3113 stop_tls(gsession, xcred);
3115 return (0);
3119 remove_cookie(int index)
3121 int i, rv = 1;
3122 GSList *cf;
3123 SoupCookie *c;
3125 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3127 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3129 for (i = 1; cf; cf = cf->next, i++) {
3130 if (i != index)
3131 continue;
3132 c = cf->data;
3133 print_cookie("remove cookie", c);
3134 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3135 rv = 0;
3136 break;
3139 soup_cookies_free(cf);
3141 return (rv);
3145 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3147 struct domain *d;
3148 char *tmp, *header, *body, *footer;
3150 /* we set this to indicate we want to manually do navaction */
3151 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3153 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3154 title, title);
3155 footer = g_strdup("</body></html>");
3156 body = g_strdup("");
3158 /* p list */
3159 if (args->i & XT_WL_PERSISTENT) {
3160 tmp = body;
3161 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3162 g_free(tmp);
3163 RB_FOREACH(d, domain_list, wl) {
3164 if (d->handy == 0)
3165 continue;
3166 tmp = body;
3167 body = g_strdup_printf("%s%s<br>", body, d->d);
3168 g_free(tmp);
3172 /* s list */
3173 if (args->i & XT_WL_SESSION) {
3174 tmp = body;
3175 body = g_strdup_printf("%s<h2>Session</h2>", body);
3176 g_free(tmp);
3177 RB_FOREACH(d, domain_list, wl) {
3178 if (d->handy == 1)
3179 continue;
3180 tmp = body;
3181 body = g_strdup_printf("%s%s<br>", body, d->d);
3182 g_free(tmp);
3186 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3187 g_free(header);
3188 g_free(body);
3189 g_free(footer);
3190 if (wl == &js_wl)
3191 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3192 else
3193 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3194 g_free(tmp);
3195 return (0);
3199 wl_save(struct tab *t, struct karg *args, int js)
3201 char file[PATH_MAX];
3202 FILE *f;
3203 char *line = NULL, *lt = NULL;
3204 size_t linelen;
3205 const gchar *uri;
3206 char *dom = NULL, *dom_save = NULL;
3207 struct karg a;
3208 struct domain *d;
3209 GSList *cf;
3210 SoupCookie *ci, *c;
3212 if (t == NULL || args == NULL)
3213 return (1);
3215 if (runtime_settings[0] == '\0')
3216 return (1);
3218 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3219 if ((f = fopen(file, "r+")) == NULL)
3220 return (1);
3222 uri = get_uri(t->wv);
3223 dom = find_domain(uri, 1);
3224 if (uri == NULL || dom == NULL) {
3225 show_oops(t, "Can't add domain to %s white list",
3226 js ? "JavaScript" : "cookie");
3227 goto done;
3230 if (args->i & XT_WL_TOPLEVEL) {
3231 /* save domain */
3232 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3233 show_oops(t, "invalid domain: %s", dom);
3234 goto done;
3236 } else if (args->i & XT_WL_FQDN) {
3237 /* save fqdn */
3238 dom_save = dom;
3239 } else
3240 goto done;
3242 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3244 while (!feof(f)) {
3245 line = fparseln(f, &linelen, NULL, NULL, 0);
3246 if (line == NULL)
3247 continue;
3248 if (!strcmp(line, lt))
3249 goto done;
3250 free(line);
3251 line = NULL;
3254 fprintf(f, "%s\n", lt);
3256 a.i = XT_WL_ENABLE;
3257 a.i |= args->i;
3258 if (js) {
3259 d = wl_find(dom_save, &js_wl);
3260 if (!d) {
3261 settings_add("js_wl", dom_save);
3262 d = wl_find(dom_save, &js_wl);
3264 toggle_js(t, &a);
3265 } else {
3266 d = wl_find(dom_save, &c_wl);
3267 if (!d) {
3268 settings_add("cookie_wl", dom_save);
3269 d = wl_find(dom_save, &c_wl);
3271 toggle_cwl(t, &a);
3273 /* find and add to persistent jar */
3274 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3275 for (;cf; cf = cf->next) {
3276 ci = cf->data;
3277 if (!strcmp(dom_save, ci->domain) ||
3278 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3279 c = soup_cookie_copy(ci);
3280 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3283 soup_cookies_free(cf);
3285 if (d)
3286 d->handy = 1;
3288 done:
3289 if (line)
3290 free(line);
3291 if (dom)
3292 g_free(dom);
3293 if (lt)
3294 g_free(lt);
3295 fclose(f);
3297 return (0);
3301 js_show_wl(struct tab *t, struct karg *args)
3303 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3304 wl_show(t, args, "JavaScript White List", &js_wl);
3306 return (0);
3310 cookie_show_wl(struct tab *t, struct karg *args)
3312 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3313 wl_show(t, args, "Cookie White List", &c_wl);
3315 return (0);
3319 cookie_cmd(struct tab *t, struct karg *args)
3321 if (args->i & XT_SHOW)
3322 wl_show(t, args, "Cookie White List", &c_wl);
3323 else if (args->i & XT_WL_TOGGLE)
3324 toggle_cwl(t, args);
3325 else if (args->i & XT_SAVE)
3326 wl_save(t, args, 0);
3327 else if (args->i & XT_DELETE)
3328 show_oops(t, "'cookie delete' currently unimplemented");
3330 return (0);
3334 js_cmd(struct tab *t, struct karg *args)
3336 if (args->i & XT_SHOW)
3337 wl_show(t, args, "JavaScript White List", &js_wl);
3338 else if (args->i & XT_SAVE)
3339 wl_save(t, args, 1);
3340 else if (args->i & XT_WL_TOGGLE)
3341 toggle_js(t, args);
3342 else if (args->i & XT_DELETE)
3343 show_oops(t, "'js delete' currently unimplemented");
3345 return (0);
3349 add_favorite(struct tab *t, struct karg *args)
3351 char file[PATH_MAX];
3352 FILE *f;
3353 char *line = NULL;
3354 size_t urilen, linelen;
3355 const gchar *uri, *title;
3357 if (t == NULL)
3358 return (1);
3360 /* don't allow adding of xtp pages to favorites */
3361 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3362 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3363 return (1);
3366 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3367 if ((f = fopen(file, "r+")) == NULL) {
3368 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3369 return (1);
3372 title = webkit_web_view_get_title(t->wv);
3373 uri = get_uri(t->wv);
3375 if (title == NULL)
3376 title = uri;
3378 if (title == NULL || uri == NULL) {
3379 show_oops(t, "can't add page to favorites");
3380 goto done;
3383 urilen = strlen(uri);
3385 for (;;) {
3386 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3387 if (feof(f) || ferror(f))
3388 break;
3390 if (linelen == urilen && !strcmp(line, uri))
3391 goto done;
3393 free(line);
3394 line = NULL;
3397 fprintf(f, "\n%s\n%s", title, uri);
3398 done:
3399 if (line)
3400 free(line);
3401 fclose(f);
3403 update_favorite_tabs(NULL);
3405 return (0);
3409 navaction(struct tab *t, struct karg *args)
3411 WebKitWebHistoryItem *item;
3413 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3414 t->tab_id, args->i);
3416 if (t->item) {
3417 if (args->i == XT_NAV_BACK)
3418 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3419 else
3420 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3421 if (item == NULL)
3422 return (XT_CB_PASSTHROUGH);
3423 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3424 t->item = NULL;
3425 return (XT_CB_PASSTHROUGH);
3428 switch (args->i) {
3429 case XT_NAV_BACK:
3430 webkit_web_view_go_back(t->wv);
3431 break;
3432 case XT_NAV_FORWARD:
3433 webkit_web_view_go_forward(t->wv);
3434 break;
3435 case XT_NAV_RELOAD:
3436 webkit_web_view_reload(t->wv);
3437 break;
3438 case XT_NAV_RELOAD_CACHE:
3439 webkit_web_view_reload_bypass_cache(t->wv);
3440 break;
3442 return (XT_CB_PASSTHROUGH);
3446 move(struct tab *t, struct karg *args)
3448 GtkAdjustment *adjust;
3449 double pi, si, pos, ps, upper, lower, max;
3451 switch (args->i) {
3452 case XT_MOVE_DOWN:
3453 case XT_MOVE_UP:
3454 case XT_MOVE_BOTTOM:
3455 case XT_MOVE_TOP:
3456 case XT_MOVE_PAGEDOWN:
3457 case XT_MOVE_PAGEUP:
3458 case XT_MOVE_HALFDOWN:
3459 case XT_MOVE_HALFUP:
3460 adjust = t->adjust_v;
3461 break;
3462 default:
3463 adjust = t->adjust_h;
3464 break;
3467 pos = gtk_adjustment_get_value(adjust);
3468 ps = gtk_adjustment_get_page_size(adjust);
3469 upper = gtk_adjustment_get_upper(adjust);
3470 lower = gtk_adjustment_get_lower(adjust);
3471 si = gtk_adjustment_get_step_increment(adjust);
3472 pi = gtk_adjustment_get_page_increment(adjust);
3473 max = upper - ps;
3475 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3476 "max %f si %f pi %f\n",
3477 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3478 pos, ps, upper, lower, max, si, pi);
3480 switch (args->i) {
3481 case XT_MOVE_DOWN:
3482 case XT_MOVE_RIGHT:
3483 pos += si;
3484 gtk_adjustment_set_value(adjust, MIN(pos, max));
3485 break;
3486 case XT_MOVE_UP:
3487 case XT_MOVE_LEFT:
3488 pos -= si;
3489 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3490 break;
3491 case XT_MOVE_BOTTOM:
3492 case XT_MOVE_FARRIGHT:
3493 gtk_adjustment_set_value(adjust, max);
3494 break;
3495 case XT_MOVE_TOP:
3496 case XT_MOVE_FARLEFT:
3497 gtk_adjustment_set_value(adjust, lower);
3498 break;
3499 case XT_MOVE_PAGEDOWN:
3500 pos += pi;
3501 gtk_adjustment_set_value(adjust, MIN(pos, max));
3502 break;
3503 case XT_MOVE_PAGEUP:
3504 pos -= pi;
3505 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3506 break;
3507 case XT_MOVE_HALFDOWN:
3508 pos += pi / 2;
3509 gtk_adjustment_set_value(adjust, MIN(pos, max));
3510 break;
3511 case XT_MOVE_HALFUP:
3512 pos -= pi / 2;
3513 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3514 break;
3515 default:
3516 return (XT_CB_PASSTHROUGH);
3519 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3521 return (XT_CB_HANDLED);
3524 void
3525 url_set_visibility(void)
3527 struct tab *t;
3529 TAILQ_FOREACH(t, &tabs, entry) {
3530 if (show_url == 0) {
3531 gtk_widget_hide(t->toolbar);
3532 focus_webview(t);
3533 } else
3534 gtk_widget_show(t->toolbar);
3538 void
3539 notebook_tab_set_visibility(GtkNotebook *notebook)
3541 if (show_tabs == 0)
3542 gtk_notebook_set_show_tabs(notebook, FALSE);
3543 else
3544 gtk_notebook_set_show_tabs(notebook, TRUE);
3547 void
3548 statusbar_set_visibility(void)
3550 struct tab *t;
3552 TAILQ_FOREACH(t, &tabs, entry) {
3553 if (show_statusbar == 0) {
3554 gtk_widget_hide(t->statusbar);
3555 focus_webview(t);
3556 } else
3557 gtk_widget_show(t->statusbar);
3561 void
3562 url_set(struct tab *t, int enable_url_entry)
3564 GdkPixbuf *pixbuf;
3565 int progress;
3567 show_url = enable_url_entry;
3569 if (enable_url_entry) {
3570 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3571 GTK_ENTRY_ICON_PRIMARY, NULL);
3572 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3573 } else {
3574 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3575 GTK_ENTRY_ICON_PRIMARY);
3576 progress =
3577 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3578 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3579 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3580 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3581 progress);
3586 fullscreen(struct tab *t, struct karg *args)
3588 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3590 if (t == NULL)
3591 return (XT_CB_PASSTHROUGH);
3593 if (show_url == 0) {
3594 url_set(t, 1);
3595 show_tabs = 1;
3596 } else {
3597 url_set(t, 0);
3598 show_tabs = 0;
3601 url_set_visibility();
3602 notebook_tab_set_visibility(notebook);
3604 return (XT_CB_HANDLED);
3608 statusaction(struct tab *t, struct karg *args)
3610 int rv = XT_CB_HANDLED;
3612 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3614 if (t == NULL)
3615 return (XT_CB_PASSTHROUGH);
3617 switch (args->i) {
3618 case XT_STATUSBAR_SHOW:
3619 if (show_statusbar == 0) {
3620 show_statusbar = 1;
3621 statusbar_set_visibility();
3623 break;
3624 case XT_STATUSBAR_HIDE:
3625 if (show_statusbar == 1) {
3626 show_statusbar = 0;
3627 statusbar_set_visibility();
3629 break;
3631 return (rv);
3635 urlaction(struct tab *t, struct karg *args)
3637 int rv = XT_CB_HANDLED;
3639 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3641 if (t == NULL)
3642 return (XT_CB_PASSTHROUGH);
3644 switch (args->i) {
3645 case XT_URL_SHOW:
3646 if (show_url == 0) {
3647 url_set(t, 1);
3648 url_set_visibility();
3650 break;
3651 case XT_URL_HIDE:
3652 if (show_url == 1) {
3653 url_set(t, 0);
3654 url_set_visibility();
3656 break;
3658 return (rv);
3662 tabaction(struct tab *t, struct karg *args)
3664 int rv = XT_CB_HANDLED;
3665 char *url = args->s;
3666 struct undo *u;
3668 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3670 if (t == NULL)
3671 return (XT_CB_PASSTHROUGH);
3673 switch (args->i) {
3674 case XT_TAB_NEW:
3675 if (strlen(url) > 0)
3676 create_new_tab(url, NULL, 1);
3677 else
3678 create_new_tab(NULL, NULL, 1);
3679 break;
3680 case XT_TAB_DELETE:
3681 delete_tab(t);
3682 break;
3683 case XT_TAB_DELQUIT:
3684 if (gtk_notebook_get_n_pages(notebook) > 1)
3685 delete_tab(t);
3686 else
3687 quit(t, args);
3688 break;
3689 case XT_TAB_OPEN:
3690 if (strlen(url) > 0)
3692 else {
3693 rv = XT_CB_PASSTHROUGH;
3694 goto done;
3696 load_uri(t, url);
3697 break;
3698 case XT_TAB_SHOW:
3699 if (show_tabs == 0) {
3700 show_tabs = 1;
3701 notebook_tab_set_visibility(notebook);
3703 break;
3704 case XT_TAB_HIDE:
3705 if (show_tabs == 1) {
3706 show_tabs = 0;
3707 notebook_tab_set_visibility(notebook);
3709 break;
3710 case XT_TAB_UNDO_CLOSE:
3711 if (undo_count == 0) {
3712 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3713 goto done;
3714 } else {
3715 undo_count--;
3716 u = TAILQ_FIRST(&undos);
3717 create_new_tab(u->uri, u, 1);
3719 TAILQ_REMOVE(&undos, u, entry);
3720 g_free(u->uri);
3721 /* u->history is freed in create_new_tab() */
3722 g_free(u);
3724 break;
3725 default:
3726 rv = XT_CB_PASSTHROUGH;
3727 goto done;
3730 done:
3731 if (args->s) {
3732 g_free(args->s);
3733 args->s = NULL;
3736 return (rv);
3740 resizetab(struct tab *t, struct karg *args)
3742 if (t == NULL || args == NULL) {
3743 show_oops_s("resizetab invalid parameters");
3744 return (XT_CB_PASSTHROUGH);
3747 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3748 t->tab_id, args->i);
3750 adjustfont_webkit(t, args->i);
3752 return (XT_CB_HANDLED);
3756 movetab(struct tab *t, struct karg *args)
3758 struct tab *tt;
3759 int x;
3761 if (t == NULL || args == NULL) {
3762 show_oops_s("movetab invalid parameters");
3763 return (XT_CB_PASSTHROUGH);
3766 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3767 t->tab_id, args->i);
3769 if (args->i == XT_TAB_INVALID)
3770 return (XT_CB_PASSTHROUGH);
3772 if (args->i < XT_TAB_INVALID) {
3773 /* next or previous tab */
3774 if (TAILQ_EMPTY(&tabs))
3775 return (XT_CB_PASSTHROUGH);
3777 switch (args->i) {
3778 case XT_TAB_NEXT:
3779 if (strlen(args->s) == 0) {
3780 /* if at the last page, loop around to the first */
3781 if (gtk_notebook_get_current_page(notebook) ==
3782 gtk_notebook_get_n_pages(notebook) - 1)
3783 gtk_notebook_set_current_page(notebook, 0);
3784 else
3785 gtk_notebook_next_page(notebook);
3786 } else {
3787 x = atoi(args->s) - 1;
3788 if (x < 0)
3789 return (XT_CB_PASSTHROUGH);
3791 if (t->tab_id == x) {
3792 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3793 return (XT_CB_HANDLED);
3795 TAILQ_FOREACH(tt, &tabs, entry) {
3796 if (tt->tab_id == x) {
3797 gtk_notebook_set_current_page(notebook, x);
3798 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3799 break;
3804 break;
3805 case XT_TAB_PREV:
3806 /* if at the first page, loop around to the last */
3807 if (gtk_notebook_current_page(notebook) == 0)
3808 gtk_notebook_set_current_page(notebook,
3809 gtk_notebook_get_n_pages(notebook) - 1);
3810 else
3811 gtk_notebook_prev_page(notebook);
3812 break;
3813 case XT_TAB_FIRST:
3814 gtk_notebook_set_current_page(notebook, 0);
3815 break;
3816 case XT_TAB_LAST:
3817 gtk_notebook_set_current_page(notebook, -1);
3818 break;
3819 default:
3820 return (XT_CB_PASSTHROUGH);
3823 return (XT_CB_HANDLED);
3826 return (XT_CB_HANDLED);
3830 command(struct tab *t, struct karg *args)
3832 char *s = NULL, *ss = NULL;
3833 GdkColor color;
3834 const gchar *uri;
3836 if (t == NULL || args == NULL) {
3837 show_oops_s("command invalid parameters");
3838 return (XT_CB_PASSTHROUGH);
3841 switch (args->i) {
3842 case '/':
3843 s = "/";
3844 break;
3845 case '?':
3846 s = "?";
3847 break;
3848 case ':':
3849 s = ":";
3850 break;
3851 case XT_CMD_OPEN:
3852 s = ":open ";
3853 break;
3854 case XT_CMD_TABNEW:
3855 s = ":tabnew ";
3856 break;
3857 case XT_CMD_OPEN_CURRENT:
3858 s = ":open ";
3859 /* FALL THROUGH */
3860 case XT_CMD_TABNEW_CURRENT:
3861 if (!s) /* FALL THROUGH? */
3862 s = ":tabnew ";
3863 if ((uri = get_uri(t->wv)) != NULL) {
3864 ss = g_strdup_printf("%s%s", s, uri);
3865 s = ss;
3867 break;
3868 default:
3869 show_oops(t, "command: invalid opcode %d", args->i);
3870 return (XT_CB_PASSTHROUGH);
3873 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3875 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3876 gdk_color_parse(XT_COLOR_WHITE, &color);
3877 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3878 show_cmd(t);
3879 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3880 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3882 if (ss)
3883 g_free(ss);
3885 return (XT_CB_HANDLED);
3889 * Return a new string with a download row (in html)
3890 * appended. Old string is freed.
3892 char *
3893 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3896 WebKitDownloadStatus stat;
3897 char *status_html = NULL, *cmd_html = NULL, *new_html;
3898 gdouble progress;
3899 char cur_sz[FMT_SCALED_STRSIZE];
3900 char tot_sz[FMT_SCALED_STRSIZE];
3901 char *xtp_prefix;
3903 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3905 /* All actions wil take this form:
3906 * xxxt://class/seskey
3908 xtp_prefix = g_strdup_printf("%s%d/%s/",
3909 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3911 stat = webkit_download_get_status(dl->download);
3913 switch (stat) {
3914 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3915 status_html = g_strdup_printf("Finished");
3916 cmd_html = g_strdup_printf(
3917 "<a href='%s%d/%d'>Remove</a>",
3918 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3919 break;
3920 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3921 /* gather size info */
3922 progress = 100 * webkit_download_get_progress(dl->download);
3924 fmt_scaled(
3925 webkit_download_get_current_size(dl->download), cur_sz);
3926 fmt_scaled(
3927 webkit_download_get_total_size(dl->download), tot_sz);
3929 status_html = g_strdup_printf(
3930 "<div style='width: 100%%' align='center'>"
3931 "<div class='progress-outer'>"
3932 "<div class='progress-inner' style='width: %.2f%%'>"
3933 "</div></div></div>"
3934 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3935 progress, cur_sz, tot_sz, progress);
3937 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3938 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3940 break;
3941 /* LLL */
3942 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3943 status_html = g_strdup_printf("Cancelled");
3944 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3945 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3946 break;
3947 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3948 status_html = g_strdup_printf("Error!");
3949 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3950 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3951 break;
3952 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3953 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3954 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3955 status_html = g_strdup_printf("Starting");
3956 break;
3957 default:
3958 show_oops(t, "%s: unknown download status", __func__);
3961 new_html = g_strdup_printf(
3962 "%s\n<tr><td>%s</td><td>%s</td>"
3963 "<td style='text-align:center'>%s</td></tr>\n",
3964 html, basename(webkit_download_get_destination_uri(dl->download)),
3965 status_html, cmd_html);
3966 g_free(html);
3968 if (status_html)
3969 g_free(status_html);
3971 if (cmd_html)
3972 g_free(cmd_html);
3974 g_free(xtp_prefix);
3976 return new_html;
3980 * update all download tabs apart from one. Pass NULL if
3981 * you want to update all.
3983 void
3984 update_download_tabs(struct tab *apart_from)
3986 struct tab *t;
3987 if (!updating_dl_tabs) {
3988 updating_dl_tabs = 1; /* stop infinite recursion */
3989 TAILQ_FOREACH(t, &tabs, entry)
3990 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3991 && (t != apart_from))
3992 xtp_page_dl(t, NULL);
3993 updating_dl_tabs = 0;
3998 * update all cookie tabs apart from one. Pass NULL if
3999 * you want to update all.
4001 void
4002 update_cookie_tabs(struct tab *apart_from)
4004 struct tab *t;
4005 if (!updating_cl_tabs) {
4006 updating_cl_tabs = 1; /* stop infinite recursion */
4007 TAILQ_FOREACH(t, &tabs, entry)
4008 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4009 && (t != apart_from))
4010 xtp_page_cl(t, NULL);
4011 updating_cl_tabs = 0;
4016 * update all history tabs apart from one. Pass NULL if
4017 * you want to update all.
4019 void
4020 update_history_tabs(struct tab *apart_from)
4022 struct tab *t;
4024 if (!updating_hl_tabs) {
4025 updating_hl_tabs = 1; /* stop infinite recursion */
4026 TAILQ_FOREACH(t, &tabs, entry)
4027 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4028 && (t != apart_from))
4029 xtp_page_hl(t, NULL);
4030 updating_hl_tabs = 0;
4034 /* cookie management XTP page */
4036 xtp_page_cl(struct tab *t, struct karg *args)
4038 char *header, *body, *footer, *page, *tmp;
4039 int i = 1; /* all ids start 1 */
4040 GSList *sc, *pc, *pc_start;
4041 SoupCookie *c;
4042 char *type, *table_headers;
4043 char *last_domain = strdup("");
4045 DNPRINTF(XT_D_CMD, "%s", __func__);
4047 if (t == NULL) {
4048 show_oops_s("%s invalid parameters", __func__);
4049 return (1);
4051 /* mark this tab as cookie jar */
4052 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4054 /* Generate a new session key */
4055 if (!updating_cl_tabs)
4056 generate_xtp_session_key(&cl_session_key);
4058 /* header */
4059 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4060 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4061 "</head><body><h1>Cookie Jar</h1>\n");
4063 /* table headers */
4064 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4065 "<th>Type</th>"
4066 "<th>Name</th>"
4067 "<th>Value</th>"
4068 "<th>Path</th>"
4069 "<th>Expires</th>"
4070 "<th>Secure</th>"
4071 "<th>HTTP<br />only</th>"
4072 "<th>Rm</th></tr>\n");
4074 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4075 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4076 pc_start = pc;
4078 body = NULL;
4079 for (; sc; sc = sc->next) {
4080 c = sc->data;
4082 if (strcmp(last_domain, c->domain) != 0) {
4083 /* new domain */
4084 free(last_domain);
4085 last_domain = strdup(c->domain);
4087 if (body != NULL) {
4088 tmp = body;
4089 body = g_strdup_printf("%s</table></div>"
4090 "<h2>%s</h2>%s\n",
4091 body, c->domain, table_headers);
4092 g_free(tmp);
4093 } else {
4094 /* first domain */
4095 body = g_strdup_printf("<h2>%s</h2>%s\n",
4096 c->domain, table_headers);
4100 type = "Session";
4101 for (pc = pc_start; pc; pc = pc->next)
4102 if (soup_cookie_equal(pc->data, c)) {
4103 type = "Session + Persistent";
4104 break;
4107 tmp = body;
4108 body = g_strdup_printf(
4109 "%s\n<tr>"
4110 "<td style='width: text-align: center'>%s</td>"
4111 "<td style='width: 1px'>%s</td>"
4112 "<td style='width=70%%;overflow: visible'>"
4113 " <textarea rows='4'>%s</textarea>"
4114 "</td>"
4115 "<td>%s</td>"
4116 "<td>%s</td>"
4117 "<td style='width: 1px; text-align: center'>%d</td>"
4118 "<td style='width: 1px; text-align: center'>%d</td>"
4119 "<td style='width: 1px; text-align: center'>"
4120 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4121 body,
4122 type,
4123 c->name,
4124 c->value,
4125 c->path,
4126 c->expires ?
4127 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4128 c->secure,
4129 c->http_only,
4131 XT_XTP_STR,
4132 XT_XTP_CL,
4133 cl_session_key,
4134 XT_XTP_CL_REMOVE,
4138 g_free(tmp);
4139 i++;
4142 soup_cookies_free(sc);
4143 soup_cookies_free(pc);
4145 /* small message if there are none */
4146 if (i == 1) {
4147 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4148 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4151 /* footer */
4152 footer = g_strdup_printf("</table></div></body></html>");
4154 page = g_strdup_printf("%s%s%s", header, body, footer);
4156 g_free(header);
4157 g_free(body);
4158 g_free(footer);
4159 g_free(table_headers);
4160 g_free(last_domain);
4162 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4163 update_cookie_tabs(t);
4165 g_free(page);
4167 return (0);
4171 xtp_page_hl(struct tab *t, struct karg *args)
4173 char *header, *body, *footer, *page, *tmp;
4174 struct history *h;
4175 int i = 1; /* all ids start 1 */
4177 DNPRINTF(XT_D_CMD, "%s", __func__);
4179 if (t == NULL) {
4180 show_oops_s("%s invalid parameters", __func__);
4181 return (1);
4184 /* mark this tab as history manager */
4185 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4187 /* Generate a new session key */
4188 if (!updating_hl_tabs)
4189 generate_xtp_session_key(&hl_session_key);
4191 /* header */
4192 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4193 "<title>History</title>\n"
4194 "%s"
4195 "</head>"
4196 "<h1>History</h1>\n",
4197 XT_PAGE_STYLE);
4199 /* body */
4200 body = g_strdup_printf("<div align='center'><table><tr>"
4201 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4203 RB_FOREACH_REVERSE(h, history_list, &hl) {
4204 tmp = body;
4205 body = g_strdup_printf(
4206 "%s\n<tr>"
4207 "<td><a href='%s'>%s</a></td>"
4208 "<td>%s</td>"
4209 "<td style='text-align: center'>"
4210 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4211 body, h->uri, h->uri, h->title,
4212 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4213 XT_XTP_HL_REMOVE, i);
4215 g_free(tmp);
4216 i++;
4219 /* small message if there are none */
4220 if (i == 1) {
4221 tmp = body;
4222 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4223 "colspan='3'>No History</td></tr>\n", body);
4224 g_free(tmp);
4227 /* footer */
4228 footer = g_strdup_printf("</table></div></body></html>");
4230 page = g_strdup_printf("%s%s%s", header, body, footer);
4233 * update all history manager tabs as the xtp session
4234 * key has now changed. No need to update the current tab.
4235 * Already did that above.
4237 update_history_tabs(t);
4239 g_free(header);
4240 g_free(body);
4241 g_free(footer);
4243 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4244 g_free(page);
4246 return (0);
4250 * Generate a web page detailing the status of any downloads
4253 xtp_page_dl(struct tab *t, struct karg *args)
4255 struct download *dl;
4256 char *header, *body, *footer, *page, *tmp;
4257 char *ref;
4258 int n_dl = 1;
4260 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4262 if (t == NULL) {
4263 show_oops_s("%s invalid parameters", __func__);
4264 return (1);
4266 /* mark as a download manager tab */
4267 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4270 * Generate a new session key for next page instance.
4271 * This only happens for the top level call to xtp_page_dl()
4272 * in which case updating_dl_tabs is 0.
4274 if (!updating_dl_tabs)
4275 generate_xtp_session_key(&dl_session_key);
4277 /* header - with refresh so as to update */
4278 if (refresh_interval >= 1)
4279 ref = g_strdup_printf(
4280 "<meta http-equiv='refresh' content='%u"
4281 ";url=%s%d/%s/%d' />\n",
4282 refresh_interval,
4283 XT_XTP_STR,
4284 XT_XTP_DL,
4285 dl_session_key,
4286 XT_XTP_DL_LIST);
4287 else
4288 ref = g_strdup("");
4291 header = g_strdup_printf(
4292 "%s\n<head>"
4293 "<title>Downloads</title>\n%s%s</head>\n",
4294 XT_DOCTYPE XT_HTML_TAG,
4295 ref,
4296 XT_PAGE_STYLE);
4298 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4299 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4300 "</p><table><tr><th style='width: 60%%'>"
4301 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4302 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4304 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4305 body = xtp_page_dl_row(t, body, dl);
4306 n_dl++;
4309 /* message if no downloads in list */
4310 if (n_dl == 1) {
4311 tmp = body;
4312 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4313 " style='text-align: center'>"
4314 "No downloads</td></tr>\n", body);
4315 g_free(tmp);
4318 /* footer */
4319 footer = g_strdup_printf("</table></div></body></html>");
4321 page = g_strdup_printf("%s%s%s", header, body, footer);
4325 * update all download manager tabs as the xtp session
4326 * key has now changed. No need to update the current tab.
4327 * Already did that above.
4329 update_download_tabs(t);
4331 g_free(ref);
4332 g_free(header);
4333 g_free(body);
4334 g_free(footer);
4336 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4337 g_free(page);
4339 return (0);
4343 search(struct tab *t, struct karg *args)
4345 gboolean d;
4347 if (t == NULL || args == NULL) {
4348 show_oops_s("search invalid parameters");
4349 return (1);
4351 if (t->search_text == NULL) {
4352 if (global_search == NULL)
4353 return (XT_CB_PASSTHROUGH);
4354 else {
4355 t->search_text = g_strdup(global_search);
4356 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4357 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4361 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4362 t->tab_id, args->i, t->search_forward, t->search_text);
4364 switch (args->i) {
4365 case XT_SEARCH_NEXT:
4366 d = t->search_forward;
4367 break;
4368 case XT_SEARCH_PREV:
4369 d = !t->search_forward;
4370 break;
4371 default:
4372 return (XT_CB_PASSTHROUGH);
4375 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4377 return (XT_CB_HANDLED);
4380 struct settings_args {
4381 char **body;
4382 int i;
4385 void
4386 print_setting(struct settings *s, char *val, void *cb_args)
4388 char *tmp, *color;
4389 struct settings_args *sa = cb_args;
4391 if (sa == NULL)
4392 return;
4394 if (s->flags & XT_SF_RUNTIME)
4395 color = "#22cc22";
4396 else
4397 color = "#cccccc";
4399 tmp = *sa->body;
4400 *sa->body = g_strdup_printf(
4401 "%s\n<tr>"
4402 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4403 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4404 *sa->body,
4405 color,
4406 s->name,
4407 color,
4410 g_free(tmp);
4411 sa->i++;
4415 set(struct tab *t, struct karg *args)
4417 char *header, *body, *footer, *page, *tmp;
4418 int i = 1;
4419 struct settings_args sa;
4421 bzero(&sa, sizeof sa);
4422 sa.body = &body;
4424 /* header */
4425 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4426 "\n<head><title>Settings</title>\n"
4427 "</head><body><h1>Settings</h1>\n");
4429 /* body */
4430 body = g_strdup_printf("<div align='center'><table><tr>"
4431 "<th align='left'>Setting</th>"
4432 "<th align='left'>Value</th></tr>\n");
4434 settings_walk(print_setting, &sa);
4435 i = sa.i;
4437 /* small message if there are none */
4438 if (i == 1) {
4439 tmp = body;
4440 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4441 "colspan='2'>No settings</td></tr>\n", body);
4442 g_free(tmp);
4445 /* footer */
4446 footer = g_strdup_printf("</table></div></body></html>");
4448 page = g_strdup_printf("%s%s%s", header, body, footer);
4450 g_free(header);
4451 g_free(body);
4452 g_free(footer);
4454 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4456 return (XT_CB_PASSTHROUGH);
4460 session_save(struct tab *t, char *filename)
4462 struct karg a;
4463 int rv = 1;
4465 if (strlen(filename) == 0)
4466 goto done;
4468 if (filename[0] == '.' || filename[0] == '/')
4469 goto done;
4471 a.s = filename;
4472 if (save_tabs(t, &a))
4473 goto done;
4474 strlcpy(named_session, filename, sizeof named_session);
4476 rv = 0;
4477 done:
4478 return (rv);
4482 session_open(struct tab *t, char *filename)
4484 struct karg a;
4485 int rv = 1;
4487 if (strlen(filename) == 0)
4488 goto done;
4490 if (filename[0] == '.' || filename[0] == '/')
4491 goto done;
4493 a.s = filename;
4494 a.i = XT_SES_CLOSETABS;
4495 if (open_tabs(t, &a))
4496 goto done;
4498 strlcpy(named_session, filename, sizeof named_session);
4500 rv = 0;
4501 done:
4502 return (rv);
4506 session_delete(struct tab *t, char *filename)
4508 char file[PATH_MAX];
4509 int rv = 1;
4511 if (strlen(filename) == 0)
4512 goto done;
4514 if (filename[0] == '.' || filename[0] == '/')
4515 goto done;
4517 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4518 if (unlink(file))
4519 goto done;
4521 if (!strcmp(filename, named_session))
4522 strlcpy(named_session, XT_SAVED_TABS_FILE,
4523 sizeof named_session);
4525 rv = 0;
4526 done:
4527 return (rv);
4531 session_cmd(struct tab *t, struct karg *args)
4533 char *filename = args->s;
4535 if (t == NULL)
4536 return (1);
4538 if (args->i & XT_SHOW)
4539 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4540 XT_SAVED_TABS_FILE : named_session);
4541 else if (args->i & XT_SAVE) {
4542 if (session_save(t, filename)) {
4543 show_oops(t, "Can't save session: %s",
4544 filename ? filename : "INVALID");
4545 goto done;
4547 } else if (args->i & XT_OPEN) {
4548 if (session_open(t, filename)) {
4549 show_oops(t, "Can't open session: %s",
4550 filename ? filename : "INVALID");
4551 goto done;
4553 } else if (args->i & XT_DELETE) {
4554 if (session_delete(t, filename)) {
4555 show_oops(t, "Can't delete session: %s",
4556 filename ? filename : "INVALID");
4557 goto done;
4560 done:
4561 return (XT_CB_PASSTHROUGH);
4565 * Make a hardcopy of the page
4568 print_page(struct tab *t, struct karg *args)
4570 WebKitWebFrame *frame;
4571 GtkPageSetup *ps;
4572 GtkPrintOperation *op;
4573 GtkPrintOperationAction action;
4574 GtkPrintOperationResult print_res;
4575 GError *g_err = NULL;
4576 int marg_l, marg_r, marg_t, marg_b;
4578 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4580 ps = gtk_page_setup_new();
4581 op = gtk_print_operation_new();
4582 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4583 frame = webkit_web_view_get_main_frame(t->wv);
4585 /* the default margins are too small, so we will bump them */
4586 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4587 XT_PRINT_EXTRA_MARGIN;
4588 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4589 XT_PRINT_EXTRA_MARGIN;
4590 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4591 XT_PRINT_EXTRA_MARGIN;
4592 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4593 XT_PRINT_EXTRA_MARGIN;
4595 /* set margins */
4596 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4597 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4598 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4599 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4601 gtk_print_operation_set_default_page_setup(op, ps);
4603 /* this appears to free 'op' and 'ps' */
4604 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4606 /* check it worked */
4607 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4608 show_oops_s("can't print: %s", g_err->message);
4609 g_error_free (g_err);
4610 return (1);
4613 return (0);
4617 go_home(struct tab *t, struct karg *args)
4619 load_uri(t, home);
4620 return (0);
4624 restart(struct tab *t, struct karg *args)
4626 struct karg a;
4628 a.s = XT_RESTART_TABS_FILE;
4629 save_tabs(t, &a);
4630 execvp(start_argv[0], start_argv);
4631 /* NOTREACHED */
4633 return (0);
4636 #define CTRL GDK_CONTROL_MASK
4637 #define MOD1 GDK_MOD1_MASK
4638 #define SHFT GDK_SHIFT_MASK
4640 /* inherent to GTK not all keys will be caught at all times */
4641 /* XXX sort key bindings */
4642 struct key_binding {
4643 char *cmd;
4644 guint mask;
4645 guint use_in_entry;
4646 guint key;
4647 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4648 } keys[] = {
4649 { "cookiejar", MOD1, 0, GDK_j },
4650 { "downloadmgr", MOD1, 0, GDK_d },
4651 { "history", MOD1, 0, GDK_h },
4652 { "print", CTRL, 0, GDK_p },
4653 { "search", 0, 0, GDK_slash },
4654 { "searchb", 0, 0, GDK_question },
4655 { "command", 0, 0, GDK_colon },
4656 { "qa", CTRL, 0, GDK_q },
4657 { "restart", MOD1, 0, GDK_q },
4658 { "js toggle", CTRL, 0, GDK_j },
4659 { "cookie toggle", MOD1, 0, GDK_c },
4660 { "togglesrc", CTRL, 0, GDK_s },
4661 { "yankuri", 0, 0, GDK_y },
4662 { "pasteuricur", 0, 0, GDK_p },
4663 { "pasteurinew", 0, 0, GDK_P },
4665 /* search */
4666 { "searchnext", 0, 0, GDK_n },
4667 { "searchprevious", 0, 0, GDK_N },
4669 /* focus */
4670 { "focusaddress", 0, 0, GDK_F6 },
4671 { "focussearch", 0, 0, GDK_F7 },
4673 /* hinting */
4674 { "hinting", 0, 0, GDK_f },
4676 /* custom stylesheet */
4677 { "userstyle", 0, 0, GDK_i },
4679 /* navigation */
4680 { "goback", 0, 0, GDK_BackSpace },
4681 { "goback", MOD1, 0, GDK_Left },
4682 { "goforward", SHFT, 0, GDK_BackSpace },
4683 { "goforward", MOD1, 0, GDK_Right },
4684 { "reload", 0, 0, GDK_F5 },
4685 { "reload", CTRL, 0, GDK_r },
4686 { "reloadforce", CTRL, 0, GDK_R },
4687 { "reload", CTRL, 0, GDK_l },
4688 { "favorites", MOD1, 1, GDK_f },
4690 /* vertical movement */
4691 { "scrolldown", 0, 0, GDK_j },
4692 { "scrolldown", 0, 0, GDK_Down },
4693 { "scrollup", 0, 0, GDK_Up },
4694 { "scrollup", 0, 0, GDK_k },
4695 { "scrollbottom", 0, 0, GDK_G },
4696 { "scrollbottom", 0, 0, GDK_End },
4697 { "scrolltop", 0, 0, GDK_Home },
4698 { "scrolltop", 0, 0, GDK_g },
4699 { "scrollpagedown", 0, 0, GDK_space },
4700 { "scrollpagedown", CTRL, 0, GDK_f },
4701 { "scrollhalfdown", CTRL, 0, GDK_d },
4702 { "scrollpagedown", 0, 0, GDK_Page_Down },
4703 { "scrollpageup", 0, 0, GDK_Page_Up },
4704 { "scrollpageup", CTRL, 0, GDK_b },
4705 { "scrollhalfup", CTRL, 0, GDK_u },
4706 /* horizontal movement */
4707 { "scrollright", 0, 0, GDK_l },
4708 { "scrollright", 0, 0, GDK_Right },
4709 { "scrollleft", 0, 0, GDK_Left },
4710 { "scrollleft", 0, 0, GDK_h },
4711 { "scrollfarright", 0, 0, GDK_dollar },
4712 { "scrollfarleft", 0, 0, GDK_0 },
4714 /* tabs */
4715 { "tabnew", CTRL, 0, GDK_t },
4716 { "tabclose", CTRL, 1, GDK_w },
4717 { "tabundoclose", 0, 0, GDK_U },
4718 { "tabnext 1", CTRL, 0, GDK_1 },
4719 { "tabnext 2", CTRL, 0, GDK_2 },
4720 { "tabnext 3", CTRL, 0, GDK_3 },
4721 { "tabnext 4", CTRL, 0, GDK_4 },
4722 { "tabnext 5", CTRL, 0, GDK_5 },
4723 { "tabnext 6", CTRL, 0, GDK_6 },
4724 { "tabnext 7", CTRL, 0, GDK_7 },
4725 { "tabnext 8", CTRL, 0, GDK_8 },
4726 { "tabnext 9", CTRL, 0, GDK_9 },
4727 { "tabnext 10", CTRL, 0, GDK_0 },
4728 { "tabfirst", CTRL, 0, GDK_less },
4729 { "tablast", CTRL, 0, GDK_greater },
4730 { "tabprevious", CTRL, 0, GDK_Left },
4731 { "tabnext", CTRL, 0, GDK_Right },
4732 { "focusout", CTRL, 0, GDK_minus },
4733 { "focusin", CTRL, 0, GDK_plus },
4734 { "focusin", CTRL, 0, GDK_equal },
4736 /* command aliases (handy when -S flag is used) */
4737 { "promptopen", 0, 0, GDK_F9 },
4738 { "promptopencurrent", 0, 0, GDK_F10 },
4739 { "prompttabnew", 0, 0, GDK_F11 },
4740 { "prompttabnewcurrent",0, 0, GDK_F12 },
4742 TAILQ_HEAD(keybinding_list, key_binding);
4744 void
4745 walk_kb(struct settings *s,
4746 void (*cb)(struct settings *, char *, void *), void *cb_args)
4748 struct key_binding *k;
4749 char str[1024];
4751 if (s == NULL || cb == NULL) {
4752 show_oops_s("walk_kb invalid parameters");
4753 return;
4756 TAILQ_FOREACH(k, &kbl, entry) {
4757 if (k->cmd == NULL)
4758 continue;
4759 str[0] = '\0';
4761 /* sanity */
4762 if (gdk_keyval_name(k->key) == NULL)
4763 continue;
4765 strlcat(str, k->cmd, sizeof str);
4766 strlcat(str, ",", sizeof str);
4768 if (k->mask & GDK_SHIFT_MASK)
4769 strlcat(str, "S-", sizeof str);
4770 if (k->mask & GDK_CONTROL_MASK)
4771 strlcat(str, "C-", sizeof str);
4772 if (k->mask & GDK_MOD1_MASK)
4773 strlcat(str, "M1-", sizeof str);
4774 if (k->mask & GDK_MOD2_MASK)
4775 strlcat(str, "M2-", sizeof str);
4776 if (k->mask & GDK_MOD3_MASK)
4777 strlcat(str, "M3-", sizeof str);
4778 if (k->mask & GDK_MOD4_MASK)
4779 strlcat(str, "M4-", sizeof str);
4780 if (k->mask & GDK_MOD5_MASK)
4781 strlcat(str, "M5-", sizeof str);
4783 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4784 cb(s, str, cb_args);
4788 void
4789 init_keybindings(void)
4791 int i;
4792 struct key_binding *k;
4794 for (i = 0; i < LENGTH(keys); i++) {
4795 k = g_malloc0(sizeof *k);
4796 k->cmd = keys[i].cmd;
4797 k->mask = keys[i].mask;
4798 k->use_in_entry = keys[i].use_in_entry;
4799 k->key = keys[i].key;
4800 TAILQ_INSERT_HEAD(&kbl, k, entry);
4802 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4803 k->cmd ? k->cmd : "unnamed key");
4807 void
4808 keybinding_clearall(void)
4810 struct key_binding *k, *next;
4812 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4813 next = TAILQ_NEXT(k, entry);
4814 if (k->cmd == NULL)
4815 continue;
4817 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4818 k->cmd ? k->cmd : "unnamed key");
4819 TAILQ_REMOVE(&kbl, k, entry);
4820 g_free(k);
4825 keybinding_add(char *cmd, char *key, int use_in_entry)
4827 struct key_binding *k;
4828 guint keyval, mask = 0;
4829 int i;
4831 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4833 /* Keys which are to be used in entry have been prefixed with an
4834 * exclamation mark. */
4835 if (use_in_entry)
4836 key++;
4838 /* find modifier keys */
4839 if (strstr(key, "S-"))
4840 mask |= GDK_SHIFT_MASK;
4841 if (strstr(key, "C-"))
4842 mask |= GDK_CONTROL_MASK;
4843 if (strstr(key, "M1-"))
4844 mask |= GDK_MOD1_MASK;
4845 if (strstr(key, "M2-"))
4846 mask |= GDK_MOD2_MASK;
4847 if (strstr(key, "M3-"))
4848 mask |= GDK_MOD3_MASK;
4849 if (strstr(key, "M4-"))
4850 mask |= GDK_MOD4_MASK;
4851 if (strstr(key, "M5-"))
4852 mask |= GDK_MOD5_MASK;
4854 /* find keyname */
4855 for (i = strlen(key) - 1; i > 0; i--)
4856 if (key[i] == '-')
4857 key = &key[i + 1];
4859 /* validate keyname */
4860 keyval = gdk_keyval_from_name(key);
4861 if (keyval == GDK_VoidSymbol) {
4862 warnx("invalid keybinding name %s", key);
4863 return (1);
4865 /* must run this test too, gtk+ doesn't handle 10 for example */
4866 if (gdk_keyval_name(keyval) == NULL) {
4867 warnx("invalid keybinding name %s", key);
4868 return (1);
4871 /* Remove eventual dupes. */
4872 TAILQ_FOREACH(k, &kbl, entry)
4873 if (k->key == keyval && k->mask == mask) {
4874 TAILQ_REMOVE(&kbl, k, entry);
4875 g_free(k);
4876 break;
4879 /* add keyname */
4880 k = g_malloc0(sizeof *k);
4881 k->cmd = g_strdup(cmd);
4882 k->mask = mask;
4883 k->use_in_entry = use_in_entry;
4884 k->key = keyval;
4886 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4887 k->cmd,
4888 k->mask,
4889 k->use_in_entry,
4890 k->key);
4891 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4892 k->cmd, gdk_keyval_name(keyval));
4894 TAILQ_INSERT_HEAD(&kbl, k, entry);
4896 return (0);
4900 add_kb(struct settings *s, char *entry)
4902 char *kb, *key;
4904 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4906 /* clearall is special */
4907 if (!strcmp(entry, "clearall")) {
4908 keybinding_clearall();
4909 return (0);
4912 kb = strstr(entry, ",");
4913 if (kb == NULL)
4914 return (1);
4915 *kb = '\0';
4916 key = kb + 1;
4918 return (keybinding_add(entry, key, key[0] == '!'));
4921 struct cmd {
4922 char *cmd;
4923 int level;
4924 int (*func)(struct tab *, struct karg *);
4925 struct karg arg;
4926 bool userarg; /* allow free text arg */
4927 } cmds[] = {
4928 { "command", 0, command, {.i = ':'}, FALSE },
4929 { "search", 0, command, {.i = '/'}, FALSE },
4930 { "searchb", 0, command, {.i = '?'}, FALSE },
4931 { "togglesrc", 0, toggle_src, {0}, FALSE },
4933 /* yanking and pasting */
4934 { "yankuri", 0, yank_uri, {0}, FALSE },
4935 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4936 { "pasteuricur", 0, paste_uri, {.i = XT_PASTE_CURRENT_TAB}, FALSE },
4937 { "pasteurinew", 0, paste_uri, {.i = XT_PASTE_NEW_TAB}, FALSE },
4939 /* search */
4940 { "searchnext", 0, search, {.i = XT_SEARCH_NEXT}, FALSE },
4941 { "searchprevious", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
4943 /* focus */
4944 { "focusaddress", 0, focus, {.i = XT_FOCUS_URI}, FALSE },
4945 { "focussearch", 0, focus, {.i = XT_FOCUS_SEARCH}, FALSE },
4947 /* hinting */
4948 { "hinting", 0, hint, {.i = 0}, FALSE },
4950 /* custom stylesheet */
4951 { "userstyle", 0, userstyle, {.i = 0 }, FALSE },
4953 /* navigation */
4954 { "goback", 0, navaction, {.i = XT_NAV_BACK}, FALSE },
4955 { "goforward", 0, navaction, {.i = XT_NAV_FORWARD}, FALSE },
4956 { "reload", 0, navaction, {.i = XT_NAV_RELOAD}, FALSE },
4957 { "reloadforce", 0, navaction, {.i = XT_NAV_RELOAD_CACHE}, FALSE },
4959 /* vertical movement */
4960 { "scrolldown", 0, move, {.i = XT_MOVE_DOWN}, FALSE },
4961 { "scrollup", 0, move, {.i = XT_MOVE_UP}, FALSE },
4962 { "scrollbottom", 0, move, {.i = XT_MOVE_BOTTOM}, FALSE },
4963 { "scrolltop", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4964 { "1", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4965 { "scrollhalfdown", 0, move, {.i = XT_MOVE_HALFDOWN},FALSE },
4966 { "scrollhalfup", 0, move, {.i = XT_MOVE_HALFUP}, FALSE },
4967 { "scrollpagedown", 0, move, {.i = XT_MOVE_PAGEDOWN},FALSE },
4968 { "scrollpageup", 0, move, {.i = XT_MOVE_PAGEUP}, FALSE },
4969 /* horizontal movement */
4970 { "scrollright", 0, move, {.i = XT_MOVE_RIGHT}, FALSE },
4971 { "scrollleft", 0, move, {.i = XT_MOVE_LEFT}, FALSE },
4972 { "scrollfarright", 0, move, {.i = XT_MOVE_FARRIGHT},FALSE },
4973 { "scrollfarleft", 0, move, {.i = XT_MOVE_FARLEFT}, FALSE },
4976 { "favorites", 0, xtp_page_fl, {0}, FALSE },
4977 { "fav", 0, xtp_page_fl, {0}, FALSE },
4978 { "favadd", 0, add_favorite, {0}, FALSE },
4980 { "qall", 0, quit, {0}, FALSE },
4981 { "quitall", 0, quit, {0}, FALSE },
4982 { "w", 0, save_tabs, {0}, FALSE },
4983 { "wq", 0, save_tabs_and_quit, {0}, FALSE },
4984 { "help", 0, help, {0}, FALSE },
4985 { "about", 0, about, {0}, FALSE },
4986 { "stats", 0, stats, {0}, FALSE },
4987 { "version", 0, about, {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 /* cookie jar */
5016 { "cookiejar", 0, xtp_page_cl, {0}, FALSE },
5018 /* cert command */
5019 { "cert", 0, cert_cmd, {.i = XT_SHOW}, FALSE },
5020 { "save", 1, cert_cmd, {.i = XT_SAVE}, FALSE },
5021 { "show", 1, cert_cmd, {.i = XT_SHOW}, FALSE },
5023 { "ca", 0, ca_cmd, {0}, FALSE },
5024 { "downloadmgr", 0, xtp_page_dl, {0}, FALSE },
5025 { "dl", 0, xtp_page_dl, {0}, FALSE },
5026 { "h", 0, xtp_page_hl, {0}, FALSE },
5027 { "history", 0, xtp_page_hl, {0}, FALSE },
5028 { "home", 0, go_home, {0}, FALSE },
5029 { "restart", 0, restart, {0}, FALSE },
5030 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5031 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5032 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5033 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5035 { "print", 0, print_page, {0}, FALSE },
5037 /* tabs */
5038 { "open", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5039 { "tabnew", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5040 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5041 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5042 { "tabundoclose", 0, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
5043 { "tabshow", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5044 { "tabhide", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5045 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5046 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5048 /* XXX add count to these commands */
5049 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5050 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5051 { "tablast", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5052 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5053 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT}, TRUE },
5054 { "focusout", 0, resizetab, {.i = -1}, FALSE },
5055 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5057 /* command aliases (handy when -S flag is used) */
5058 { "promptopen", 0, command, {.i = XT_CMD_OPEN}, FALSE },
5059 { "promptopencurrent", 0, command, {.i = XT_CMD_OPEN_CURRENT}, FALSE },
5060 { "prompttabnew", 0, command, {.i = XT_CMD_TABNEW}, FALSE },
5061 { "prompttabnewcurrent",0, command, {.i = XT_CMD_TABNEW_CURRENT}, FALSE },
5063 /* settings */
5064 { "set", 0, set, {0}, FALSE },
5065 { "fullscreen", 0, fullscreen, {0}, FALSE },
5066 { "f", 0, fullscreen, {0}, FALSE },
5068 /* sessions */
5069 { "session", 0, session_cmd, {.i = XT_SHOW}, FALSE },
5070 { "delete", 1, session_cmd, {.i = XT_DELETE}, TRUE },
5071 { "open", 1, session_cmd, {.i = XT_OPEN}, TRUE },
5072 { "save", 1, session_cmd, {.i = XT_SAVE}, TRUE },
5073 { "show", 1, session_cmd, {.i = XT_SHOW}, FALSE },
5076 struct {
5077 int index;
5078 int len;
5079 gchar *list[256];
5080 } cmd_status = {-1, 0};
5082 gboolean
5083 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5085 struct karg a;
5087 hide_oops(t);
5089 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5090 /* go backward */
5091 a.i = XT_NAV_BACK;
5092 navaction(t, &a);
5094 return (TRUE);
5095 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5096 /* go forward */
5097 a.i = XT_NAV_FORWARD;
5098 navaction(t, &a);
5100 return (TRUE);
5103 return (FALSE);
5106 gboolean
5107 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5109 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5111 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5112 delete_tab(t);
5114 return (FALSE);
5118 * cancel, remove, etc. downloads
5120 void
5121 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5123 struct download find, *d = NULL;
5125 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5127 /* some commands require a valid download id */
5128 if (cmd != XT_XTP_DL_LIST) {
5129 /* lookup download in question */
5130 find.id = id;
5131 d = RB_FIND(download_list, &downloads, &find);
5133 if (d == NULL) {
5134 show_oops(t, "%s: no such download", __func__);
5135 return;
5139 /* decide what to do */
5140 switch (cmd) {
5141 case XT_XTP_DL_CANCEL:
5142 webkit_download_cancel(d->download);
5143 break;
5144 case XT_XTP_DL_REMOVE:
5145 webkit_download_cancel(d->download); /* just incase */
5146 g_object_unref(d->download);
5147 RB_REMOVE(download_list, &downloads, d);
5148 break;
5149 case XT_XTP_DL_LIST:
5150 /* Nothing */
5151 break;
5152 default:
5153 show_oops(t, "%s: unknown command", __func__);
5154 break;
5156 xtp_page_dl(t, NULL);
5160 * Actions on history, only does one thing for now, but
5161 * we provide the function for future actions
5163 void
5164 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5166 struct history *h, *next;
5167 int i = 1;
5169 switch (cmd) {
5170 case XT_XTP_HL_REMOVE:
5171 /* walk backwards, as listed in reverse */
5172 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5173 next = RB_PREV(history_list, &hl, h);
5174 if (id == i) {
5175 RB_REMOVE(history_list, &hl, h);
5176 g_free((gpointer) h->title);
5177 g_free((gpointer) h->uri);
5178 g_free(h);
5179 break;
5181 i++;
5183 break;
5184 case XT_XTP_HL_LIST:
5185 /* Nothing - just xtp_page_hl() below */
5186 break;
5187 default:
5188 show_oops(t, "%s: unknown command", __func__);
5189 break;
5192 xtp_page_hl(t, NULL);
5195 /* remove a favorite */
5196 void
5197 remove_favorite(struct tab *t, int index)
5199 char file[PATH_MAX], *title, *uri = NULL;
5200 char *new_favs, *tmp;
5201 FILE *f;
5202 int i;
5203 size_t len, lineno;
5205 /* open favorites */
5206 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5208 if ((f = fopen(file, "r")) == NULL) {
5209 show_oops(t, "%s: can't open favorites: %s",
5210 __func__, strerror(errno));
5211 return;
5214 /* build a string which will become the new favroites file */
5215 new_favs = g_strdup_printf("%s", "");
5217 for (i = 1;;) {
5218 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5219 if (feof(f) || ferror(f))
5220 break;
5221 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5222 if (len == 0) {
5223 free(title);
5224 title = NULL;
5225 continue;
5228 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5229 if (feof(f) || ferror(f)) {
5230 show_oops(t, "%s: can't parse favorites %s",
5231 __func__, strerror(errno));
5232 goto clean;
5236 /* as long as this isn't the one we are deleting add to file */
5237 if (i != index) {
5238 tmp = new_favs;
5239 new_favs = g_strdup_printf("%s%s\n%s\n",
5240 new_favs, title, uri);
5241 g_free(tmp);
5244 free(uri);
5245 uri = NULL;
5246 free(title);
5247 title = NULL;
5248 i++;
5250 fclose(f);
5252 /* write back new favorites file */
5253 if ((f = fopen(file, "w")) == NULL) {
5254 show_oops(t, "%s: can't open favorites: %s",
5255 __func__, strerror(errno));
5256 goto clean;
5259 fwrite(new_favs, strlen(new_favs), 1, f);
5260 fclose(f);
5262 clean:
5263 if (uri)
5264 free(uri);
5265 if (title)
5266 free(title);
5268 g_free(new_favs);
5271 void
5272 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5274 switch (cmd) {
5275 case XT_XTP_FL_LIST:
5276 /* nothing, just the below call to xtp_page_fl() */
5277 break;
5278 case XT_XTP_FL_REMOVE:
5279 remove_favorite(t, arg);
5280 break;
5281 default:
5282 show_oops(t, "%s: invalid favorites command", __func__);
5283 break;
5286 xtp_page_fl(t, NULL);
5289 void
5290 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5292 switch (cmd) {
5293 case XT_XTP_CL_LIST:
5294 /* nothing, just xtp_page_cl() */
5295 break;
5296 case XT_XTP_CL_REMOVE:
5297 remove_cookie(arg);
5298 break;
5299 default:
5300 show_oops(t, "%s: unknown cookie xtp command", __func__);
5301 break;
5304 xtp_page_cl(t, NULL);
5307 /* link an XTP class to it's session key and handler function */
5308 struct xtp_despatch {
5309 uint8_t xtp_class;
5310 char **session_key;
5311 void (*handle_func)(struct tab *, uint8_t, int);
5314 struct xtp_despatch xtp_despatches[] = {
5315 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5316 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5317 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5318 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5319 { XT_XTP_INVALID, NULL, NULL }
5323 * is the url xtp protocol? (xxxt://)
5324 * if so, parse and despatch correct bahvior
5327 parse_xtp_url(struct tab *t, const char *url)
5329 char *dup = NULL, *p, *last;
5330 uint8_t n_tokens = 0;
5331 char *tokens[4] = {NULL, NULL, NULL, ""};
5332 struct xtp_despatch *dsp, *dsp_match = NULL;
5333 uint8_t req_class;
5334 int ret = FALSE;
5337 * tokens array meaning:
5338 * tokens[0] = class
5339 * tokens[1] = session key
5340 * tokens[2] = action
5341 * tokens[3] = optional argument
5344 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5346 /*xtp tab meaning is normal unless proven special */
5347 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5349 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5350 goto clean;
5352 dup = g_strdup(url + strlen(XT_XTP_STR));
5354 /* split out the url */
5355 for ((p = strtok_r(dup, "/", &last)); p;
5356 (p = strtok_r(NULL, "/", &last))) {
5357 if (n_tokens < 4)
5358 tokens[n_tokens++] = p;
5361 /* should be atleast three fields 'class/seskey/command/arg' */
5362 if (n_tokens < 3)
5363 goto clean;
5365 dsp = xtp_despatches;
5366 req_class = atoi(tokens[0]);
5367 while (dsp->xtp_class) {
5368 if (dsp->xtp_class == req_class) {
5369 dsp_match = dsp;
5370 break;
5372 dsp++;
5375 /* did we find one atall? */
5376 if (dsp_match == NULL) {
5377 show_oops(t, "%s: no matching xtp despatch found", __func__);
5378 goto clean;
5381 /* check session key and call despatch function */
5382 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5383 ret = TRUE; /* all is well, this was a valid xtp request */
5384 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5387 clean:
5388 if (dup)
5389 g_free(dup);
5391 return (ret);
5396 void
5397 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5399 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5401 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5403 if (t == NULL) {
5404 show_oops_s("activate_uri_entry_cb invalid parameters");
5405 return;
5408 if (uri == NULL) {
5409 show_oops(t, "activate_uri_entry_cb no uri");
5410 return;
5413 uri += strspn(uri, "\t ");
5415 /* if xxxt:// treat specially */
5416 if (parse_xtp_url(t, uri))
5417 return;
5419 /* otherwise continue to load page normally */
5420 load_uri(t, (gchar *)uri);
5421 focus_webview(t);
5424 void
5425 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5427 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5428 char *newuri = NULL;
5429 gchar *enc_search;
5431 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5433 if (t == NULL) {
5434 show_oops_s("activate_search_entry_cb invalid parameters");
5435 return;
5438 if (search_string == NULL) {
5439 show_oops(t, "no search_string");
5440 return;
5443 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5444 newuri = g_strdup_printf(search_string, enc_search);
5445 g_free(enc_search);
5447 webkit_web_view_load_uri(t->wv, newuri);
5448 focus_webview(t);
5450 if (newuri)
5451 g_free(newuri);
5454 void
5455 check_and_set_js(const gchar *uri, struct tab *t)
5457 struct domain *d = NULL;
5458 int es = 0;
5460 if (uri == NULL || t == NULL)
5461 return;
5463 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5464 es = 0;
5465 else
5466 es = 1;
5468 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5469 es ? "enable" : "disable", uri);
5471 g_object_set(G_OBJECT(t->settings),
5472 "enable-scripts", es, (char *)NULL);
5473 g_object_set(G_OBJECT(t->settings),
5474 "javascript-can-open-windows-automatically", es, (char *)NULL);
5475 webkit_web_view_set_settings(t->wv, t->settings);
5477 button_set_stockid(t->js_toggle,
5478 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5481 void
5482 show_ca_status(struct tab *t, const char *uri)
5484 WebKitWebFrame *frame;
5485 WebKitWebDataSource *source;
5486 WebKitNetworkRequest *request;
5487 SoupMessage *message;
5488 GdkColor color;
5489 gchar *col_str = XT_COLOR_WHITE;
5490 int r;
5492 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5493 ssl_strict_certs, ssl_ca_file, uri);
5495 if (uri == NULL)
5496 goto done;
5497 if (ssl_ca_file == NULL) {
5498 if (g_str_has_prefix(uri, "http://"))
5499 goto done;
5500 if (g_str_has_prefix(uri, "https://")) {
5501 col_str = XT_COLOR_RED;
5502 goto done;
5504 return;
5506 if (g_str_has_prefix(uri, "http://") ||
5507 !g_str_has_prefix(uri, "https://"))
5508 goto done;
5510 frame = webkit_web_view_get_main_frame(t->wv);
5511 source = webkit_web_frame_get_data_source(frame);
5512 request = webkit_web_data_source_get_request(source);
5513 message = webkit_network_request_get_message(request);
5515 if (message && (soup_message_get_flags(message) &
5516 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5517 col_str = XT_COLOR_GREEN;
5518 goto done;
5519 } else {
5520 r = load_compare_cert(t, NULL);
5521 if (r == 0)
5522 col_str = XT_COLOR_BLUE;
5523 else if (r == 1)
5524 col_str = XT_COLOR_YELLOW;
5525 else
5526 col_str = XT_COLOR_RED;
5527 goto done;
5529 done:
5530 if (col_str) {
5531 gdk_color_parse(col_str, &color);
5532 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5534 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5535 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5536 &color);
5537 gdk_color_parse(XT_COLOR_BLACK, &color);
5538 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5539 &color);
5540 } else {
5541 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5542 &color);
5543 gdk_color_parse(XT_COLOR_BLACK, &color);
5544 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5545 &color);
5550 void
5551 free_favicon(struct tab *t)
5553 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5554 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5556 if (t->icon_request)
5557 g_object_unref(t->icon_request);
5558 if (t->icon_pixbuf)
5559 g_object_unref(t->icon_pixbuf);
5560 if (t->icon_dest_uri)
5561 g_free(t->icon_dest_uri);
5563 t->icon_pixbuf = NULL;
5564 t->icon_request = NULL;
5565 t->icon_dest_uri = NULL;
5568 void
5569 xt_icon_from_name(struct tab *t, gchar *name)
5571 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5572 GTK_ENTRY_ICON_PRIMARY, "text-html");
5573 if (show_url == 0)
5574 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5575 GTK_ENTRY_ICON_PRIMARY, "text-html");
5576 else
5577 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5578 GTK_ENTRY_ICON_PRIMARY, NULL);
5581 void
5582 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5584 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5585 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5586 if (show_url == 0)
5587 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5588 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5589 else
5590 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5591 GTK_ENTRY_ICON_PRIMARY, NULL);
5594 gboolean
5595 is_valid_icon(char *file)
5597 gboolean valid = 0;
5598 const char *mime_type;
5599 GFileInfo *fi;
5600 GFile *gf;
5602 gf = g_file_new_for_path(file);
5603 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5604 NULL, NULL);
5605 mime_type = g_file_info_get_content_type(fi);
5606 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5607 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5608 g_strcmp0(mime_type, "image/png") == 0 ||
5609 g_strcmp0(mime_type, "image/gif") == 0 ||
5610 g_strcmp0(mime_type, "application/octet-stream") == 0;
5611 g_object_unref(fi);
5612 g_object_unref(gf);
5614 return (valid);
5617 void
5618 set_favicon_from_file(struct tab *t, char *file)
5620 gint width, height;
5621 GdkPixbuf *pixbuf, *scaled;
5622 struct stat sb;
5624 if (t == NULL || file == NULL)
5625 return;
5626 if (t->icon_pixbuf) {
5627 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5628 return;
5631 if (g_str_has_prefix(file, "file://"))
5632 file += strlen("file://");
5633 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5635 if (!stat(file, &sb)) {
5636 if (sb.st_size == 0 || !is_valid_icon(file)) {
5637 /* corrupt icon so trash it */
5638 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5639 __func__, file);
5640 unlink(file);
5641 /* no need to set icon to default here */
5642 return;
5646 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5647 if (pixbuf == NULL) {
5648 xt_icon_from_name(t, "text-html");
5649 return;
5652 g_object_get(pixbuf, "width", &width, "height", &height,
5653 (char *)NULL);
5654 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5655 __func__, t->tab_id, width, height);
5657 if (width > 16 || height > 16) {
5658 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5659 GDK_INTERP_BILINEAR);
5660 g_object_unref(pixbuf);
5661 } else
5662 scaled = pixbuf;
5664 if (scaled == NULL) {
5665 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5666 GDK_INTERP_BILINEAR);
5667 return;
5670 t->icon_pixbuf = scaled;
5671 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5674 void
5675 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5676 WebKitWebView *wv)
5678 WebKitDownloadStatus status = webkit_download_get_status(download);
5679 struct tab *tt = NULL, *t = NULL;
5682 * find the webview instead of passing in the tab as it could have been
5683 * deleted from underneath us.
5685 TAILQ_FOREACH(tt, &tabs, entry) {
5686 if (tt->wv == wv) {
5687 t = tt;
5688 break;
5691 if (t == NULL)
5692 return;
5694 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5695 __func__, t->tab_id, status);
5697 switch (status) {
5698 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5699 /* -1 */
5700 t->icon_download = NULL;
5701 free_favicon(t);
5702 break;
5703 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5704 /* 0 */
5705 break;
5706 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5707 /* 1 */
5708 break;
5709 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5710 /* 2 */
5711 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5712 __func__, t->tab_id);
5713 t->icon_download = NULL;
5714 free_favicon(t);
5715 break;
5716 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5717 /* 3 */
5719 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5720 __func__, t->icon_dest_uri);
5721 set_favicon_from_file(t, t->icon_dest_uri);
5722 /* these will be freed post callback */
5723 t->icon_request = NULL;
5724 t->icon_download = NULL;
5725 break;
5726 default:
5727 break;
5731 void
5732 abort_favicon_download(struct tab *t)
5734 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5736 if (t->icon_download) {
5737 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5738 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5739 webkit_download_cancel(t->icon_download);
5740 t->icon_download = NULL;
5741 } else
5742 free_favicon(t);
5744 xt_icon_from_name(t, "text-html");
5747 void
5748 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5750 gchar *name_hash, file[PATH_MAX];
5751 struct stat sb;
5753 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5755 if (uri == NULL || t == NULL)
5756 return;
5758 if (t->icon_request) {
5759 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5760 return;
5763 /* check to see if we got the icon in cache */
5764 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5765 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5766 g_free(name_hash);
5768 if (!stat(file, &sb)) {
5769 if (sb.st_size > 0) {
5770 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5771 __func__, file);
5772 set_favicon_from_file(t, file);
5773 return;
5776 /* corrupt icon so trash it */
5777 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5778 __func__, file);
5779 unlink(file);
5782 /* create download for icon */
5783 t->icon_request = webkit_network_request_new(uri);
5784 if (t->icon_request == NULL) {
5785 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5786 __func__, uri);
5787 return;
5790 t->icon_download = webkit_download_new(t->icon_request);
5791 if (t->icon_download == NULL) {
5792 fprintf(stderr, "%s: icon_download", __func__);
5793 return;
5796 /* we have to free icon_dest_uri later */
5797 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5798 webkit_download_set_destination_uri(t->icon_download,
5799 t->icon_dest_uri);
5801 if (webkit_download_get_status(t->icon_download) ==
5802 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5803 fprintf(stderr, "%s: download failed to start", __func__);
5804 g_object_unref(t->icon_request);
5805 g_free(t->icon_dest_uri);
5806 t->icon_request = NULL;
5807 t->icon_dest_uri = NULL;
5808 return;
5811 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5812 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5814 webkit_download_start(t->icon_download);
5817 void
5818 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5820 const gchar *set = NULL, *uri = NULL, *title = NULL;
5821 struct history *h, find;
5822 const gchar *s_loading;
5823 struct karg a;
5825 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5826 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5828 if (t == NULL) {
5829 show_oops_s("notify_load_status_cb invalid paramters");
5830 return;
5833 switch (webkit_web_view_get_load_status(wview)) {
5834 case WEBKIT_LOAD_PROVISIONAL:
5835 /* 0 */
5836 abort_favicon_download(t);
5837 #if GTK_CHECK_VERSION(2, 20, 0)
5838 gtk_widget_show(t->spinner);
5839 gtk_spinner_start(GTK_SPINNER(t->spinner));
5840 #endif
5841 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5843 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5845 t->focus_wv = 1;
5847 break;
5849 case WEBKIT_LOAD_COMMITTED:
5850 /* 1 */
5851 if ((uri = get_uri(wview)) != NULL) {
5852 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5853 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5855 if (t->status) {
5856 g_free(t->status);
5857 t->status = NULL;
5859 set_status(t, (char *)uri, XT_STATUS_LOADING);
5862 /* check if js white listing is enabled */
5863 if (enable_js_whitelist) {
5864 uri = get_uri(wview);
5865 check_and_set_js(uri, t);
5868 if (t->styled)
5869 apply_style(t);
5871 show_ca_status(t, uri);
5873 /* we know enough to autosave the session */
5874 if (session_autosave) {
5875 a.s = NULL;
5876 save_tabs(t, &a);
5878 break;
5880 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5881 /* 3 */
5882 break;
5884 case WEBKIT_LOAD_FINISHED:
5885 /* 2 */
5886 uri = get_uri(wview);
5887 if (uri == NULL)
5888 return;
5890 if (!strncmp(uri, "http://", strlen("http://")) ||
5891 !strncmp(uri, "https://", strlen("https://")) ||
5892 !strncmp(uri, "file://", strlen("file://"))) {
5893 find.uri = uri;
5894 h = RB_FIND(history_list, &hl, &find);
5895 if (!h) {
5896 title = webkit_web_view_get_title(wview);
5897 set = title ? title: uri;
5898 h = g_malloc(sizeof *h);
5899 h->uri = g_strdup(uri);
5900 h->title = g_strdup(set);
5901 RB_INSERT(history_list, &hl, h);
5902 completion_add_uri(h->uri);
5903 update_history_tabs(NULL);
5907 set_status(t, (char *)uri, XT_STATUS_URI);
5908 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5909 case WEBKIT_LOAD_FAILED:
5910 /* 4 */
5911 #endif
5912 #if GTK_CHECK_VERSION(2, 20, 0)
5913 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5914 gtk_widget_hide(t->spinner);
5915 #endif
5916 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5917 if (s_loading && !strcmp(s_loading, "Loading"))
5918 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5919 default:
5920 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5921 break;
5924 if (t->item)
5925 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5926 else
5927 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5928 webkit_web_view_can_go_back(wview));
5930 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5931 webkit_web_view_can_go_forward(wview));
5933 /* take focus if we are visible */
5934 focus_webview(t);
5937 void
5938 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5940 const gchar *set = NULL, *title = NULL;
5942 title = webkit_web_view_get_title(wview);
5943 set = title ? title: get_uri(wview);
5944 if (set) {
5945 gtk_label_set_text(GTK_LABEL(t->label), set);
5946 gtk_window_set_title(GTK_WINDOW(main_window), set);
5947 } else {
5948 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5949 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5953 void
5954 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5956 run_script(t, JS_HINTING);
5959 void
5960 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5962 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5963 progress == 100 ? 0 : (double)progress / 100);
5964 if (show_url == 0) {
5965 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5966 progress == 100 ? 0 : (double)progress / 100);
5971 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5972 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5973 WebKitWebPolicyDecision *pd, struct tab *t)
5975 char *uri;
5976 WebKitWebNavigationReason reason;
5977 struct domain *d = NULL;
5979 if (t == NULL) {
5980 show_oops_s("webview_npd_cb invalid parameters");
5981 return (FALSE);
5984 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5985 t->ctrl_click,
5986 webkit_network_request_get_uri(request));
5988 uri = (char *)webkit_network_request_get_uri(request);
5990 /* if this is an xtp url, we don't load anything else */
5991 if (parse_xtp_url(t, uri))
5992 return (TRUE);
5994 if (t->ctrl_click) {
5995 t->ctrl_click = 0;
5996 create_new_tab(uri, NULL, ctrl_click_focus);
5997 webkit_web_policy_decision_ignore(pd);
5998 return (TRUE); /* we made the decission */
6002 * This is a little hairy but it comes down to this:
6003 * when we run in whitelist mode we have to assist the browser in
6004 * opening the URL that it would have opened in a new tab.
6006 reason = webkit_web_navigation_action_get_reason(na);
6007 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6008 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6009 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6010 load_uri(t, uri);
6012 webkit_web_policy_decision_use(pd);
6013 return (TRUE); /* we made the decission */
6016 return (FALSE);
6019 WebKitWebView *
6020 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6022 struct tab *tt;
6023 struct domain *d = NULL;
6024 const gchar *uri;
6025 WebKitWebView *webview = NULL;
6027 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6028 webkit_web_view_get_uri(wv));
6030 if (tabless) {
6031 /* open in current tab */
6032 webview = t->wv;
6033 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6034 uri = webkit_web_view_get_uri(wv);
6035 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6036 return (NULL);
6038 tt = create_new_tab(NULL, NULL, 1);
6039 webview = tt->wv;
6040 } else if (enable_scripts == 1) {
6041 tt = create_new_tab(NULL, NULL, 1);
6042 webview = tt->wv;
6045 return (webview);
6048 gboolean
6049 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6051 const gchar *uri;
6052 struct domain *d = NULL;
6054 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6056 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6057 uri = webkit_web_view_get_uri(wv);
6058 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6059 return (FALSE);
6061 delete_tab(t);
6062 } else if (enable_scripts == 1)
6063 delete_tab(t);
6065 return (TRUE);
6069 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6071 /* we can not eat the event without throwing gtk off so defer it */
6073 /* catch middle click */
6074 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6075 t->ctrl_click = 1;
6076 goto done;
6079 /* catch ctrl click */
6080 if (e->type == GDK_BUTTON_RELEASE &&
6081 CLEAN(e->state) == GDK_CONTROL_MASK)
6082 t->ctrl_click = 1;
6083 else
6084 t->ctrl_click = 0;
6085 done:
6086 return (XT_CB_PASSTHROUGH);
6090 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6092 struct mime_type *m;
6094 m = find_mime_type(mime_type);
6095 if (m == NULL)
6096 return (1);
6097 if (m->mt_download)
6098 return (1);
6100 switch (fork()) {
6101 case -1:
6102 show_oops(t, "can't fork mime handler");
6103 /* NOTREACHED */
6104 case 0:
6105 break;
6106 default:
6107 return (0);
6110 /* child */
6111 execlp(m->mt_action, m->mt_action,
6112 webkit_network_request_get_uri(request), (void *)NULL);
6114 _exit(0);
6116 /* NOTREACHED */
6117 return (0);
6120 const gchar *
6121 get_mime_type(char *file)
6123 const char *mime_type;
6124 GFileInfo *fi;
6125 GFile *gf;
6127 if (g_str_has_prefix(file, "file://"))
6128 file += strlen("file://");
6130 gf = g_file_new_for_path(file);
6131 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6132 NULL, NULL);
6133 mime_type = g_file_info_get_content_type(fi);
6134 g_object_unref(fi);
6135 g_object_unref(gf);
6137 return (mime_type);
6141 run_download_mimehandler(char *mime_type, char *file)
6143 struct mime_type *m;
6145 m = find_mime_type(mime_type);
6146 if (m == NULL)
6147 return (1);
6149 switch (fork()) {
6150 case -1:
6151 show_oops_s("can't fork download mime handler");
6152 /* NOTREACHED */
6153 case 0:
6154 break;
6155 default:
6156 return (0);
6159 /* child */
6160 if (g_str_has_prefix(file, "file://"))
6161 file += strlen("file://");
6162 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6164 _exit(0);
6166 /* NOTREACHED */
6167 return (0);
6170 void
6171 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6172 WebKitWebView *wv)
6174 WebKitDownloadStatus status;
6175 const gchar *file = NULL, *mime = NULL;
6177 if (download == NULL)
6178 return;
6179 status = webkit_download_get_status(download);
6180 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6181 return;
6183 file = webkit_download_get_destination_uri(download);
6184 if (file == NULL)
6185 return;
6186 mime = get_mime_type((char *)file);
6187 if (mime == NULL)
6188 return;
6190 run_download_mimehandler((char *)mime, (char *)file);
6194 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6195 WebKitNetworkRequest *request, char *mime_type,
6196 WebKitWebPolicyDecision *decision, struct tab *t)
6198 if (t == NULL) {
6199 show_oops_s("webview_mimetype_cb invalid parameters");
6200 return (FALSE);
6203 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6204 t->tab_id, mime_type);
6206 if (run_mimehandler(t, mime_type, request) == 0) {
6207 webkit_web_policy_decision_ignore(decision);
6208 focus_webview(t);
6209 return (TRUE);
6212 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6213 webkit_web_policy_decision_download(decision);
6214 return (TRUE);
6217 return (FALSE);
6221 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6222 struct tab *t)
6224 const gchar *filename;
6225 char *uri = NULL;
6226 struct download *download_entry;
6227 int ret = TRUE;
6229 if (wk_download == NULL || t == NULL) {
6230 show_oops_s("%s invalid parameters", __func__);
6231 return (FALSE);
6234 filename = webkit_download_get_suggested_filename(wk_download);
6235 if (filename == NULL)
6236 return (FALSE); /* abort download */
6238 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6240 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6241 "local %s\n", __func__, t->tab_id, filename, uri);
6243 webkit_download_set_destination_uri(wk_download, uri);
6245 if (webkit_download_get_status(wk_download) ==
6246 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6247 show_oops(t, "%s: download failed to start", __func__);
6248 ret = FALSE;
6249 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6250 } else {
6251 /* connect "download first" mime handler */
6252 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6253 G_CALLBACK(download_status_changed_cb), NULL);
6255 download_entry = g_malloc(sizeof(struct download));
6256 download_entry->download = wk_download;
6257 download_entry->tab = t;
6258 download_entry->id = next_download_id++;
6259 RB_INSERT(download_list, &downloads, download_entry);
6260 /* get from history */
6261 g_object_ref(wk_download);
6262 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6263 show_oops(t, "Download of '%s' started...",
6264 basename(webkit_download_get_destination_uri(wk_download)));
6267 if (uri)
6268 g_free(uri);
6270 /* sync other download manager tabs */
6271 update_download_tabs(NULL);
6274 * NOTE: never redirect/render the current tab before this
6275 * function returns. This will cause the download to never start.
6277 return (ret); /* start download */
6280 void
6281 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6283 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6285 if (t == NULL) {
6286 show_oops_s("webview_hover_cb");
6287 return;
6290 if (uri)
6291 set_status(t, uri, XT_STATUS_LINK);
6292 else {
6293 if (t->status)
6294 set_status(t, t->status, XT_STATUS_NOTHING);
6298 gboolean
6299 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6301 struct key_binding *k;
6303 TAILQ_FOREACH(k, &kbl, entry)
6304 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6305 if (k->mask == 0) {
6306 if ((e->state & (CTRL | MOD1)) == 0)
6307 return (cmd_execute(t, k->cmd));
6308 } else if ((e->state & k->mask) == k->mask) {
6309 return (cmd_execute(t, k->cmd));
6313 return (XT_CB_PASSTHROUGH);
6317 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6319 char s[2], buf[128];
6320 const char *errstr = NULL;
6321 long long link;
6323 /* don't use w directly; use t->whatever instead */
6325 if (t == NULL) {
6326 show_oops_s("wv_keypress_after_cb");
6327 return (XT_CB_PASSTHROUGH);
6330 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6331 e->keyval, e->state, t);
6333 if (t->hints_on) {
6334 /* ESC */
6335 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6336 disable_hints(t);
6337 return (XT_CB_HANDLED);
6340 /* RETURN */
6341 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6342 link = strtonum(t->hint_num, 1, 1000, &errstr);
6343 if (errstr) {
6344 /* we have a string */
6345 } else {
6346 /* we have a number */
6347 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6348 t->hint_num);
6349 run_script(t, buf);
6351 disable_hints(t);
6354 /* BACKSPACE */
6355 /* XXX unfuck this */
6356 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6357 if (t->hint_mode == XT_HINT_NUMERICAL) {
6358 /* last input was numerical */
6359 int l;
6360 l = strlen(t->hint_num);
6361 if (l > 0) {
6362 l--;
6363 if (l == 0) {
6364 disable_hints(t);
6365 enable_hints(t);
6366 } else {
6367 t->hint_num[l] = '\0';
6368 goto num;
6371 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6372 /* last input was alphanumerical */
6373 int l;
6374 l = strlen(t->hint_buf);
6375 if (l > 0) {
6376 l--;
6377 if (l == 0) {
6378 disable_hints(t);
6379 enable_hints(t);
6380 } else {
6381 t->hint_buf[l] = '\0';
6382 goto anum;
6385 } else {
6386 /* bogus */
6387 disable_hints(t);
6391 /* numerical input */
6392 if (CLEAN(e->state) == 0 &&
6393 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6394 snprintf(s, sizeof s, "%c", e->keyval);
6395 strlcat(t->hint_num, s, sizeof t->hint_num);
6396 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6397 t->hint_num);
6398 num:
6399 link = strtonum(t->hint_num, 1, 1000, &errstr);
6400 if (errstr) {
6401 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6402 disable_hints(t);
6403 } else {
6404 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6405 t->hint_num);
6406 t->hint_mode = XT_HINT_NUMERICAL;
6407 run_script(t, buf);
6410 /* empty the counter buffer */
6411 bzero(t->hint_buf, sizeof t->hint_buf);
6412 return (XT_CB_HANDLED);
6415 /* alphanumerical input */
6416 if (
6417 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6418 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6419 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6420 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6421 snprintf(s, sizeof s, "%c", e->keyval);
6422 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6423 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6424 t->hint_buf);
6425 anum:
6426 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6427 run_script(t, buf);
6429 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6430 t->hint_buf);
6431 t->hint_mode = XT_HINT_ALPHANUM;
6432 run_script(t, buf);
6434 /* empty the counter buffer */
6435 bzero(t->hint_num, sizeof t->hint_num);
6436 return (XT_CB_HANDLED);
6439 return (XT_CB_HANDLED);
6442 return (handle_keypress(t, e, 0));
6446 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6448 hide_oops(t);
6450 return (XT_CB_PASSTHROUGH);
6454 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6456 const gchar *c = gtk_entry_get_text(w);
6457 GdkColor color;
6458 int forward = TRUE;
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 (t == NULL) {
6464 show_oops_s("cmd_keyrelease_cb invalid parameters");
6465 return (XT_CB_PASSTHROUGH);
6468 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6469 e->keyval, e->state, t);
6471 if (c[0] == ':')
6472 goto done;
6473 if (strlen(c) == 1) {
6474 webkit_web_view_unmark_text_matches(t->wv);
6475 goto done;
6478 if (c[0] == '/')
6479 forward = TRUE;
6480 else if (c[0] == '?')
6481 forward = FALSE;
6482 else
6483 goto done;
6485 /* search */
6486 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6487 FALSE) {
6488 /* not found, mark red */
6489 gdk_color_parse(XT_COLOR_RED, &color);
6490 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6491 /* unmark and remove selection */
6492 webkit_web_view_unmark_text_matches(t->wv);
6493 /* my kingdom for a way to unselect text in webview */
6494 } else {
6495 /* found, highlight all */
6496 webkit_web_view_unmark_text_matches(t->wv);
6497 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6498 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6499 gdk_color_parse(XT_COLOR_WHITE, &color);
6500 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6502 done:
6503 return (XT_CB_PASSTHROUGH);
6506 gboolean
6507 match_uri(const gchar *uri, const gchar *key) {
6508 gchar *voffset;
6509 size_t len;
6510 gboolean match = FALSE;
6512 len = strlen(key);
6514 if (!strncmp(key, uri, len))
6515 match = TRUE;
6516 else {
6517 voffset = strstr(uri, "/") + 2;
6518 if (!strncmp(key, voffset, len))
6519 match = TRUE;
6520 else if (g_str_has_prefix(voffset, "www.")) {
6521 voffset = voffset + strlen("www.");
6522 if (!strncmp(key, voffset, len))
6523 match = TRUE;
6527 return (match);
6530 void
6531 cmd_getlist(int id, char *key)
6533 int i, dep, c = 0;
6534 struct history *h;
6536 if (id >= 0 && (cmds[id].userarg && (cmds[id].arg.i == XT_TAB_OPEN || cmds[id].arg.i == XT_TAB_NEW))) {
6537 RB_FOREACH_REVERSE(h, history_list, &hl)
6538 if (match_uri(h->uri, key)) {
6539 cmd_status.list[c] = (char *)h->uri;
6540 if (++c > 255)
6541 break;
6544 cmd_status.len = c;
6545 return;
6548 dep = (id == -1) ? 0 : cmds[id].level + 1;
6550 for (i = id + 1; i < LENGTH(cmds); i++) {
6551 if(cmds[i].level < dep)
6552 break;
6553 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6554 cmd_status.list[c++] = cmds[i].cmd;
6558 cmd_status.len = c;
6561 char *
6562 cmd_getnext(int dir)
6564 cmd_status.index += dir;
6566 if (cmd_status.index < 0)
6567 cmd_status.index = cmd_status.len - 1;
6568 else if (cmd_status.index >= cmd_status.len)
6569 cmd_status.index = 0;
6571 return cmd_status.list[cmd_status.index];
6575 cmd_tokenize(char *s, char *tokens[])
6577 int i = 0;
6578 char *tok, *last;
6579 size_t len = strlen(s);
6580 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6582 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6583 tokens[i] = tok;
6585 if (blank && i < 3)
6586 tokens[i++] = "";
6588 return (i);
6591 void
6592 cmd_complete(struct tab *t, char *str, int dir)
6594 GtkEntry *w = GTK_ENTRY(t->cmd);
6595 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6596 char *tok, *match, *s = g_strdup(str);
6597 char *tokens[3];
6598 char res[XT_MAX_URL_LENGTH + 32] = ":";
6600 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", str);
6602 levels = cmd_tokenize(s, tokens);
6603 g_free(s);
6605 for (i = 0; i < levels - 1; i++) {
6606 tok = tokens[i];
6607 matchcount = 0;
6608 for (j = c; j < LENGTH(cmds); j++) {
6609 if (cmds[j].level < dep)
6610 break;
6611 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6612 matchcount++;
6613 c = j + 1;
6614 if (strlen(tok) == strlen(cmds[j].cmd)) {
6615 matchcount = 1;
6616 break;
6621 if (matchcount == 1) {
6622 strlcat(res, tok, sizeof res);
6623 strlcat(res, " ", sizeof res);
6624 dep++;
6625 } else
6626 return;
6628 parent = c - 1;
6631 if (cmd_status.index == -1)
6632 cmd_getlist(parent, tokens[i]);
6634 if (cmd_status.len > 0) {
6635 match = cmd_getnext(dir);
6636 strlcat(res, match, sizeof res);
6637 gtk_entry_set_text(w, res);
6638 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6642 gboolean
6643 cmd_execute(struct tab *t, char *str)
6645 struct cmd *cmd = NULL;
6646 char *tok, *last, *s = g_strdup(str);
6647 int j, len, c = 0, dep = 0, matchcount = 0;
6649 for (tok = strtok_r(s, " ", &last); tok;
6650 tok = strtok_r(NULL, " ", &last)) {
6651 matchcount = 0;
6652 for (j = c; j < LENGTH(cmds); j++) {
6653 if (cmds[j].level < dep)
6654 break;
6655 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6656 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6657 matchcount++;
6658 c = j + 1;
6659 cmd = &cmds[j];
6660 if (len == strlen(cmds[j].cmd)) {
6661 matchcount = 1;
6662 break;
6666 if (matchcount == 1) {
6667 if (cmd->userarg)
6668 goto execute_cmd;
6669 dep++;
6670 } else {
6671 show_oops(t, "Invalid command: %s", str);
6672 g_free(s);
6673 return (XT_CB_PASSTHROUGH);
6676 execute_cmd:
6677 if (cmd->userarg)
6678 cmd->arg.s = last ? g_strdup(last) : g_strdup("");
6679 else
6680 cmd->arg.s = g_strdup(tok);
6682 /* arg->s contains last token */
6683 cmd->func(t, &cmd->arg);
6684 g_free(s);
6685 if (cmd->arg.s)
6686 g_free(cmd->arg.s);
6688 return (XT_CB_HANDLED);
6692 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6694 if (t == NULL) {
6695 show_oops_s("entry_key_cb invalid parameters");
6696 return (XT_CB_PASSTHROUGH);
6699 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6700 e->keyval, e->state, t);
6702 hide_oops(t);
6704 if (e->keyval == GDK_Escape) {
6705 /* don't use focus_webview(t) because we want to type :cmds */
6706 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6709 return (handle_keypress(t, e, 1));
6713 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6715 int rv = XT_CB_HANDLED;
6716 const gchar *c = gtk_entry_get_text(w);
6718 if (t == NULL) {
6719 show_oops_s("cmd_keypress_cb parameters");
6720 return (XT_CB_PASSTHROUGH);
6723 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6724 e->keyval, e->state, t);
6726 /* sanity */
6727 if (c == NULL)
6728 e->keyval = GDK_Escape;
6729 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6730 e->keyval = GDK_Escape;
6732 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6733 cmd_status.index = -1;
6735 switch (e->keyval) {
6736 case GDK_Tab:
6737 if (c[0] == ':')
6738 cmd_complete(t, (char *)&c[1], 1);
6739 goto done;
6740 case GDK_ISO_Left_Tab:
6741 if (c[0] == ':')
6742 cmd_complete(t, (char *)&c[1], -1);
6744 goto done;
6745 case GDK_BackSpace:
6746 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6747 break;
6748 /* FALLTHROUGH */
6749 case GDK_Escape:
6750 hide_cmd(t);
6751 focus_webview(t);
6753 /* cancel search */
6754 if (c[0] == '/' || c[0] == '?')
6755 webkit_web_view_unmark_text_matches(t->wv);
6756 goto done;
6759 rv = XT_CB_PASSTHROUGH;
6760 done:
6761 return (rv);
6765 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6767 if (t == NULL) {
6768 show_oops_s("cmd_focusout_cb invalid parameters");
6769 return (XT_CB_PASSTHROUGH);
6771 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6773 hide_cmd(t);
6774 hide_oops(t);
6776 if (show_url == 0 || t->focus_wv)
6777 focus_webview(t);
6778 else
6779 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6781 return (XT_CB_PASSTHROUGH);
6784 void
6785 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6787 char *s;
6788 const gchar *c = gtk_entry_get_text(entry);
6790 if (t == NULL) {
6791 show_oops_s("cmd_activate_cb invalid parameters");
6792 return;
6795 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6797 hide_cmd(t);
6799 /* sanity */
6800 if (c == NULL)
6801 goto done;
6802 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6803 goto done;
6804 if (strlen(c) < 2)
6805 goto done;
6806 s = (char *)&c[1];
6808 if (c[0] == '/' || c[0] == '?') {
6809 if (t->search_text) {
6810 g_free(t->search_text);
6811 t->search_text = NULL;
6814 t->search_text = g_strdup(s);
6815 if (global_search)
6816 g_free(global_search);
6817 global_search = g_strdup(s);
6818 t->search_forward = c[0] == '/';
6820 goto done;
6823 cmd_execute(t, s);
6825 done:
6826 return;
6829 void
6830 backward_cb(GtkWidget *w, struct tab *t)
6832 struct karg a;
6834 if (t == NULL) {
6835 show_oops_s("backward_cb invalid parameters");
6836 return;
6839 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6841 a.i = XT_NAV_BACK;
6842 navaction(t, &a);
6845 void
6846 forward_cb(GtkWidget *w, struct tab *t)
6848 struct karg a;
6850 if (t == NULL) {
6851 show_oops_s("forward_cb invalid parameters");
6852 return;
6855 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6857 a.i = XT_NAV_FORWARD;
6858 navaction(t, &a);
6861 void
6862 home_cb(GtkWidget *w, struct tab *t)
6864 if (t == NULL) {
6865 show_oops_s("home_cb invalid parameters");
6866 return;
6869 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6871 load_uri(t, home);
6874 void
6875 stop_cb(GtkWidget *w, struct tab *t)
6877 WebKitWebFrame *frame;
6879 if (t == NULL) {
6880 show_oops_s("stop_cb invalid parameters");
6881 return;
6884 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6886 frame = webkit_web_view_get_main_frame(t->wv);
6887 if (frame == NULL) {
6888 show_oops(t, "stop_cb: no frame");
6889 return;
6892 webkit_web_frame_stop_loading(frame);
6893 abort_favicon_download(t);
6896 void
6897 setup_webkit(struct tab *t)
6899 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6900 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6901 FALSE, (char *)NULL);
6902 else
6903 warnx("webkit does not have \"enable-dns-prefetching\" property");
6904 g_object_set(G_OBJECT(t->settings),
6905 "user-agent", t->user_agent, (char *)NULL);
6906 g_object_set(G_OBJECT(t->settings),
6907 "enable-scripts", enable_scripts, (char *)NULL);
6908 g_object_set(G_OBJECT(t->settings),
6909 "enable-plugins", enable_plugins, (char *)NULL);
6910 g_object_set(G_OBJECT(t->settings),
6911 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6912 g_object_set(G_OBJECT(t->wv),
6913 "full-content-zoom", TRUE, (char *)NULL);
6914 adjustfont_webkit(t, XT_FONT_SET);
6916 webkit_web_view_set_settings(t->wv, t->settings);
6919 GtkWidget *
6920 create_browser(struct tab *t)
6922 GtkWidget *w;
6923 gchar *strval;
6925 if (t == NULL) {
6926 show_oops_s("create_browser invalid parameters");
6927 return (NULL);
6930 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6931 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6932 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6933 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6935 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6936 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6937 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6939 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6940 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6942 /* set defaults */
6943 t->settings = webkit_web_settings_new();
6945 if (user_agent == NULL) {
6946 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6947 (char *)NULL);
6948 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6949 g_free(strval);
6950 } else
6951 t->user_agent = g_strdup(user_agent);
6953 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
6955 setup_webkit(t);
6957 return (w);
6960 GtkWidget *
6961 create_window(void)
6963 GtkWidget *w;
6965 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6966 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6967 gtk_widget_set_name(w, "xxxterm");
6968 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6969 g_signal_connect(G_OBJECT(w), "delete_event",
6970 G_CALLBACK (gtk_main_quit), NULL);
6972 return (w);
6975 GtkWidget *
6976 create_kiosk_toolbar(struct tab *t)
6978 GtkWidget *toolbar = NULL, *b;
6980 b = gtk_hbox_new(FALSE, 0);
6981 toolbar = b;
6982 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6984 /* backward button */
6985 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
6986 gtk_widget_set_sensitive(t->backward, FALSE);
6987 g_signal_connect(G_OBJECT(t->backward), "clicked",
6988 G_CALLBACK(backward_cb), t);
6989 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
6991 /* forward button */
6992 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
6993 gtk_widget_set_sensitive(t->forward, FALSE);
6994 g_signal_connect(G_OBJECT(t->forward), "clicked",
6995 G_CALLBACK(forward_cb), t);
6996 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
6998 /* home button */
6999 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7000 gtk_widget_set_sensitive(t->gohome, true);
7001 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7002 G_CALLBACK(home_cb), t);
7003 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7005 /* create widgets but don't use them */
7006 t->uri_entry = gtk_entry_new();
7007 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7008 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7009 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7011 return (toolbar);
7014 GtkWidget *
7015 create_toolbar(struct tab *t)
7017 GtkWidget *toolbar = NULL, *b, *eb1;
7019 b = gtk_hbox_new(FALSE, 0);
7020 toolbar = b;
7021 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7023 if (fancy_bar) {
7024 /* backward button */
7025 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7026 gtk_widget_set_sensitive(t->backward, FALSE);
7027 g_signal_connect(G_OBJECT(t->backward), "clicked",
7028 G_CALLBACK(backward_cb), t);
7029 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7031 /* forward button */
7032 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7033 gtk_widget_set_sensitive(t->forward, FALSE);
7034 g_signal_connect(G_OBJECT(t->forward), "clicked",
7035 G_CALLBACK(forward_cb), t);
7036 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7037 FALSE, 0);
7039 /* stop button */
7040 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7041 gtk_widget_set_sensitive(t->stop, FALSE);
7042 g_signal_connect(G_OBJECT(t->stop), "clicked",
7043 G_CALLBACK(stop_cb), t);
7044 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7045 FALSE, 0);
7047 /* JS button */
7048 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7049 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7050 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7051 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7052 G_CALLBACK(js_toggle_cb), t);
7053 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7056 t->uri_entry = gtk_entry_new();
7057 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7058 G_CALLBACK(activate_uri_entry_cb), t);
7059 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7060 G_CALLBACK(entry_key_cb), t);
7061 completion_add(t);
7062 eb1 = gtk_hbox_new(FALSE, 0);
7063 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7064 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7065 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7067 /* search entry */
7068 if (fancy_bar && search_string) {
7069 GtkWidget *eb2;
7070 t->search_entry = gtk_entry_new();
7071 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7072 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7073 G_CALLBACK(activate_search_entry_cb), t);
7074 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7075 G_CALLBACK(entry_key_cb), t);
7076 gtk_widget_set_size_request(t->search_entry, -1, -1);
7077 eb2 = gtk_hbox_new(FALSE, 0);
7078 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7079 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7081 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7083 return (toolbar);
7086 void
7087 recalc_tabs(void)
7089 struct tab *t;
7091 TAILQ_FOREACH(t, &tabs, entry)
7092 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7096 undo_close_tab_save(struct tab *t)
7098 int m, n;
7099 const gchar *uri;
7100 struct undo *u1, *u2;
7101 GList *items;
7102 WebKitWebHistoryItem *item;
7104 if ((uri = get_uri(t->wv)) == NULL)
7105 return (1);
7107 u1 = g_malloc0(sizeof(struct undo));
7108 u1->uri = g_strdup(uri);
7110 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7112 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7113 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7114 u1->back = n;
7116 /* forward history */
7117 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7119 while (items) {
7120 item = items->data;
7121 u1->history = g_list_prepend(u1->history,
7122 webkit_web_history_item_copy(item));
7123 items = g_list_next(items);
7126 /* current item */
7127 if (m) {
7128 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7129 u1->history = g_list_prepend(u1->history,
7130 webkit_web_history_item_copy(item));
7133 /* back history */
7134 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7136 while (items) {
7137 item = items->data;
7138 u1->history = g_list_prepend(u1->history,
7139 webkit_web_history_item_copy(item));
7140 items = g_list_next(items);
7143 TAILQ_INSERT_HEAD(&undos, u1, entry);
7145 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7146 u2 = TAILQ_LAST(&undos, undo_tailq);
7147 TAILQ_REMOVE(&undos, u2, entry);
7148 g_free(u2->uri);
7149 g_list_free(u2->history);
7150 g_free(u2);
7151 } else
7152 undo_count++;
7154 return (0);
7157 void
7158 delete_tab(struct tab *t)
7160 struct karg a;
7162 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7164 if (t == NULL)
7165 return;
7167 TAILQ_REMOVE(&tabs, t, entry);
7169 /* halt all webkit activity */
7170 abort_favicon_download(t);
7171 webkit_web_view_stop_loading(t->wv);
7172 undo_close_tab_save(t);
7174 if (browser_mode == XT_BM_KIOSK) {
7175 gtk_widget_destroy(t->uri_entry);
7176 gtk_widget_destroy(t->stop);
7177 gtk_widget_destroy(t->js_toggle);
7180 gtk_widget_destroy(t->vbox);
7181 g_free(t->user_agent);
7182 g_free(t->stylesheet);
7183 g_free(t);
7185 recalc_tabs();
7186 if (TAILQ_EMPTY(&tabs)) {
7187 if (browser_mode == XT_BM_KIOSK)
7188 create_new_tab(home, NULL, 1);
7189 else
7190 create_new_tab(NULL, NULL, 1);
7193 /* recreate session */
7194 if (session_autosave) {
7195 a.s = NULL;
7196 save_tabs(t, &a);
7200 void
7201 adjustfont_webkit(struct tab *t, int adjust)
7203 gfloat zoom;
7205 if (t == NULL) {
7206 show_oops_s("adjustfont_webkit invalid parameters");
7207 return;
7210 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7211 if (adjust == XT_FONT_SET) {
7212 t->font_size = default_font_size;
7213 zoom = default_zoom_level;
7214 t->font_size += adjust;
7215 g_object_set(G_OBJECT(t->settings), "default-font-size",
7216 t->font_size, (char *)NULL);
7217 g_object_get(G_OBJECT(t->settings), "default-font-size",
7218 &t->font_size, (char *)NULL);
7219 } else {
7220 t->font_size += adjust;
7221 zoom += adjust/25.0;
7222 if (zoom < 0.0) {
7223 zoom = 0.04;
7226 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7227 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7230 void
7231 append_tab(struct tab *t)
7233 if (t == NULL)
7234 return;
7236 TAILQ_INSERT_TAIL(&tabs, t, entry);
7237 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7240 struct tab *
7241 create_new_tab(char *title, struct undo *u, int focus)
7243 struct tab *t, *tt;
7244 int load = 1, id, notfound;
7245 GtkWidget *b, *bb;
7246 WebKitWebHistoryItem *item;
7247 GList *items;
7248 GdkColor color;
7250 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7252 if (tabless && !TAILQ_EMPTY(&tabs)) {
7253 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7254 return (NULL);
7257 t = g_malloc0(sizeof *t);
7259 if (title == NULL) {
7260 title = "(untitled)";
7261 load = 0;
7264 t->vbox = gtk_vbox_new(FALSE, 0);
7266 /* label + button for tab */
7267 b = gtk_hbox_new(FALSE, 0);
7268 t->tab_content = b;
7270 #if GTK_CHECK_VERSION(2, 20, 0)
7271 t->spinner = gtk_spinner_new ();
7272 #endif
7273 t->label = gtk_label_new(title);
7274 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7275 gtk_widget_set_size_request(t->label, 100, 0);
7276 gtk_widget_set_size_request(b, 130, 0);
7278 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7279 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7280 #if GTK_CHECK_VERSION(2, 20, 0)
7281 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7282 #endif
7284 /* toolbar */
7285 if (browser_mode == XT_BM_KIOSK)
7286 t->toolbar = create_kiosk_toolbar(t);
7287 else
7288 t->toolbar = create_toolbar(t);
7290 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7292 /* browser */
7293 t->browser_win = create_browser(t);
7294 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7296 /* oops message for user feedback */
7297 t->oops = gtk_entry_new();
7298 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7299 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7300 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7301 gdk_color_parse(XT_COLOR_RED, &color);
7302 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7303 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7305 /* command entry */
7306 t->cmd = gtk_entry_new();
7307 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7308 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7309 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7311 /* status bar */
7312 t->statusbar = gtk_entry_new();
7313 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7314 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7315 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7316 gdk_color_parse(XT_COLOR_BLACK, &color);
7317 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7318 gdk_color_parse(XT_COLOR_WHITE, &color);
7319 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7320 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7322 /* xtp meaning is normal by default */
7323 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7325 /* set empty favicon */
7326 xt_icon_from_name(t, "text-html");
7328 /* and show it all */
7329 gtk_widget_show_all(b);
7330 gtk_widget_show_all(t->vbox);
7332 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7333 append_tab(t);
7334 else {
7335 notfound = 1;
7336 id = gtk_notebook_get_current_page(notebook);
7337 TAILQ_FOREACH(tt, &tabs, entry) {
7338 if (tt->tab_id == id) {
7339 notfound = 0;
7340 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7341 gtk_notebook_insert_page(notebook, t->vbox, b,
7342 id + 1);
7343 recalc_tabs();
7344 break;
7347 if (notfound)
7348 append_tab(t);
7351 #if GTK_CHECK_VERSION(2, 20, 0)
7352 /* turn spinner off if we are a new tab without uri */
7353 if (!load) {
7354 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7355 gtk_widget_hide(t->spinner);
7357 #endif
7358 /* make notebook tabs reorderable */
7359 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7361 g_object_connect(G_OBJECT(t->cmd),
7362 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7363 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7364 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7365 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7366 (char *)NULL);
7368 /* reuse wv_button_cb to hide oops */
7369 g_object_connect(G_OBJECT(t->oops),
7370 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7371 (char *)NULL);
7373 g_object_connect(G_OBJECT(t->wv),
7374 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7375 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7376 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7377 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7378 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7379 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7380 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7381 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7382 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7383 "signal::event", G_CALLBACK(webview_event_cb), t,
7384 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7385 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7386 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7387 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7388 #endif
7389 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7390 (char *)NULL);
7391 g_signal_connect(t->wv,
7392 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7393 g_signal_connect(t->wv,
7394 "notify::title", G_CALLBACK(notify_title_cb), t);
7396 /* hijack the unused keys as if we were the browser */
7397 g_object_connect(G_OBJECT(t->toolbar),
7398 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7399 (char *)NULL);
7401 g_signal_connect(G_OBJECT(bb), "button_press_event",
7402 G_CALLBACK(tab_close_cb), t);
7404 /* hide stuff */
7405 hide_cmd(t);
7406 hide_oops(t);
7407 url_set_visibility();
7408 statusbar_set_visibility();
7410 if (focus) {
7411 gtk_notebook_set_current_page(notebook, t->tab_id);
7412 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7413 t->tab_id);
7416 if (load) {
7417 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7418 load_uri(t, title);
7419 } else {
7420 if (show_url == 1)
7421 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7422 else
7423 focus_webview(t);
7426 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7427 /* restore the tab's history */
7428 if (u && u->history) {
7429 items = u->history;
7430 while (items) {
7431 item = items->data;
7432 webkit_web_back_forward_list_add_item(t->bfl, item);
7433 items = g_list_next(items);
7436 item = g_list_nth_data(u->history, u->back);
7437 if (item)
7438 webkit_web_view_go_to_back_forward_item(t->wv, item);
7440 g_list_free(items);
7441 g_list_free(u->history);
7442 } else
7443 webkit_web_back_forward_list_clear(t->bfl);
7445 return (t);
7448 void
7449 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7450 gpointer *udata)
7452 struct tab *t;
7453 const gchar *uri;
7455 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7457 TAILQ_FOREACH(t, &tabs, entry) {
7458 if (t->tab_id == pn) {
7459 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7460 "%d\n", pn);
7462 uri = webkit_web_view_get_title(t->wv);
7463 if (uri == NULL)
7464 uri = XT_NAME;
7465 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7467 hide_cmd(t);
7468 hide_oops(t);
7470 if (t->focus_wv)
7471 focus_webview(t);
7476 void
7477 menuitem_response(struct tab *t)
7479 gtk_notebook_set_current_page(notebook, t->tab_id);
7482 gboolean
7483 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7485 GtkWidget *menu, *menu_items;
7486 GdkEventButton *bevent;
7487 const gchar *uri;
7488 struct tab *ti;
7490 if (event->type == GDK_BUTTON_PRESS) {
7491 bevent = (GdkEventButton *) event;
7492 menu = gtk_menu_new();
7494 TAILQ_FOREACH(ti, &tabs, entry) {
7495 if ((uri = get_uri(ti->wv)) == NULL)
7496 /* XXX make sure there is something to print */
7497 /* XXX add gui pages in here to look purdy */
7498 uri = "(untitled)";
7499 menu_items = gtk_menu_item_new_with_label(uri);
7500 gtk_menu_append(GTK_MENU (menu), menu_items);
7501 gtk_widget_show(menu_items);
7503 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7504 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7505 (gpointer)ti);
7508 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7509 bevent->button, bevent->time);
7511 /* unref object so it'll free itself when popped down */
7512 g_object_ref_sink(menu);
7513 g_object_unref(menu);
7515 return (TRUE /* eat event */);
7518 return (FALSE /* propagate */);
7522 icon_size_map(int icon_size)
7524 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7525 icon_size > GTK_ICON_SIZE_DIALOG)
7526 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7528 return (icon_size);
7531 GtkWidget *
7532 create_button(char *name, char *stockid, int size)
7534 GtkWidget *button, *image;
7535 gchar *rcstring;
7536 int gtk_icon_size;
7538 rcstring = g_strdup_printf(
7539 "style \"%s-style\"\n"
7540 "{\n"
7541 " GtkWidget::focus-padding = 0\n"
7542 " GtkWidget::focus-line-width = 0\n"
7543 " xthickness = 0\n"
7544 " ythickness = 0\n"
7545 "}\n"
7546 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7547 gtk_rc_parse_string(rcstring);
7548 g_free(rcstring);
7549 button = gtk_button_new();
7550 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7551 gtk_icon_size = icon_size_map(size ? size : icon_size);
7553 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7554 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7555 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7556 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7557 gtk_widget_set_name(button, name);
7558 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7560 return (button);
7563 void
7564 button_set_stockid(GtkWidget *button, char *stockid)
7566 GtkWidget *image;
7568 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7569 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7570 gtk_button_set_image(GTK_BUTTON(button), image);
7573 void
7574 create_canvas(void)
7576 GtkWidget *vbox;
7577 GList *l = NULL;
7578 GdkPixbuf *pb;
7579 char file[PATH_MAX];
7580 int i;
7582 vbox = gtk_vbox_new(FALSE, 0);
7583 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7584 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7585 gtk_notebook_set_tab_hborder(notebook, 0);
7586 gtk_notebook_set_tab_vborder(notebook, 0);
7587 gtk_notebook_set_scrollable(notebook, TRUE);
7588 notebook_tab_set_visibility(notebook);
7589 gtk_notebook_set_show_border(notebook, FALSE);
7590 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7592 abtn = gtk_button_new();
7593 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7594 gtk_widget_set_size_request(arrow, -1, -1);
7595 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7596 gtk_widget_set_size_request(abtn, -1, 20);
7598 #if GTK_CHECK_VERSION(2, 20, 0)
7599 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7600 #endif
7601 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7602 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7603 gtk_widget_set_size_request(vbox, -1, -1);
7605 g_object_connect(G_OBJECT(notebook),
7606 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7607 (char *)NULL);
7608 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7609 G_CALLBACK(arrow_cb), NULL);
7611 main_window = create_window();
7612 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7613 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7615 /* icons */
7616 for (i = 0; i < LENGTH(icons); i++) {
7617 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7618 pb = gdk_pixbuf_new_from_file(file, NULL);
7619 l = g_list_append(l, pb);
7621 gtk_window_set_default_icon_list(l);
7623 gtk_widget_show_all(abtn);
7624 gtk_widget_show_all(main_window);
7627 void
7628 set_hook(void **hook, char *name)
7630 if (hook == NULL)
7631 errx(1, "set_hook");
7633 if (*hook == NULL) {
7634 *hook = dlsym(RTLD_NEXT, name);
7635 if (*hook == NULL)
7636 errx(1, "can't hook %s", name);
7640 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7641 gboolean
7642 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7644 g_return_val_if_fail(cookie1, FALSE);
7645 g_return_val_if_fail(cookie2, FALSE);
7647 return (!strcmp (cookie1->name, cookie2->name) &&
7648 !strcmp (cookie1->value, cookie2->value) &&
7649 !strcmp (cookie1->path, cookie2->path) &&
7650 !strcmp (cookie1->domain, cookie2->domain));
7653 void
7654 transfer_cookies(void)
7656 GSList *cf;
7657 SoupCookie *sc, *pc;
7659 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7661 for (;cf; cf = cf->next) {
7662 pc = cf->data;
7663 sc = soup_cookie_copy(pc);
7664 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7667 soup_cookies_free(cf);
7670 void
7671 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7673 GSList *cf;
7674 SoupCookie *ci;
7676 print_cookie("soup_cookie_jar_delete_cookie", c);
7678 if (cookies_enabled == 0)
7679 return;
7681 if (jar == NULL || c == NULL)
7682 return;
7684 /* find and remove from persistent jar */
7685 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7687 for (;cf; cf = cf->next) {
7688 ci = cf->data;
7689 if (soup_cookie_equal(ci, c)) {
7690 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7691 break;
7695 soup_cookies_free(cf);
7697 /* delete from session jar */
7698 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7701 void
7702 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7704 struct domain *d = NULL;
7705 SoupCookie *c;
7706 FILE *r_cookie_f;
7708 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7709 jar, p_cookiejar, s_cookiejar);
7711 if (cookies_enabled == 0)
7712 return;
7714 /* see if we are up and running */
7715 if (p_cookiejar == NULL) {
7716 _soup_cookie_jar_add_cookie(jar, cookie);
7717 return;
7719 /* disallow p_cookiejar adds, shouldn't happen */
7720 if (jar == p_cookiejar)
7721 return;
7723 /* sanity */
7724 if (jar == NULL || cookie == NULL)
7725 return;
7727 if (enable_cookie_whitelist &&
7728 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7729 blocked_cookies++;
7730 DNPRINTF(XT_D_COOKIE,
7731 "soup_cookie_jar_add_cookie: reject %s\n",
7732 cookie->domain);
7733 if (save_rejected_cookies) {
7734 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7735 show_oops_s("can't open reject cookie file");
7736 return;
7738 fseek(r_cookie_f, 0, SEEK_END);
7739 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7740 cookie->http_only ? "#HttpOnly_" : "",
7741 cookie->domain,
7742 *cookie->domain == '.' ? "TRUE" : "FALSE",
7743 cookie->path,
7744 cookie->secure ? "TRUE" : "FALSE",
7745 cookie->expires ?
7746 (gulong)soup_date_to_time_t(cookie->expires) :
7748 cookie->name,
7749 cookie->value);
7750 fflush(r_cookie_f);
7751 fclose(r_cookie_f);
7753 if (!allow_volatile_cookies)
7754 return;
7757 if (cookie->expires == NULL && session_timeout) {
7758 soup_cookie_set_expires(cookie,
7759 soup_date_new_from_now(session_timeout));
7760 print_cookie("modified add cookie", cookie);
7763 /* see if we are white listed for persistence */
7764 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7765 /* add to persistent jar */
7766 c = soup_cookie_copy(cookie);
7767 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7768 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7771 /* add to session jar */
7772 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7773 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7776 void
7777 setup_cookies(void)
7779 char file[PATH_MAX];
7781 set_hook((void *)&_soup_cookie_jar_add_cookie,
7782 "soup_cookie_jar_add_cookie");
7783 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7784 "soup_cookie_jar_delete_cookie");
7786 if (cookies_enabled == 0)
7787 return;
7790 * the following code is intricate due to overriding several libsoup
7791 * functions.
7792 * do not alter order of these operations.
7795 /* rejected cookies */
7796 if (save_rejected_cookies)
7797 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7799 /* persistent cookies */
7800 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7801 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7803 /* session cookies */
7804 s_cookiejar = soup_cookie_jar_new();
7805 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7806 cookie_policy, (void *)NULL);
7807 transfer_cookies();
7809 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7812 void
7813 setup_proxy(char *uri)
7815 if (proxy_uri) {
7816 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7817 soup_uri_free(proxy_uri);
7818 proxy_uri = NULL;
7820 if (http_proxy) {
7821 if (http_proxy != uri) {
7822 g_free(http_proxy);
7823 http_proxy = NULL;
7827 if (uri) {
7828 http_proxy = g_strdup(uri);
7829 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7830 proxy_uri = soup_uri_new(http_proxy);
7831 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7836 send_cmd_to_socket(char *cmd)
7838 int s, len, rv = 1;
7839 struct sockaddr_un sa;
7841 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7842 warnx("%s: socket", __func__);
7843 return (rv);
7846 sa.sun_family = AF_UNIX;
7847 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7848 work_dir, XT_SOCKET_FILE);
7849 len = SUN_LEN(&sa);
7851 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7852 warnx("%s: connect", __func__);
7853 goto done;
7856 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7857 warnx("%s: send", __func__);
7858 goto done;
7861 rv = 0;
7862 done:
7863 close(s);
7864 return (rv);
7867 void
7868 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7870 int s, n;
7871 char str[XT_MAX_URL_LENGTH];
7872 socklen_t t = sizeof(struct sockaddr_un);
7873 struct sockaddr_un sa;
7874 struct passwd *p;
7875 uid_t uid;
7876 gid_t gid;
7877 struct tab *tt;
7879 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7880 warn("accept");
7881 return;
7884 if (getpeereid(s, &uid, &gid) == -1) {
7885 warn("getpeereid");
7886 return;
7888 if (uid != getuid() || gid != getgid()) {
7889 warnx("unauthorized user");
7890 return;
7893 p = getpwuid(uid);
7894 if (p == NULL) {
7895 warnx("not a valid user");
7896 return;
7899 n = recv(s, str, sizeof(str), 0);
7900 if (n <= 0)
7901 return;
7903 tt = TAILQ_LAST(&tabs, tab_list);
7904 cmd_execute(tt, str);
7908 is_running(void)
7910 int s, len, rv = 1;
7911 struct sockaddr_un sa;
7913 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7914 warn("is_running: socket");
7915 return (-1);
7918 sa.sun_family = AF_UNIX;
7919 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7920 work_dir, XT_SOCKET_FILE);
7921 len = SUN_LEN(&sa);
7923 /* connect to see if there is a listener */
7924 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7925 rv = 0; /* not running */
7926 else
7927 rv = 1; /* already running */
7929 close(s);
7931 return (rv);
7935 build_socket(void)
7937 int s, len;
7938 struct sockaddr_un sa;
7940 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7941 warn("build_socket: socket");
7942 return (-1);
7945 sa.sun_family = AF_UNIX;
7946 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7947 work_dir, XT_SOCKET_FILE);
7948 len = SUN_LEN(&sa);
7950 /* connect to see if there is a listener */
7951 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7952 /* no listener so we will */
7953 unlink(sa.sun_path);
7955 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7956 warn("build_socket: bind");
7957 goto done;
7960 if (listen(s, 1) == -1) {
7961 warn("build_socket: listen");
7962 goto done;
7965 return (s);
7968 done:
7969 close(s);
7970 return (-1);
7973 static gboolean
7974 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7975 GtkTreeIter *iter, struct tab *t)
7977 gchar *value;
7979 gtk_tree_model_get(model, iter, 0, &value, -1);
7980 load_uri(t, value);
7982 return (FALSE);
7985 void
7986 completion_add_uri(const gchar *uri)
7988 GtkTreeIter iter;
7990 /* add uri to list_store */
7991 gtk_list_store_append(completion_model, &iter);
7992 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
7995 gboolean
7996 completion_match(GtkEntryCompletion *completion, const gchar *key,
7997 GtkTreeIter *iter, gpointer user_data)
7999 gchar *value;
8000 gboolean match = FALSE;
8002 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8003 -1);
8005 if (value == NULL)
8006 return FALSE;
8008 match = match_uri(value, key);
8010 g_free(value);
8011 return (match);
8014 void
8015 completion_add(struct tab *t)
8017 /* enable completion for tab */
8018 t->completion = gtk_entry_completion_new();
8019 gtk_entry_completion_set_text_column(t->completion, 0);
8020 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8021 gtk_entry_completion_set_model(t->completion,
8022 GTK_TREE_MODEL(completion_model));
8023 gtk_entry_completion_set_match_func(t->completion, completion_match,
8024 NULL, NULL);
8025 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8026 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8027 G_CALLBACK(completion_select_cb), t);
8030 void
8031 usage(void)
8033 fprintf(stderr,
8034 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8035 exit(0);
8039 main(int argc, char *argv[])
8041 struct stat sb;
8042 int c, s, optn = 0, opte = 0, focus = 1;
8043 char conf[PATH_MAX] = { '\0' };
8044 char file[PATH_MAX];
8045 char *env_proxy = NULL;
8046 FILE *f = NULL;
8047 struct karg a;
8048 struct sigaction sact;
8049 gchar *priority = g_strdup("NORMAL");
8051 start_argv = argv;
8053 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8055 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8056 switch (c) {
8057 case 'S':
8058 show_url = 0;
8059 break;
8060 case 'T':
8061 show_tabs = 0;
8062 break;
8063 case 'V':
8064 errx(0 , "Version: %s", version);
8065 break;
8066 case 'f':
8067 strlcpy(conf, optarg, sizeof(conf));
8068 break;
8069 case 's':
8070 strlcpy(named_session, optarg, sizeof(named_session));
8071 break;
8072 case 't':
8073 tabless = 1;
8074 break;
8075 case 'n':
8076 optn = 1;
8077 break;
8078 case 'e':
8079 opte = 1;
8080 break;
8081 default:
8082 usage();
8083 /* NOTREACHED */
8086 argc -= optind;
8087 argv += optind;
8089 RB_INIT(&hl);
8090 RB_INIT(&js_wl);
8091 RB_INIT(&downloads);
8093 TAILQ_INIT(&tabs);
8094 TAILQ_INIT(&mtl);
8095 TAILQ_INIT(&aliases);
8096 TAILQ_INIT(&undos);
8097 TAILQ_INIT(&kbl);
8099 init_keybindings();
8101 gnutls_global_init();
8103 /* generate session keys for xtp pages */
8104 generate_xtp_session_key(&dl_session_key);
8105 generate_xtp_session_key(&hl_session_key);
8106 generate_xtp_session_key(&cl_session_key);
8107 generate_xtp_session_key(&fl_session_key);
8109 /* prepare gtk */
8110 gtk_init(&argc, &argv);
8111 if (!g_thread_supported())
8112 g_thread_init(NULL);
8114 /* signals */
8115 bzero(&sact, sizeof(sact));
8116 sigemptyset(&sact.sa_mask);
8117 sact.sa_handler = sigchild;
8118 sact.sa_flags = SA_NOCLDSTOP;
8119 sigaction(SIGCHLD, &sact, NULL);
8121 /* set download dir */
8122 pwd = getpwuid(getuid());
8123 if (pwd == NULL)
8124 errx(1, "invalid user %d", getuid());
8125 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8127 /* set default string settings */
8128 home = g_strdup("http://www.peereboom.us");
8129 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8130 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8131 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8133 /* read config file */
8134 if (strlen(conf) == 0)
8135 snprintf(conf, sizeof conf, "%s/.%s",
8136 pwd->pw_dir, XT_CONF_FILE);
8137 config_parse(conf, 0);
8139 /* working directory */
8140 if (strlen(work_dir) == 0)
8141 snprintf(work_dir, sizeof work_dir, "%s/%s",
8142 pwd->pw_dir, XT_DIR);
8143 if (stat(work_dir, &sb)) {
8144 if (mkdir(work_dir, S_IRWXU) == -1)
8145 err(1, "mkdir work_dir");
8146 if (stat(work_dir, &sb))
8147 err(1, "stat work_dir");
8149 if (S_ISDIR(sb.st_mode) == 0)
8150 errx(1, "%s not a dir", work_dir);
8151 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8152 warnx("fixing invalid permissions on %s", work_dir);
8153 if (chmod(work_dir, S_IRWXU) == -1)
8154 err(1, "chmod");
8157 /* icon cache dir */
8158 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8159 if (stat(cache_dir, &sb)) {
8160 if (mkdir(cache_dir, S_IRWXU) == -1)
8161 err(1, "mkdir cache_dir");
8162 if (stat(cache_dir, &sb))
8163 err(1, "stat cache_dir");
8165 if (S_ISDIR(sb.st_mode) == 0)
8166 errx(1, "%s not a dir", cache_dir);
8167 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8168 warnx("fixing invalid permissions on %s", cache_dir);
8169 if (chmod(cache_dir, S_IRWXU) == -1)
8170 err(1, "chmod");
8173 /* certs dir */
8174 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8175 if (stat(certs_dir, &sb)) {
8176 if (mkdir(certs_dir, S_IRWXU) == -1)
8177 err(1, "mkdir certs_dir");
8178 if (stat(certs_dir, &sb))
8179 err(1, "stat certs_dir");
8181 if (S_ISDIR(sb.st_mode) == 0)
8182 errx(1, "%s not a dir", certs_dir);
8183 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8184 warnx("fixing invalid permissions on %s", certs_dir);
8185 if (chmod(certs_dir, S_IRWXU) == -1)
8186 err(1, "chmod");
8189 /* sessions dir */
8190 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8191 work_dir, XT_SESSIONS_DIR);
8192 if (stat(sessions_dir, &sb)) {
8193 if (mkdir(sessions_dir, S_IRWXU) == -1)
8194 err(1, "mkdir sessions_dir");
8195 if (stat(sessions_dir, &sb))
8196 err(1, "stat sessions_dir");
8198 if (S_ISDIR(sb.st_mode) == 0)
8199 errx(1, "%s not a dir", sessions_dir);
8200 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8201 warnx("fixing invalid permissions on %s", sessions_dir);
8202 if (chmod(sessions_dir, S_IRWXU) == -1)
8203 err(1, "chmod");
8205 /* runtime settings that can override config file */
8206 if (runtime_settings[0] != '\0')
8207 config_parse(runtime_settings, 1);
8209 /* download dir */
8210 if (!strcmp(download_dir, pwd->pw_dir))
8211 strlcat(download_dir, "/downloads", sizeof download_dir);
8212 if (stat(download_dir, &sb)) {
8213 if (mkdir(download_dir, S_IRWXU) == -1)
8214 err(1, "mkdir download_dir");
8215 if (stat(download_dir, &sb))
8216 err(1, "stat download_dir");
8218 if (S_ISDIR(sb.st_mode) == 0)
8219 errx(1, "%s not a dir", download_dir);
8220 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8221 warnx("fixing invalid permissions on %s", download_dir);
8222 if (chmod(download_dir, S_IRWXU) == -1)
8223 err(1, "chmod");
8226 /* favorites file */
8227 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8228 if (stat(file, &sb)) {
8229 warnx("favorites file doesn't exist, creating it");
8230 if ((f = fopen(file, "w")) == NULL)
8231 err(1, "favorites");
8232 fclose(f);
8235 /* cookies */
8236 session = webkit_get_default_session();
8237 /* XXX ssl-priority property not quite available yet */
8238 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8239 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8240 (char *)NULL);
8241 else
8242 warnx("session does not have \"ssl-priority\" property");
8243 setup_cookies();
8245 /* certs */
8246 if (ssl_ca_file) {
8247 if (stat(ssl_ca_file, &sb)) {
8248 warn("no CA file: %s", ssl_ca_file);
8249 g_free(ssl_ca_file);
8250 ssl_ca_file = NULL;
8251 } else
8252 g_object_set(session,
8253 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8254 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8255 (void *)NULL);
8258 /* proxy */
8259 env_proxy = getenv("http_proxy");
8260 if (env_proxy)
8261 setup_proxy(env_proxy);
8262 else
8263 setup_proxy(http_proxy);
8265 if (opte) {
8266 send_cmd_to_socket(argv[0]);
8267 exit(0);
8270 /* set some connection parameters */
8271 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8272 g_object_set(session, "max-conns-per-host", max_host_connections,
8273 (char *)NULL);
8275 /* see if there is already an xxxterm running */
8276 if (single_instance && is_running()) {
8277 optn = 1;
8278 warnx("already running");
8281 char *cmd = NULL;
8282 if (optn) {
8283 while (argc) {
8284 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8285 send_cmd_to_socket(cmd);
8286 if (cmd)
8287 g_free(cmd);
8289 argc--;
8290 argv++;
8292 exit(0);
8295 /* uri completion */
8296 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8298 /* go graphical */
8299 create_canvas();
8301 if (save_global_history)
8302 restore_global_history();
8304 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8305 restore_saved_tabs();
8306 else {
8307 a.s = named_session;
8308 a.i = XT_SES_DONOTHING;
8309 open_tabs(NULL, &a);
8312 while (argc) {
8313 create_new_tab(argv[0], NULL, focus);
8314 focus = 0;
8316 argc--;
8317 argv++;
8320 if (TAILQ_EMPTY(&tabs))
8321 create_new_tab(home, NULL, 1);
8323 if (enable_socket)
8324 if ((s = build_socket()) != -1)
8325 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8327 gtk_main();
8329 gnutls_global_deinit();
8331 return (0);