Low contrast web browsing (yay!)
[xxxterm.git] / xxxterm.c
bloba26ab324b6ae0ce8f5b05bcca48620e7fa254a64
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>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 * TODO:
23 * multi letter commands
24 * pre and post counts for commands
25 * autocompletion on various inputs
26 * create privacy browsing
27 * - encrypted local data
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <err.h>
33 #include <pwd.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <pthread.h>
37 #include <dlfcn.h>
38 #include <errno.h>
39 #include <signal.h>
40 #include <libgen.h>
41 #include <ctype.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
49 #include <libutil.h>
50 #include "freebsd/util.h"
51 #include <sys/tree.h>
52 #else /* OpenBSD */
53 #include <util.h>
54 #include <sys/tree.h>
55 #endif
56 #include <sys/queue.h>
57 #include <sys/stat.h>
58 #include <sys/socket.h>
59 #include <sys/un.h>
61 #include <gtk/gtk.h>
62 #include <gdk/gdkkeysyms.h>
63 #include <webkit/webkit.h>
64 #include <libsoup/soup.h>
65 #include <gnutls/gnutls.h>
66 #include <JavaScriptCore/JavaScript.h>
67 #include <gnutls/x509.h>
69 #include "javascript.h"
72 javascript.h borrowed from vimprobable2 under the following license:
74 Copyright (c) 2009 Leon Winter
75 Copyright (c) 2009 Hannes Schueller
76 Copyright (c) 2009 Matto Fransen
78 Permission is hereby granted, free of charge, to any person obtaining a copy
79 of this software and associated documentation files (the "Software"), to deal
80 in the Software without restriction, including without limitation the rights
81 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
82 copies of the Software, and to permit persons to whom the Software is
83 furnished to do so, subject to the following conditions:
85 The above copyright notice and this permission notice shall be included in
86 all copies or substantial portions of the Software.
88 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
89 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
90 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
91 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
92 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
93 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
94 THE SOFTWARE.
97 static char *version = "$xxxterm$";
99 /* hooked functions */
100 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
101 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
102 SoupCookie *);
104 /*#define XT_DEBUG*/
105 #ifdef XT_DEBUG
106 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
107 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
108 #define XT_D_MOVE 0x0001
109 #define XT_D_KEY 0x0002
110 #define XT_D_TAB 0x0004
111 #define XT_D_URL 0x0008
112 #define XT_D_CMD 0x0010
113 #define XT_D_NAV 0x0020
114 #define XT_D_DOWNLOAD 0x0040
115 #define XT_D_CONFIG 0x0080
116 #define XT_D_JS 0x0100
117 #define XT_D_FAVORITE 0x0200
118 #define XT_D_PRINTING 0x0400
119 #define XT_D_COOKIE 0x0800
120 #define XT_D_KEYBINDING 0x1000
121 u_int32_t swm_debug = 0
122 | XT_D_MOVE
123 | XT_D_KEY
124 | XT_D_TAB
125 | XT_D_URL
126 | XT_D_CMD
127 | XT_D_NAV
128 | XT_D_DOWNLOAD
129 | XT_D_CONFIG
130 | XT_D_JS
131 | XT_D_FAVORITE
132 | XT_D_PRINTING
133 | XT_D_COOKIE
134 | XT_D_KEYBINDING
136 #else
137 #define DPRINTF(x...)
138 #define DNPRINTF(n,x...)
139 #endif
141 #define LENGTH(x) (sizeof x / sizeof x[0])
142 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
143 ~(GDK_BUTTON1_MASK) & \
144 ~(GDK_BUTTON2_MASK) & \
145 ~(GDK_BUTTON3_MASK) & \
146 ~(GDK_BUTTON4_MASK) & \
147 ~(GDK_BUTTON5_MASK))
149 char *icons[] = {
150 "xxxtermicon16.png",
151 "xxxtermicon32.png",
152 "xxxtermicon48.png",
153 "xxxtermicon64.png",
154 "xxxtermicon128.png"
157 struct tab {
158 TAILQ_ENTRY(tab) entry;
159 GtkWidget *vbox;
160 GtkWidget *tab_content;
161 GtkWidget *label;
162 GtkWidget *spinner;
163 GtkWidget *uri_entry;
164 GtkWidget *search_entry;
165 GtkWidget *toolbar;
166 GtkWidget *browser_win;
167 GtkWidget *statusbar;
168 GtkWidget *cmd;
169 GtkWidget *oops;
170 GtkWidget *backward;
171 GtkWidget *forward;
172 GtkWidget *stop;
173 GtkWidget *js_toggle;
174 GtkEntryCompletion *completion;
175 guint tab_id;
176 WebKitWebView *wv;
178 WebKitWebHistoryItem *item;
179 WebKitWebBackForwardList *bfl;
181 /* favicon */
182 WebKitNetworkRequest *icon_request;
183 WebKitDownload *icon_download;
184 GdkPixbuf *icon_pixbuf;
185 gchar *icon_dest_uri;
187 /* adjustments for browser */
188 GtkScrollbar *sb_h;
189 GtkScrollbar *sb_v;
190 GtkAdjustment *adjust_h;
191 GtkAdjustment *adjust_v;
193 /* flags */
194 int focus_wv;
195 int ctrl_click;
196 gchar *status;
197 int xtp_meaning; /* identifies dls/favorites */
199 /* hints */
200 int hints_on;
201 int hint_mode;
202 #define XT_HINT_NONE (0)
203 #define XT_HINT_NUMERICAL (1)
204 #define XT_HINT_ALPHANUM (2)
205 char hint_buf[128];
206 char hint_num[128];
208 /* custom stylesheet */
209 int styled;
210 char *stylesheet;
212 /* search */
213 char *search_text;
214 int search_forward;
216 /* settings */
217 WebKitWebSettings *settings;
218 int font_size;
219 gchar *user_agent;
221 TAILQ_HEAD(tab_list, tab);
223 struct history {
224 RB_ENTRY(history) entry;
225 const gchar *uri;
226 const gchar *title;
228 RB_HEAD(history_list, history);
230 struct download {
231 RB_ENTRY(download) entry;
232 int id;
233 WebKitDownload *download;
234 struct tab *tab;
236 RB_HEAD(download_list, download);
238 struct domain {
239 RB_ENTRY(domain) entry;
240 gchar *d;
241 int handy; /* app use */
243 RB_HEAD(domain_list, domain);
245 struct undo {
246 TAILQ_ENTRY(undo) entry;
247 gchar *uri;
248 GList *history;
249 int back; /* Keeps track of how many back
250 * history items there are. */
252 TAILQ_HEAD(undo_tailq, undo);
254 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
255 int next_download_id = 1;
257 struct karg {
258 int i;
259 char *s;
262 /* defines */
263 #define XT_NAME ("XXXTerm")
264 #define XT_DIR (".xxxterm")
265 #define XT_CACHE_DIR ("cache")
266 #define XT_CERT_DIR ("certs/")
267 #define XT_SESSIONS_DIR ("sessions/")
268 #define XT_CONF_FILE ("xxxterm.conf")
269 #define XT_FAVS_FILE ("favorites")
270 #define XT_SAVED_TABS_FILE ("main_session")
271 #define XT_RESTART_TABS_FILE ("restart_tabs")
272 #define XT_SOCKET_FILE ("socket")
273 #define XT_HISTORY_FILE ("history")
274 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
275 #define XT_CB_HANDLED (TRUE)
276 #define XT_CB_PASSTHROUGH (FALSE)
277 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
278 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
279 #define XT_DLMAN_REFRESH "10"
280 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
281 "td {overflow: hidden;" \
282 " padding: 2px 2px 2px 2px;" \
283 " border: 1px solid black}\n" \
284 "tr:hover {background: #ffff99 ;}\n" \
285 "th {background-color: #cccccc;" \
286 " border: 1px solid black}" \
287 "table {border-spacing: 0; " \
288 " width: 90%%;" \
289 " border: 1px black solid;}\n" \
290 ".progress-outer{" \
291 " border: 1px solid black;" \
292 " height: 8px;" \
293 " width: 90%%;}" \
294 ".progress-inner{" \
295 " float: left;" \
296 " height: 8px;" \
297 " background: green;}" \
298 ".dlstatus{" \
299 " font-size: small;" \
300 " text-align: center;}" \
301 "</style>\n\n"
302 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
303 #define XT_MAX_UNDO_CLOSE_TAB (32)
304 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
305 #define XT_PRINT_EXTRA_MARGIN 10
307 /* file sizes */
308 #define SZ_KB ((uint64_t) 1024)
309 #define SZ_MB (SZ_KB * SZ_KB)
310 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
311 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
314 * xxxterm "protocol" (xtp)
315 * We use this for managing stuff like downloads and favorites. They
316 * make magical HTML pages in memory which have xxxt:// links in order
317 * to communicate with xxxterm's internals. These links take the format:
318 * xxxt://class/session_key/action/arg
320 * Don't begin xtp class/actions as 0. atoi returns that on error.
322 * Typically we have not put addition of items in this framework, as
323 * adding items is either done via an ex-command or via a keybinding instead.
326 #define XT_XTP_STR "xxxt://"
328 /* XTP classes (xxxt://<class>) */
329 #define XT_XTP_DL 1 /* downloads */
330 #define XT_XTP_HL 2 /* history */
331 #define XT_XTP_CL 3 /* cookies */
332 #define XT_XTP_FL 4 /* favorites */
334 /* XTP download actions */
335 #define XT_XTP_DL_LIST 1
336 #define XT_XTP_DL_CANCEL 2
337 #define XT_XTP_DL_REMOVE 3
339 /* XTP history actions */
340 #define XT_XTP_HL_LIST 1
341 #define XT_XTP_HL_REMOVE 2
343 /* XTP cookie actions */
344 #define XT_XTP_CL_LIST 1
345 #define XT_XTP_CL_REMOVE 2
347 /* XTP cookie actions */
348 #define XT_XTP_FL_LIST 1
349 #define XT_XTP_FL_REMOVE 2
351 /* xtp tab meanings - identifies which tabs have xtp pages in */
352 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
353 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
354 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
355 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
356 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
358 /* actions */
359 #define XT_MOVE_INVALID (0)
360 #define XT_MOVE_DOWN (1)
361 #define XT_MOVE_UP (2)
362 #define XT_MOVE_BOTTOM (3)
363 #define XT_MOVE_TOP (4)
364 #define XT_MOVE_PAGEDOWN (5)
365 #define XT_MOVE_PAGEUP (6)
366 #define XT_MOVE_HALFDOWN (7)
367 #define XT_MOVE_HALFUP (8)
368 #define XT_MOVE_LEFT (9)
369 #define XT_MOVE_FARLEFT (10)
370 #define XT_MOVE_RIGHT (11)
371 #define XT_MOVE_FARRIGHT (12)
373 #define XT_TAB_LAST (-4)
374 #define XT_TAB_FIRST (-3)
375 #define XT_TAB_PREV (-2)
376 #define XT_TAB_NEXT (-1)
377 #define XT_TAB_INVALID (0)
378 #define XT_TAB_NEW (1)
379 #define XT_TAB_DELETE (2)
380 #define XT_TAB_DELQUIT (3)
381 #define XT_TAB_OPEN (4)
382 #define XT_TAB_UNDO_CLOSE (5)
383 #define XT_TAB_SHOW (6)
384 #define XT_TAB_HIDE (7)
386 #define XT_NAV_INVALID (0)
387 #define XT_NAV_BACK (1)
388 #define XT_NAV_FORWARD (2)
389 #define XT_NAV_RELOAD (3)
390 #define XT_NAV_RELOAD_CACHE (4)
392 #define XT_FOCUS_INVALID (0)
393 #define XT_FOCUS_URI (1)
394 #define XT_FOCUS_SEARCH (2)
396 #define XT_SEARCH_INVALID (0)
397 #define XT_SEARCH_NEXT (1)
398 #define XT_SEARCH_PREV (2)
400 #define XT_PASTE_CURRENT_TAB (0)
401 #define XT_PASTE_NEW_TAB (1)
403 #define XT_FONT_SET (0)
405 #define XT_URL_SHOW (1)
406 #define XT_URL_HIDE (2)
408 #define XT_STATUSBAR_SHOW (1)
409 #define XT_STATUSBAR_HIDE (2)
411 #define XT_WL_TOGGLE (1<<0)
412 #define XT_WL_ENABLE (1<<1)
413 #define XT_WL_DISABLE (1<<2)
414 #define XT_WL_FQDN (1<<3) /* default */
415 #define XT_WL_TOPLEVEL (1<<4)
417 #define XT_CMD_OPEN (0)
418 #define XT_CMD_OPEN_CURRENT (1)
419 #define XT_CMD_TABNEW (2)
420 #define XT_CMD_TABNEW_CURRENT (3)
422 #define XT_STATUS_NOTHING (0)
423 #define XT_STATUS_LINK (1)
424 #define XT_STATUS_URI (2)
425 #define XT_STATUS_LOADING (3)
427 #define XT_SES_DONOTHING (0)
428 #define XT_SES_CLOSETABS (1)
430 #define XT_COOKIE_NORMAL (0)
431 #define XT_COOKIE_WHITELIST (1)
433 /* mime types */
434 struct mime_type {
435 char *mt_type;
436 char *mt_action;
437 int mt_default;
438 TAILQ_ENTRY(mime_type) entry;
440 TAILQ_HEAD(mime_type_list, mime_type);
442 /* uri aliases */
443 struct alias {
444 char *a_name;
445 char *a_uri;
446 TAILQ_ENTRY(alias) entry;
448 TAILQ_HEAD(alias_list, alias);
450 /* settings that require restart */
451 int tabless = 0; /* allow only 1 tab */
452 int enable_socket = 0;
453 int single_instance = 0; /* only allow one xxxterm to run */
454 int fancy_bar = 1; /* fancy toolbar */
455 int browser_mode = XT_COOKIE_NORMAL;
457 /* runtime settings */
458 int show_tabs = 1; /* show tabs on notebook */
459 int show_url = 1; /* show url toolbar on notebook */
460 int show_statusbar = 0; /* vimperator style status bar */
461 int ctrl_click_focus = 0; /* ctrl click gets focus */
462 int cookies_enabled = 1; /* enable cookies */
463 int read_only_cookies = 0; /* enable to not write cookies */
464 int enable_scripts = 1;
465 int enable_plugins = 0;
466 int default_font_size = 12;
467 gfloat default_zoom_level = 1.0;
468 int window_height = 768;
469 int window_width = 1024;
470 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
471 unsigned refresh_interval = 10; /* download refresh interval */
472 int enable_cookie_whitelist = 0;
473 int enable_js_whitelist = 0;
474 time_t session_timeout = 3600; /* cookie session timeout */
475 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
476 char *ssl_ca_file = NULL;
477 char *resource_dir = NULL;
478 gboolean ssl_strict_certs = FALSE;
479 int append_next = 1; /* append tab after current tab */
480 char *home = NULL;
481 char *search_string = NULL;
482 char *http_proxy = NULL;
483 char download_dir[PATH_MAX];
484 char runtime_settings[PATH_MAX]; /* override of settings */
485 int allow_volatile_cookies = 0;
486 int save_global_history = 0; /* save global history to disk */
487 char *user_agent = NULL;
488 int save_rejected_cookies = 0;
489 time_t session_autosave = 0;
490 int guess_search = 0;
492 struct settings;
493 struct key_binding;
494 int set_download_dir(struct settings *, char *);
495 int set_work_dir(struct settings *, char *);
496 int set_runtime_dir(struct settings *, char *);
497 int set_browser_mode(struct settings *, char *);
498 int set_cookie_policy(struct settings *, char *);
499 int add_alias(struct settings *, char *);
500 int add_mime_type(struct settings *, char *);
501 int add_cookie_wl(struct settings *, char *);
502 int add_js_wl(struct settings *, char *);
503 int add_kb(struct settings *, char *);
504 void button_set_stockid(GtkWidget *, char *);
505 GtkWidget * create_button(char *, char *, int);
507 char *get_browser_mode(struct settings *);
508 char *get_cookie_policy(struct settings *);
510 char *get_download_dir(struct settings *);
511 char *get_work_dir(struct settings *);
512 char *get_runtime_dir(struct settings *);
514 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
515 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
516 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
517 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
518 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
520 struct special {
521 int (*set)(struct settings *, char *);
522 char *(*get)(struct settings *);
523 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
526 struct special s_browser_mode = {
527 set_browser_mode,
528 get_browser_mode,
529 NULL
532 struct special s_cookie = {
533 set_cookie_policy,
534 get_cookie_policy,
535 NULL
538 struct special s_alias = {
539 add_alias,
540 NULL,
541 walk_alias
544 struct special s_mime = {
545 add_mime_type,
546 NULL,
547 walk_mime_type
550 struct special s_js = {
551 add_js_wl,
552 NULL,
553 walk_js_wl
556 struct special s_kb = {
557 add_kb,
558 NULL,
559 walk_kb
562 struct special s_cookie_wl = {
563 add_cookie_wl,
564 NULL,
565 walk_cookie_wl
568 struct special s_download_dir = {
569 set_download_dir,
570 get_download_dir,
571 NULL
574 struct special s_work_dir = {
575 set_work_dir,
576 get_work_dir,
577 NULL
580 struct settings {
581 char *name;
582 int type;
583 #define XT_S_INVALID (0)
584 #define XT_S_INT (1)
585 #define XT_S_STR (2)
586 #define XT_S_FLOAT (3)
587 uint32_t flags;
588 #define XT_SF_RESTART (1<<0)
589 #define XT_SF_RUNTIME (1<<1)
590 int *ival;
591 char **sval;
592 struct special *s;
593 gfloat *fval;
594 } rs[] = {
595 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
596 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
597 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
598 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
599 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
600 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
601 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
602 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
603 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
604 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
605 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
606 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
607 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
608 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
609 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
610 { "home", XT_S_STR, 0, NULL, &home, NULL },
611 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
612 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
613 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
614 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
615 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
616 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
617 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
618 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
619 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
620 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
621 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
622 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
623 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
624 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
625 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
626 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
627 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
628 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
629 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
630 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
631 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
633 /* runtime settings */
634 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
635 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
636 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
637 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
638 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
641 int about(struct tab *, struct karg *);
642 int blank(struct tab *, struct karg *);
643 int cookie_show_wl(struct tab *, struct karg *);
644 int js_show_wl(struct tab *, struct karg *);
645 int help(struct tab *, struct karg *);
646 int set(struct tab *, struct karg *);
647 int stats(struct tab *, struct karg *);
648 int xtp_page_cl(struct tab *, struct karg *);
649 int xtp_page_dl(struct tab *, struct karg *);
650 int xtp_page_fl(struct tab *, struct karg *);
651 int xtp_page_hl(struct tab *, struct karg *);
653 #define XT_URI_ABOUT ("about:")
654 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
655 #define XT_URI_ABOUT_ABOUT ("about")
656 #define XT_URI_ABOUT_BLANK ("blank")
657 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
658 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
659 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
660 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
661 #define XT_URI_ABOUT_FAVORITES ("favorites")
662 #define XT_URI_ABOUT_HELP ("help")
663 #define XT_URI_ABOUT_HISTORY ("history")
664 #define XT_URI_ABOUT_JSWL ("jswl")
665 #define XT_URI_ABOUT_SET ("set")
666 #define XT_URI_ABOUT_STATS ("stats")
668 struct about_type {
669 char *name;
670 int (*func)(struct tab *, struct karg *);
671 } about_list[] = {
672 { XT_URI_ABOUT_ABOUT, about },
673 { XT_URI_ABOUT_BLANK, blank },
674 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
675 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
676 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
677 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
678 { XT_URI_ABOUT_HELP, help },
679 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
680 { XT_URI_ABOUT_JSWL, js_show_wl },
681 { XT_URI_ABOUT_SET, set },
682 { XT_URI_ABOUT_STATS, stats },
685 /* globals */
686 extern char *__progname;
687 char **start_argv;
688 struct passwd *pwd;
689 GtkWidget *main_window;
690 GtkNotebook *notebook;
691 GtkWidget *arrow, *abtn;
692 struct tab_list tabs;
693 struct history_list hl;
694 struct download_list downloads;
695 struct domain_list c_wl;
696 struct domain_list js_wl;
697 struct undo_tailq undos;
698 struct keybinding_list kbl;
699 int undo_count;
700 int updating_dl_tabs = 0;
701 int updating_hl_tabs = 0;
702 int updating_cl_tabs = 0;
703 int updating_fl_tabs = 0;
704 char *global_search;
705 uint64_t blocked_cookies = 0;
706 char named_session[PATH_MAX];
707 void update_favicon(struct tab *);
708 int icon_size_map(int);
710 GtkListStore *completion_model;
711 void completion_add(struct tab *);
712 void completion_add_uri(const gchar *);
714 void
715 sigchild(int sig)
717 int saved_errno, status;
718 pid_t pid;
720 saved_errno = errno;
722 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
723 if (pid == -1) {
724 if (errno == EINTR)
725 continue;
726 if (errno != ECHILD) {
728 clog_warn("sigchild: waitpid:");
731 break;
734 if (WIFEXITED(status)) {
735 if (WEXITSTATUS(status) != 0) {
737 clog_warnx("sigchild: child exit status: %d",
738 WEXITSTATUS(status));
741 } else {
743 clog_warnx("sigchild: child is terminated abnormally");
748 errno = saved_errno;
751 void
752 load_webkit_string(struct tab *t, const char *str, gchar *title)
754 gchar *uri;
755 char file[PATH_MAX];
756 GdkPixbuf *pb;
758 /* we set this to indicate we want to manually do navaction */
759 if (t->bfl)
760 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
761 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
763 if (title) {
764 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
765 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
766 g_free(uri);
768 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
769 pb = gdk_pixbuf_new_from_file(file, NULL);
770 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
771 GTK_ENTRY_ICON_PRIMARY, pb);
772 gdk_pixbuf_unref(pb);
776 void
777 set_status(struct tab *t, gchar *s, int status)
779 gchar *type = NULL;
781 if (s == NULL)
782 return;
784 switch (status) {
785 case XT_STATUS_LOADING:
786 type = g_strdup_printf("Loading: %s", s);
787 s = type;
788 break;
789 case XT_STATUS_LINK:
790 type = g_strdup_printf("Link: %s", s);
791 if (!t->status)
792 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
793 s = type;
794 break;
795 case XT_STATUS_URI:
796 type = g_strdup_printf("%s", s);
797 if (!t->status) {
798 t->status = g_strdup(type);
800 s = type;
801 if (!t->status)
802 t->status = g_strdup(s);
803 break;
804 case XT_STATUS_NOTHING:
805 /* FALL THROUGH */
806 default:
807 break;
809 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
810 if (type)
811 g_free(type);
814 void
815 hide_oops(struct tab *t)
817 gtk_widget_hide(t->oops);
820 void
821 hide_cmd(struct tab *t)
823 gtk_widget_hide(t->cmd);
826 void
827 show_cmd(struct tab *t)
829 gtk_widget_hide(t->oops);
830 gtk_widget_show(t->cmd);
833 void
834 show_oops(struct tab *t, const char *fmt, ...)
836 va_list ap;
837 char *msg;
839 if (fmt == NULL)
840 return;
842 va_start(ap, fmt);
843 if (vasprintf(&msg, fmt, ap) == -1)
844 errx(1, "show_oops failed");
845 va_end(ap);
847 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
848 gtk_widget_hide(t->cmd);
849 gtk_widget_show(t->oops);
852 /* XXX collapse with show_oops */
853 void
854 show_oops_s(const char *fmt, ...)
856 va_list ap;
857 char *msg;
858 struct tab *ti, *t = NULL;
860 if (fmt == NULL)
861 return;
863 TAILQ_FOREACH(ti, &tabs, entry)
864 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
865 t = ti;
866 break;
868 if (t == NULL)
869 return;
871 va_start(ap, fmt);
872 if (vasprintf(&msg, fmt, ap) == -1)
873 errx(1, "show_oops_s failed");
874 va_end(ap);
876 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
877 gtk_widget_hide(t->cmd);
878 gtk_widget_show(t->oops);
881 char *
882 get_as_string(struct settings *s)
884 char *r = NULL;
886 if (s == NULL)
887 return (NULL);
889 if (s->s) {
890 if (s->s->get)
891 r = s->s->get(s);
892 else
893 warnx("get_as_string skip %s\n", s->name);
894 } else if (s->type == XT_S_INT)
895 r = g_strdup_printf("%d", *s->ival);
896 else if (s->type == XT_S_STR)
897 r = g_strdup(*s->sval);
898 else if (s->type == XT_S_FLOAT)
899 r = g_strdup_printf("%f", *s->fval);
900 else
901 r = g_strdup_printf("INVALID TYPE");
903 return (r);
906 void
907 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
909 int i;
910 char *s;
912 for (i = 0; i < LENGTH(rs); i++) {
913 if (rs[i].s && rs[i].s->walk)
914 rs[i].s->walk(&rs[i], cb, cb_args);
915 else {
916 s = get_as_string(&rs[i]);
917 cb(&rs[i], s, cb_args);
918 g_free(s);
924 set_browser_mode(struct settings *s, char *val)
926 if (!strcmp(val, "whitelist")) {
927 browser_mode = XT_COOKIE_WHITELIST;
928 allow_volatile_cookies = 0;
929 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
930 cookies_enabled = 1;
931 enable_cookie_whitelist = 1;
932 read_only_cookies = 0;
933 save_rejected_cookies = 0;
934 session_timeout = 3600;
935 enable_scripts = 0;
936 enable_js_whitelist = 1;
937 } else if (!strcmp(val, "normal")) {
938 browser_mode = XT_COOKIE_NORMAL;
939 allow_volatile_cookies = 0;
940 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
941 cookies_enabled = 1;
942 enable_cookie_whitelist = 0;
943 read_only_cookies = 0;
944 save_rejected_cookies = 0;
945 session_timeout = 3600;
946 enable_scripts = 1;
947 enable_js_whitelist = 0;
948 } else
949 return (1);
951 return (0);
954 char *
955 get_browser_mode(struct settings *s)
957 char *r = NULL;
959 if (browser_mode == XT_COOKIE_WHITELIST)
960 r = g_strdup("whitelist");
961 else if (browser_mode == XT_COOKIE_NORMAL)
962 r = g_strdup("normal");
963 else
964 return (NULL);
966 return (r);
970 set_cookie_policy(struct settings *s, char *val)
972 if (!strcmp(val, "no3rdparty"))
973 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
974 else if (!strcmp(val, "accept"))
975 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
976 else if (!strcmp(val, "reject"))
977 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
978 else
979 return (1);
981 return (0);
984 char *
985 get_cookie_policy(struct settings *s)
987 char *r = NULL;
989 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
990 r = g_strdup("no3rdparty");
991 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
992 r = g_strdup("accept");
993 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
994 r = g_strdup("reject");
995 else
996 return (NULL);
998 return (r);
1001 char *
1002 get_download_dir(struct settings *s)
1004 if (download_dir[0] == '\0')
1005 return (0);
1006 return (g_strdup(download_dir));
1010 set_download_dir(struct settings *s, char *val)
1012 if (val[0] == '~')
1013 snprintf(download_dir, sizeof download_dir, "%s/%s",
1014 pwd->pw_dir, &val[1]);
1015 else
1016 strlcpy(download_dir, val, sizeof download_dir);
1018 return (0);
1022 * Session IDs.
1023 * We use these to prevent people putting xxxt:// URLs on
1024 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1026 #define XT_XTP_SES_KEY_SZ 8
1027 #define XT_XTP_SES_KEY_HEX_FMT \
1028 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1029 char *dl_session_key; /* downloads */
1030 char *hl_session_key; /* history list */
1031 char *cl_session_key; /* cookie list */
1032 char *fl_session_key; /* favorites list */
1034 char work_dir[PATH_MAX];
1035 char certs_dir[PATH_MAX];
1036 char cache_dir[PATH_MAX];
1037 char sessions_dir[PATH_MAX];
1038 char cookie_file[PATH_MAX];
1039 SoupURI *proxy_uri = NULL;
1040 SoupSession *session;
1041 SoupCookieJar *s_cookiejar;
1042 SoupCookieJar *p_cookiejar;
1043 char rc_fname[PATH_MAX];
1045 struct mime_type_list mtl;
1046 struct alias_list aliases;
1048 /* protos */
1049 struct tab *create_new_tab(char *, struct undo *, int);
1050 void delete_tab(struct tab *);
1051 void adjustfont_webkit(struct tab *, int);
1052 int run_script(struct tab *, char *);
1053 int download_rb_cmp(struct download *, struct download *);
1056 history_rb_cmp(struct history *h1, struct history *h2)
1058 return (strcmp(h1->uri, h2->uri));
1060 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1063 domain_rb_cmp(struct domain *d1, struct domain *d2)
1065 return (strcmp(d1->d, d2->d));
1067 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1069 char *
1070 get_work_dir(struct settings *s)
1072 if (work_dir[0] == '\0')
1073 return (0);
1074 return (g_strdup(work_dir));
1078 set_work_dir(struct settings *s, char *val)
1080 if (val[0] == '~')
1081 snprintf(work_dir, sizeof work_dir, "%s/%s",
1082 pwd->pw_dir, &val[1]);
1083 else
1084 strlcpy(work_dir, val, sizeof work_dir);
1086 return (0);
1090 * generate a session key to secure xtp commands.
1091 * pass in a ptr to the key in question and it will
1092 * be modified in place.
1094 void
1095 generate_xtp_session_key(char **key)
1097 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1099 /* free old key */
1100 if (*key)
1101 g_free(*key);
1103 /* make a new one */
1104 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1105 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1106 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1107 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1109 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1113 * validate a xtp session key.
1114 * return 1 if OK
1117 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1119 if (strcmp(trusted, untrusted) != 0) {
1120 show_oops(t, "%s: xtp session key mismatch possible spoof",
1121 __func__);
1122 return (0);
1125 return (1);
1129 download_rb_cmp(struct download *e1, struct download *e2)
1131 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1133 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1135 struct valid_url_types {
1136 char *type;
1137 } vut[] = {
1138 { "http://" },
1139 { "https://" },
1140 { "ftp://" },
1141 { "file://" },
1142 { XT_XTP_STR },
1146 valid_url_type(char *url)
1148 int i;
1150 for (i = 0; i < LENGTH(vut); i++)
1151 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1152 return (0);
1154 return (1);
1157 void
1158 print_cookie(char *msg, SoupCookie *c)
1160 if (c == NULL)
1161 return;
1163 if (msg)
1164 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1165 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1166 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1167 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1168 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1169 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1170 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1171 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1172 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1173 DNPRINTF(XT_D_COOKIE, "====================================\n");
1176 void
1177 walk_alias(struct settings *s,
1178 void (*cb)(struct settings *, char *, void *), void *cb_args)
1180 struct alias *a;
1181 char *str;
1183 if (s == NULL || cb == NULL) {
1184 show_oops_s("walk_alias invalid parameters");
1185 return;
1188 TAILQ_FOREACH(a, &aliases, entry) {
1189 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1190 cb(s, str, cb_args);
1191 g_free(str);
1195 char *
1196 match_alias(char *url_in)
1198 struct alias *a;
1199 char *arg;
1200 char *url_out = NULL, *search, *enc_arg;
1202 search = g_strdup(url_in);
1203 arg = search;
1204 if (strsep(&arg, " \t") == NULL) {
1205 show_oops_s("match_alias: NULL URL");
1206 goto done;
1209 TAILQ_FOREACH(a, &aliases, entry) {
1210 if (!strcmp(search, a->a_name))
1211 break;
1214 if (a != NULL) {
1215 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1216 a->a_name);
1217 if (arg != NULL) {
1218 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1219 url_out = g_strdup_printf(a->a_uri, enc_arg);
1220 g_free(enc_arg);
1221 } else
1222 url_out = g_strdup(a->a_uri);
1224 done:
1225 g_free(search);
1226 return (url_out);
1229 char *
1230 guess_url_type(char *url_in)
1232 struct stat sb;
1233 char *url_out = NULL, *enc_search = NULL;
1235 url_out = match_alias(url_in);
1236 if (url_out != NULL)
1237 return (url_out);
1239 if (guess_search) {
1241 * If there is no dot nor slash in the string and it isn't a
1242 * path to a local file and doesn't resolves to an IP, assume
1243 * that the user wants to search for the string.
1246 if (strchr(url_in, '.') == NULL &&
1247 strchr(url_in, '/') == NULL &&
1248 stat(url_in, &sb) != 0 &&
1249 gethostbyname(url_in) == NULL) {
1251 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1252 url_out = g_strdup_printf(search_string, enc_search);
1253 g_free(enc_search);
1254 return (url_out);
1258 /* XXX not sure about this heuristic */
1259 if (stat(url_in, &sb) == 0)
1260 url_out = g_strdup_printf("file://%s", url_in);
1261 else
1262 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1264 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1266 return (url_out);
1269 void
1270 load_uri(struct tab *t, gchar *uri)
1272 struct karg args;
1273 gchar *newuri = NULL;
1274 int i;
1276 if (uri == NULL)
1277 return;
1279 /* Strip leading spaces. */
1280 while(*uri && isspace(*uri))
1281 uri++;
1283 if (strlen(uri) == 0) {
1284 blank(t, NULL);
1285 return;
1288 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1289 for (i = 0; i < LENGTH(about_list); i++)
1290 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1291 bzero(&args, sizeof args);
1292 about_list[i].func(t, &args);
1293 return;
1295 show_oops(t, "invalid about page");
1296 return;
1299 if (valid_url_type(uri)) {
1300 newuri = guess_url_type(uri);
1301 uri = newuri;
1304 set_status(t, (char *)uri, XT_STATUS_LOADING);
1305 webkit_web_view_load_uri(t->wv, uri);
1307 if (newuri)
1308 g_free(newuri);
1311 const gchar *
1312 get_uri(WebKitWebView *wv)
1314 WebKitWebFrame *frame;
1315 const gchar *uri;
1317 frame = webkit_web_view_get_main_frame(wv);
1318 uri = webkit_web_frame_get_uri(frame);
1320 if (uri && strlen(uri) > 0)
1321 return (uri);
1322 else
1323 return (NULL);
1327 add_alias(struct settings *s, char *line)
1329 char *l, *alias;
1330 struct alias *a = NULL;
1332 if (s == NULL || line == NULL) {
1333 show_oops_s("add_alias invalid parameters");
1334 return (1);
1337 l = line;
1338 a = g_malloc(sizeof(*a));
1340 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1341 show_oops_s("add_alias: incomplete alias definition");
1342 goto bad;
1344 if (strlen(alias) == 0 || strlen(l) == 0) {
1345 show_oops_s("add_alias: invalid alias definition");
1346 goto bad;
1349 a->a_name = g_strdup(alias);
1350 a->a_uri = g_strdup(l);
1352 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1354 TAILQ_INSERT_TAIL(&aliases, a, entry);
1356 return (0);
1357 bad:
1358 if (a)
1359 g_free(a);
1360 return (1);
1364 add_mime_type(struct settings *s, char *line)
1366 char *mime_type;
1367 char *l;
1368 struct mime_type *m = NULL;
1370 /* XXX this could be smarter */
1372 if (line == NULL) {
1373 show_oops_s("add_mime_type invalid parameters");
1374 return (1);
1377 l = line;
1378 m = g_malloc(sizeof(*m));
1380 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1381 show_oops_s("add_mime_type: invalid mime_type");
1382 goto bad;
1384 if (mime_type[strlen(mime_type) - 1] == '*') {
1385 mime_type[strlen(mime_type) - 1] = '\0';
1386 m->mt_default = 1;
1387 } else
1388 m->mt_default = 0;
1390 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1391 show_oops_s("add_mime_type: invalid mime_type");
1392 goto bad;
1395 m->mt_type = g_strdup(mime_type);
1396 m->mt_action = g_strdup(l);
1398 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1399 m->mt_type, m->mt_action, m->mt_default);
1401 TAILQ_INSERT_TAIL(&mtl, m, entry);
1403 return (0);
1404 bad:
1405 if (m)
1406 g_free(m);
1407 return (1);
1410 struct mime_type *
1411 find_mime_type(char *mime_type)
1413 struct mime_type *m, *def = NULL, *rv = NULL;
1415 TAILQ_FOREACH(m, &mtl, entry) {
1416 if (m->mt_default &&
1417 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1418 def = m;
1420 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1421 rv = m;
1422 break;
1426 if (rv == NULL)
1427 rv = def;
1429 return (rv);
1432 void
1433 walk_mime_type(struct settings *s,
1434 void (*cb)(struct settings *, char *, void *), void *cb_args)
1436 struct mime_type *m;
1437 char *str;
1439 if (s == NULL || cb == NULL)
1440 show_oops_s("walk_mime_type invalid parameters");
1442 TAILQ_FOREACH(m, &mtl, entry) {
1443 str = g_strdup_printf("%s%s --> %s",
1444 m->mt_type,
1445 m->mt_default ? "*" : "",
1446 m->mt_action);
1447 cb(s, str, cb_args);
1448 g_free(str);
1452 void
1453 wl_add(char *str, struct domain_list *wl, int handy)
1455 struct domain *d;
1456 int add_dot = 0;
1458 if (str == NULL || wl == NULL)
1459 return;
1460 if (strlen(str) < 2)
1461 return;
1463 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1465 /* treat *.moo.com the same as .moo.com */
1466 if (str[0] == '*' && str[1] == '.')
1467 str = &str[1];
1468 else if (str[0] == '.')
1469 str = &str[0];
1470 else
1471 add_dot = 1;
1473 d = g_malloc(sizeof *d);
1474 if (add_dot)
1475 d->d = g_strdup_printf(".%s", str);
1476 else
1477 d->d = g_strdup(str);
1478 d->handy = handy;
1480 if (RB_INSERT(domain_list, wl, d))
1481 goto unwind;
1483 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1484 return;
1485 unwind:
1486 if (d) {
1487 if (d->d)
1488 g_free(d->d);
1489 g_free(d);
1494 add_cookie_wl(struct settings *s, char *entry)
1496 wl_add(entry, &c_wl, 1);
1497 return (0);
1500 void
1501 walk_cookie_wl(struct settings *s,
1502 void (*cb)(struct settings *, char *, void *), void *cb_args)
1504 struct domain *d;
1506 if (s == NULL || cb == NULL) {
1507 show_oops_s("walk_cookie_wl invalid parameters");
1508 return;
1511 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1512 cb(s, d->d, cb_args);
1515 void
1516 walk_js_wl(struct settings *s,
1517 void (*cb)(struct settings *, char *, void *), void *cb_args)
1519 struct domain *d;
1521 if (s == NULL || cb == NULL) {
1522 show_oops_s("walk_js_wl invalid parameters");
1523 return;
1526 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1527 cb(s, d->d, cb_args);
1531 add_js_wl(struct settings *s, char *entry)
1533 wl_add(entry, &js_wl, 1 /* persistent */);
1534 return (0);
1537 struct domain *
1538 wl_find(const gchar *search, struct domain_list *wl)
1540 int i;
1541 struct domain *d = NULL, dfind;
1542 gchar *s = NULL;
1544 if (search == NULL || wl == NULL)
1545 return (NULL);
1546 if (strlen(search) < 2)
1547 return (NULL);
1549 if (search[0] != '.')
1550 s = g_strdup_printf(".%s", search);
1551 else
1552 s = g_strdup(search);
1554 for (i = strlen(s) - 1; i >= 0; i--) {
1555 if (s[i] == '.') {
1556 dfind.d = &s[i];
1557 d = RB_FIND(domain_list, wl, &dfind);
1558 if (d)
1559 goto done;
1563 done:
1564 if (s)
1565 g_free(s);
1567 return (d);
1570 struct domain *
1571 wl_find_uri(const gchar *s, struct domain_list *wl)
1573 int i;
1574 char *ss;
1575 struct domain *r;
1577 if (s == NULL || wl == NULL)
1578 return (NULL);
1580 if (!strncmp(s, "http://", strlen("http://")))
1581 s = &s[strlen("http://")];
1582 else if (!strncmp(s, "https://", strlen("https://")))
1583 s = &s[strlen("https://")];
1585 if (strlen(s) < 2)
1586 return (NULL);
1588 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1589 /* chop string at first slash */
1590 if (s[i] == '/' || s[i] == '\0') {
1591 ss = g_strdup(s);
1592 ss[i] = '\0';
1593 r = wl_find(ss, wl);
1594 g_free(ss);
1595 return (r);
1598 return (NULL);
1601 char *
1602 get_toplevel_domain(char *domain)
1604 char *s;
1605 int found = 0;
1607 if (domain == NULL)
1608 return (NULL);
1609 if (strlen(domain) < 2)
1610 return (NULL);
1612 s = &domain[strlen(domain) - 1];
1613 while (s != domain) {
1614 if (*s == '.') {
1615 found++;
1616 if (found == 2)
1617 return (s);
1619 s--;
1622 if (found)
1623 return (domain);
1625 return (NULL);
1629 settings_add(char *var, char *val)
1631 int i, rv, *p;
1632 gfloat *f;
1633 char **s;
1635 /* get settings */
1636 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1637 if (strcmp(var, rs[i].name))
1638 continue;
1640 if (rs[i].s) {
1641 if (rs[i].s->set(&rs[i], val))
1642 errx(1, "invalid value for %s", var);
1643 rv = 1;
1644 break;
1645 } else
1646 switch (rs[i].type) {
1647 case XT_S_INT:
1648 p = rs[i].ival;
1649 *p = atoi(val);
1650 rv = 1;
1651 break;
1652 case XT_S_STR:
1653 s = rs[i].sval;
1654 if (s == NULL)
1655 errx(1, "invalid sval for %s",
1656 rs[i].name);
1657 if (*s)
1658 g_free(*s);
1659 *s = g_strdup(val);
1660 rv = 1;
1661 break;
1662 case XT_S_FLOAT:
1663 f = rs[i].fval;
1664 *f = atof(val);
1665 rv = 1;
1666 break;
1667 case XT_S_INVALID:
1668 default:
1669 errx(1, "invalid type for %s", var);
1671 break;
1673 return (rv);
1676 #define WS "\n= \t"
1677 void
1678 config_parse(char *filename, int runtime)
1680 FILE *config, *f;
1681 char *line, *cp, *var, *val;
1682 size_t len, lineno = 0;
1683 int handled;
1684 char file[PATH_MAX];
1685 struct stat sb;
1687 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1689 if (filename == NULL)
1690 return;
1692 if (runtime && runtime_settings[0] != '\0') {
1693 snprintf(file, sizeof file, "%s/%s",
1694 work_dir, runtime_settings);
1695 if (stat(file, &sb)) {
1696 warnx("runtime file doesn't exist, creating it");
1697 if ((f = fopen(file, "w")) == NULL)
1698 err(1, "runtime");
1699 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1700 fclose(f);
1702 } else
1703 strlcpy(file, filename, sizeof file);
1705 if ((config = fopen(file, "r")) == NULL) {
1706 warn("config_parse: cannot open %s", filename);
1707 return;
1710 for (;;) {
1711 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1712 if (feof(config) || ferror(config))
1713 break;
1715 cp = line;
1716 cp += (long)strspn(cp, WS);
1717 if (cp[0] == '\0') {
1718 /* empty line */
1719 free(line);
1720 continue;
1723 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1724 errx(1, "invalid config file entry: %s", line);
1726 cp += (long)strspn(cp, WS);
1728 if ((val = strsep(&cp, "\0")) == NULL)
1729 break;
1731 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1732 handled = settings_add(var, val);
1733 if (handled == 0)
1734 errx(1, "invalid conf file entry: %s=%s", var, val);
1736 free(line);
1739 fclose(config);
1742 char *
1743 js_ref_to_string(JSContextRef context, JSValueRef ref)
1745 char *s = NULL;
1746 size_t l;
1747 JSStringRef jsref;
1749 jsref = JSValueToStringCopy(context, ref, NULL);
1750 if (jsref == NULL)
1751 return (NULL);
1753 l = JSStringGetMaximumUTF8CStringSize(jsref);
1754 s = g_malloc(l);
1755 if (s)
1756 JSStringGetUTF8CString(jsref, s, l);
1757 JSStringRelease(jsref);
1759 return (s);
1762 void
1763 disable_hints(struct tab *t)
1765 bzero(t->hint_buf, sizeof t->hint_buf);
1766 bzero(t->hint_num, sizeof t->hint_num);
1767 run_script(t, "vimprobable_clear()");
1768 t->hints_on = 0;
1769 t->hint_mode = XT_HINT_NONE;
1772 void
1773 enable_hints(struct tab *t)
1775 bzero(t->hint_buf, sizeof t->hint_buf);
1776 run_script(t, "vimprobable_show_hints()");
1777 t->hints_on = 1;
1778 t->hint_mode = XT_HINT_NONE;
1781 #define XT_JS_OPEN ("open;")
1782 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1783 #define XT_JS_FIRE ("fire;")
1784 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1785 #define XT_JS_FOUND ("found;")
1786 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1789 run_script(struct tab *t, char *s)
1791 JSGlobalContextRef ctx;
1792 WebKitWebFrame *frame;
1793 JSStringRef str;
1794 JSValueRef val, exception;
1795 char *es, buf[128];
1797 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1798 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1800 frame = webkit_web_view_get_main_frame(t->wv);
1801 ctx = webkit_web_frame_get_global_context(frame);
1803 str = JSStringCreateWithUTF8CString(s);
1804 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1805 NULL, 0, &exception);
1806 JSStringRelease(str);
1808 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1809 if (val == NULL) {
1810 es = js_ref_to_string(ctx, exception);
1811 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1812 g_free(es);
1813 return (1);
1814 } else {
1815 es = js_ref_to_string(ctx, val);
1816 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1818 /* handle return value right here */
1819 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1820 disable_hints(t);
1821 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1824 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1825 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1826 &es[XT_JS_FIRE_LEN]);
1827 run_script(t, buf);
1828 disable_hints(t);
1831 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1832 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1833 disable_hints(t);
1836 g_free(es);
1839 return (0);
1843 hint(struct tab *t, struct karg *args)
1846 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1848 if (t->hints_on == 0)
1849 enable_hints(t);
1850 else
1851 disable_hints(t);
1853 return (0);
1856 void
1857 apply_style(struct tab *t)
1859 g_object_set(G_OBJECT(t->settings),
1860 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1864 userstyle(struct tab *t, struct karg *args)
1866 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1868 if (t->styled) {
1869 t->styled = 0;
1870 g_object_set(G_OBJECT(t->settings),
1871 "user-stylesheet-uri", NULL, (char *)NULL);
1872 } else {
1873 t->styled = 1;
1874 apply_style(t);
1876 return (0);
1880 * Doesn't work fully, due to the following bug:
1881 * https://bugs.webkit.org/show_bug.cgi?id=51747
1884 restore_global_history(void)
1886 char file[PATH_MAX];
1887 FILE *f;
1888 struct history *h;
1889 gchar *uri;
1890 gchar *title;
1892 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1894 if ((f = fopen(file, "r")) == NULL) {
1895 warnx("%s: fopen", __func__);
1896 return (1);
1899 for (;;) {
1900 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1901 if (feof(f) || ferror(f))
1902 break;
1904 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1905 if (feof(f) || ferror(f)) {
1906 free(uri);
1907 warnx("%s: broken history file\n", __func__);
1908 return (1);
1911 if (uri && strlen(uri) && title && strlen(title)) {
1912 webkit_web_history_item_new_with_data(uri, title);
1913 h = g_malloc(sizeof(struct history));
1914 h->uri = g_strdup(uri);
1915 h->title = g_strdup(title);
1916 RB_INSERT(history_list, &hl, h);
1917 completion_add_uri(h->uri);
1918 } else {
1919 warnx("%s: failed to restore history\n", __func__);
1920 free(uri);
1921 free(title);
1922 return (1);
1925 free(uri);
1926 free(title);
1927 uri = NULL;
1928 title = NULL;
1931 return (0);
1935 save_global_history_to_disk(struct tab *t)
1937 char file[PATH_MAX];
1938 FILE *f;
1939 struct history *h;
1941 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1943 if ((f = fopen(file, "w")) == NULL) {
1944 show_oops(t, "%s: global history file: %s",
1945 __func__, strerror(errno));
1946 return (1);
1949 RB_FOREACH_REVERSE(h, history_list, &hl) {
1950 if (h->uri && h->title)
1951 fprintf(f, "%s\n%s\n", h->uri, h->title);
1954 fclose(f);
1956 return (0);
1960 quit(struct tab *t, struct karg *args)
1962 if (save_global_history)
1963 save_global_history_to_disk(t);
1965 gtk_main_quit();
1967 return (1);
1971 open_tabs(struct tab *t, struct karg *a)
1973 char file[PATH_MAX];
1974 FILE *f = NULL;
1975 char *uri = NULL;
1976 int rv = 1;
1977 struct tab *ti, *tt;
1979 if (a == NULL)
1980 goto done;
1982 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1983 if ((f = fopen(file, "r")) == NULL)
1984 goto done;
1986 ti = TAILQ_LAST(&tabs, tab_list);
1988 for (;;) {
1989 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1990 if (feof(f) || ferror(f))
1991 break;
1993 /* retrieve session name */
1994 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1995 strlcpy(named_session,
1996 &uri[strlen(XT_SAVE_SESSION_ID)],
1997 sizeof named_session);
1998 continue;
2001 if (uri && strlen(uri))
2002 create_new_tab(uri, NULL, 1);
2004 free(uri);
2005 uri = NULL;
2008 /* close open tabs */
2009 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2010 for (;;) {
2011 tt = TAILQ_FIRST(&tabs);
2012 if (tt != ti) {
2013 delete_tab(tt);
2014 continue;
2016 delete_tab(tt);
2017 break;
2021 rv = 0;
2022 done:
2023 if (f)
2024 fclose(f);
2026 return (rv);
2030 restore_saved_tabs(void)
2032 char file[PATH_MAX];
2033 int unlink_file = 0;
2034 struct stat sb;
2035 struct karg a;
2036 int rv = 0;
2038 snprintf(file, sizeof file, "%s/%s",
2039 sessions_dir, XT_RESTART_TABS_FILE);
2040 if (stat(file, &sb) == -1)
2041 a.s = XT_SAVED_TABS_FILE;
2042 else {
2043 unlink_file = 1;
2044 a.s = XT_RESTART_TABS_FILE;
2047 a.i = XT_SES_DONOTHING;
2048 rv = open_tabs(NULL, &a);
2050 if (unlink_file)
2051 unlink(file);
2053 return (rv);
2057 save_tabs(struct tab *t, struct karg *a)
2059 char file[PATH_MAX];
2060 FILE *f;
2061 struct tab *ti;
2062 const gchar *uri;
2063 int len = 0, i;
2064 const gchar **arr = NULL;
2066 if (a == NULL)
2067 return (1);
2068 if (a->s == NULL)
2069 snprintf(file, sizeof file, "%s/%s",
2070 sessions_dir, named_session);
2071 else
2072 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2074 if ((f = fopen(file, "w")) == NULL) {
2075 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2076 return (1);
2079 /* save session name */
2080 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2082 /* save tabs, in the order they are arranged in the notebook */
2083 TAILQ_FOREACH(ti, &tabs, entry)
2084 len++;
2086 arr = g_malloc0(len * sizeof(gchar *));
2088 TAILQ_FOREACH(ti, &tabs, entry) {
2089 if ((uri = get_uri(ti->wv)) != NULL)
2090 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2093 for (i = 0; i < len; i++)
2094 if (arr[i])
2095 fprintf(f, "%s\n", arr[i]);
2097 g_free(arr);
2098 fclose(f);
2100 return (0);
2104 save_tabs_and_quit(struct tab *t, struct karg *args)
2106 struct karg a;
2108 a.s = NULL;
2109 save_tabs(t, &a);
2110 quit(t, NULL);
2112 return (1);
2116 yank_uri(struct tab *t, struct karg *args)
2118 const gchar *uri;
2119 GtkClipboard *clipboard;
2121 if ((uri = get_uri(t->wv)) == NULL)
2122 return (1);
2124 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2125 gtk_clipboard_set_text(clipboard, uri, -1);
2127 return (0);
2130 struct paste_args {
2131 struct tab *t;
2132 int i;
2135 void
2136 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2138 struct paste_args *pap;
2140 if (data == NULL || text == NULL || !strlen(text))
2141 return;
2143 pap = (struct paste_args *)data;
2145 switch(pap->i) {
2146 case XT_PASTE_CURRENT_TAB:
2147 load_uri(pap->t, (gchar *)text);
2148 break;
2149 case XT_PASTE_NEW_TAB:
2150 create_new_tab((gchar *)text, NULL, 1);
2151 break;
2154 g_free(pap);
2158 paste_uri(struct tab *t, struct karg *args)
2160 GtkClipboard *clipboard;
2161 struct paste_args *pap;
2163 pap = g_malloc(sizeof(struct paste_args));
2165 pap->t = t;
2166 pap->i = args->i;
2168 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2169 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2171 return (0);
2174 char *
2175 find_domain(const gchar *s, int add_dot)
2177 int i;
2178 char *r = NULL, *ss = NULL;
2180 if (s == NULL)
2181 return (NULL);
2183 if (!strncmp(s, "http://", strlen("http://")))
2184 s = &s[strlen("http://")];
2185 else if (!strncmp(s, "https://", strlen("https://")))
2186 s = &s[strlen("https://")];
2188 if (strlen(s) < 2)
2189 return (NULL);
2191 ss = g_strdup(s);
2192 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2193 /* chop string at first slash */
2194 if (ss[i] == '/' || ss[i] == '\0') {
2195 ss[i] = '\0';
2196 if (add_dot)
2197 r = g_strdup_printf(".%s", ss);
2198 else
2199 r = g_strdup(ss);
2200 break;
2202 g_free(ss);
2204 return (r);
2208 toggle_cwl(struct tab *t, struct karg *args)
2210 struct domain *d;
2211 const gchar *uri;
2212 char *dom = NULL, *dom_toggle = NULL;
2213 int es;
2215 if (args == NULL)
2216 return (1);
2218 uri = get_uri(t->wv);
2219 dom = find_domain(uri, 1);
2220 d = wl_find(dom, &c_wl);
2222 if (d == NULL)
2223 es = 0;
2224 else
2225 es = 1;
2227 if (args->i & XT_WL_TOGGLE)
2228 es = !es;
2229 else if ((args->i & XT_WL_ENABLE) && es != 1)
2230 es = 1;
2231 else if ((args->i & XT_WL_DISABLE) && es != 0)
2232 es = 0;
2234 if (args->i & XT_WL_TOPLEVEL)
2235 dom_toggle = get_toplevel_domain(dom);
2236 else
2237 dom_toggle = dom;
2239 if (es)
2240 /* enable cookies for domain */
2241 wl_add(dom_toggle, &c_wl, 0);
2242 else
2243 /* disable cookies for domain */
2244 RB_REMOVE(domain_list, &c_wl, d);
2246 webkit_web_view_reload(t->wv);
2248 g_free(dom);
2249 return (0);
2253 toggle_js(struct tab *t, struct karg *args)
2255 int es;
2256 const gchar *uri;
2257 struct domain *d;
2258 char *dom = NULL, *dom_toggle = NULL;
2260 if (args == NULL)
2261 return (1);
2263 g_object_get(G_OBJECT(t->settings),
2264 "enable-scripts", &es, (char *)NULL);
2265 if (args->i & XT_WL_TOGGLE)
2266 es = !es;
2267 else if ((args->i & XT_WL_ENABLE) && es != 1)
2268 es = 1;
2269 else if ((args->i & XT_WL_DISABLE) && es != 0)
2270 es = 0;
2271 else
2272 return (1);
2274 uri = get_uri(t->wv);
2275 dom = find_domain(uri, 1);
2277 if (uri == NULL || dom == NULL) {
2278 show_oops(t, "Can't toggle domain in JavaScript white list");
2279 goto done;
2282 if (args->i & XT_WL_TOPLEVEL)
2283 dom_toggle = get_toplevel_domain(dom);
2284 else
2285 dom_toggle = dom;
2287 if (es) {
2288 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2289 wl_add(dom_toggle, &js_wl, 0 /* session */);
2290 } else {
2291 d = wl_find(dom_toggle, &js_wl);
2292 if (d)
2293 RB_REMOVE(domain_list, &js_wl, d);
2294 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2296 g_object_set(G_OBJECT(t->settings),
2297 "enable-scripts", es, (char *)NULL);
2298 g_object_set(G_OBJECT(t->settings),
2299 "javascript-can-open-windows-automatically", es, (char *)NULL);
2300 webkit_web_view_set_settings(t->wv, t->settings);
2301 webkit_web_view_reload(t->wv);
2302 done:
2303 if (dom)
2304 g_free(dom);
2305 return (0);
2308 void
2309 js_toggle_cb(GtkWidget *w, struct tab *t)
2311 struct karg a;
2313 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2314 toggle_js(t, &a);
2318 toggle_src(struct tab *t, struct karg *args)
2320 gboolean mode;
2322 if (t == NULL)
2323 return (0);
2325 mode = webkit_web_view_get_view_source_mode(t->wv);
2326 webkit_web_view_set_view_source_mode(t->wv, !mode);
2327 webkit_web_view_reload(t->wv);
2329 return (0);
2332 void
2333 focus_webview(struct tab *t)
2335 if (t == NULL)
2336 return;
2338 /* only grab focus if we are visible */
2339 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2340 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2344 focus(struct tab *t, struct karg *args)
2346 if (t == NULL || args == NULL)
2347 return (1);
2349 if (show_url == 0)
2350 return (0);
2352 if (args->i == XT_FOCUS_URI)
2353 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2354 else if (args->i == XT_FOCUS_SEARCH)
2355 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2357 return (0);
2361 stats(struct tab *t, struct karg *args)
2363 char *stats, *s, line[64 * 1024];
2364 uint64_t line_count = 0;
2365 FILE *r_cookie_f;
2367 if (t == NULL)
2368 show_oops_s("stats invalid parameters");
2370 line[0] = '\0';
2371 if (save_rejected_cookies) {
2372 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2373 for (;;) {
2374 s = fgets(line, sizeof line, r_cookie_f);
2375 if (s == NULL || feof(r_cookie_f) ||
2376 ferror(r_cookie_f))
2377 break;
2378 line_count++;
2380 fclose(r_cookie_f);
2381 snprintf(line, sizeof line,
2382 "<br>Cookies blocked(*) total: %llu", line_count);
2383 } else
2384 show_oops(t, "Can't open blocked cookies file: %s",
2385 strerror(errno));
2388 stats = g_strdup_printf(XT_DOCTYPE
2389 "<html>"
2390 "<head>"
2391 "<title>Statistics</title>"
2392 "</head>"
2393 "<h1>Statistics</h1>"
2394 "<body>"
2395 "Cookies blocked(*) this session: %llu"
2396 "%s"
2397 "<p><small><b>*</b> results vary based on settings"
2398 "</body>"
2399 "</html>",
2400 blocked_cookies,
2401 line);
2403 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2404 g_free(stats);
2406 return (0);
2410 blank(struct tab *t, struct karg *args)
2412 if (t == NULL)
2413 show_oops_s("about invalid parameters");
2415 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2417 return (0);
2420 about(struct tab *t, struct karg *args)
2422 char *about;
2424 if (t == NULL)
2425 show_oops_s("about invalid parameters");
2427 about = g_strdup_printf(XT_DOCTYPE
2428 "<html>"
2429 "<head>"
2430 "<title>About</title>"
2431 "</head>"
2432 "<h1>About</h1>"
2433 "<body>"
2434 "<b>Version: %s</b><p>"
2435 "Authors:"
2436 "<ul>"
2437 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2438 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2439 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2440 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2441 "</ul>"
2442 "Copyrights and licenses can be found on the XXXterm "
2443 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2444 "</body>"
2445 "</html>",
2446 version
2449 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2450 g_free(about);
2452 return (0);
2456 help(struct tab *t, struct karg *args)
2458 char *help;
2460 if (t == NULL)
2461 show_oops_s("help invalid parameters");
2463 help = XT_DOCTYPE
2464 "<html>"
2465 "<head>"
2466 "<title>XXXterm</title>"
2467 "<meta http-equiv=\"REFRESH\" content=\"0;"
2468 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2469 "</head>"
2470 "<body>"
2471 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2472 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2473 "cgi-bin/man-cgi?xxxterm</a>"
2474 "</body>"
2475 "</html>"
2478 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2480 return (0);
2484 * update all favorite tabs apart from one. Pass NULL if
2485 * you want to update all.
2487 void
2488 update_favorite_tabs(struct tab *apart_from)
2490 struct tab *t;
2491 if (!updating_fl_tabs) {
2492 updating_fl_tabs = 1; /* stop infinite recursion */
2493 TAILQ_FOREACH(t, &tabs, entry)
2494 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2495 && (t != apart_from))
2496 xtp_page_fl(t, NULL);
2497 updating_fl_tabs = 0;
2501 /* show a list of favorites (bookmarks) */
2503 xtp_page_fl(struct tab *t, struct karg *args)
2505 char file[PATH_MAX];
2506 FILE *f;
2507 char *uri = NULL, *title = NULL;
2508 size_t len, lineno = 0;
2509 int i, failed = 0;
2510 char *header, *body, *tmp, *html = NULL;
2512 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2514 if (t == NULL)
2515 warn("%s: bad param", __func__);
2517 /* mark tab as favorite list */
2518 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2520 /* new session key */
2521 if (!updating_fl_tabs)
2522 generate_xtp_session_key(&fl_session_key);
2524 /* open favorites */
2525 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2526 if ((f = fopen(file, "r")) == NULL) {
2527 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2528 return (1);
2531 /* header */
2532 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2533 "<title>Favorites</title>\n"
2534 "%s"
2535 "</head>"
2536 "<h1>Favorites</h1>\n",
2537 XT_PAGE_STYLE);
2539 /* body */
2540 body = g_strdup_printf("<div align='center'><table><tr>"
2541 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2542 "<th style='width: 15%%'>Remove</th></tr>\n");
2544 for (i = 1;;) {
2545 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2546 if (feof(f) || ferror(f))
2547 break;
2548 if (len == 0) {
2549 free(title);
2550 title = NULL;
2551 continue;
2554 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2555 if (feof(f) || ferror(f)) {
2556 show_oops(t, "favorites file corrupt");
2557 failed = 1;
2558 break;
2561 tmp = body;
2562 body = g_strdup_printf("%s<tr>"
2563 "<td>%d</td>"
2564 "<td><a href='%s'>%s</a></td>"
2565 "<td style='text-align: center'>"
2566 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2567 "</tr>\n",
2568 body, i, uri, title,
2569 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2571 g_free(tmp);
2573 free(uri);
2574 uri = NULL;
2575 free(title);
2576 title = NULL;
2577 i++;
2579 fclose(f);
2581 /* if none, say so */
2582 if (i == 1) {
2583 tmp = body;
2584 body = g_strdup_printf("%s<tr>"
2585 "<td colspan='3' style='text-align: center'>"
2586 "No favorites - To add one use the 'favadd' command."
2587 "</td></tr>", body);
2588 g_free(tmp);
2591 if (uri)
2592 free(uri);
2593 if (title)
2594 free(title);
2596 /* render */
2597 if (!failed) {
2598 html = g_strdup_printf("%s%s</table></div></html>",
2599 header, body);
2600 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2603 update_favorite_tabs(t);
2605 if (header)
2606 g_free(header);
2607 if (body)
2608 g_free(body);
2609 if (html)
2610 g_free(html);
2612 return (failed);
2615 char *
2616 getparams(char *cmd, char *cmp)
2618 char *rv = NULL;
2620 if (cmd && cmp) {
2621 if (!strncmp(cmd, cmp, strlen(cmp))) {
2622 rv = cmd + strlen(cmp);
2623 while (*rv == ' ')
2624 rv++;
2625 if (strlen(rv) == 0)
2626 rv = NULL;
2630 return (rv);
2633 void
2634 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2635 size_t cert_count, char *title)
2637 gnutls_datum_t cinfo;
2638 char *tmp, *header, *body, *footer;
2639 int i;
2641 header = g_strdup_printf("<title>%s</title><html><body>", title);
2642 footer = g_strdup("</body></html>");
2643 body = g_strdup("");
2645 for (i = 0; i < cert_count; i++) {
2646 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2647 &cinfo))
2648 return;
2650 tmp = body;
2651 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2652 body, i, cinfo.data);
2653 gnutls_free(cinfo.data);
2654 g_free(tmp);
2657 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2658 g_free(header);
2659 g_free(body);
2660 g_free(footer);
2661 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2662 g_free(tmp);
2666 ca_cmd(struct tab *t, struct karg *args)
2668 FILE *f = NULL;
2669 int rv = 1, certs = 0, certs_read;
2670 struct stat sb;
2671 gnutls_datum dt;
2672 gnutls_x509_crt_t *c = NULL;
2673 char *certs_buf = NULL, *s;
2675 /* yeah yeah stat race */
2676 if (stat(ssl_ca_file, &sb)) {
2677 show_oops(t, "no CA file: %s", ssl_ca_file);
2678 goto done;
2681 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2682 show_oops(t, "Can't open CA file: %s", strerror(errno));
2683 return (1);
2686 certs_buf = g_malloc(sb.st_size + 1);
2687 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2688 show_oops(t, "Can't read CA file: %s", strerror(errno));
2689 goto done;
2691 certs_buf[sb.st_size] = '\0';
2693 s = certs_buf;
2694 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2695 certs++;
2696 s += strlen("BEGIN CERTIFICATE");
2699 bzero(&dt, sizeof dt);
2700 dt.data = certs_buf;
2701 dt.size = sb.st_size;
2702 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2703 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2704 GNUTLS_X509_FMT_PEM, 0);
2705 if (certs_read <= 0) {
2706 show_oops(t, "No cert(s) available");
2707 goto done;
2709 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2710 done:
2711 if (c)
2712 g_free(c);
2713 if (certs_buf)
2714 g_free(certs_buf);
2715 if (f)
2716 fclose(f);
2718 return (rv);
2722 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2724 SoupURI *su = NULL;
2725 struct addrinfo hints, *res = NULL, *ai;
2726 int s = -1, on;
2727 char port[8];
2729 if (uri && !g_str_has_prefix(uri, "https://"))
2730 goto done;
2732 su = soup_uri_new(uri);
2733 if (su == NULL)
2734 goto done;
2735 if (!SOUP_URI_VALID_FOR_HTTP(su))
2736 goto done;
2738 snprintf(port, sizeof port, "%d", su->port);
2739 bzero(&hints, sizeof(struct addrinfo));
2740 hints.ai_flags = AI_CANONNAME;
2741 hints.ai_family = AF_UNSPEC;
2742 hints.ai_socktype = SOCK_STREAM;
2744 if (getaddrinfo(su->host, port, &hints, &res))
2745 goto done;
2747 for (ai = res; ai; ai = ai->ai_next) {
2748 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2749 continue;
2751 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2752 if (s < 0)
2753 goto done;
2754 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2755 sizeof(on)) == -1)
2756 goto done;
2758 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2759 goto done;
2762 if (domain)
2763 strlcpy(domain, su->host, domain_sz);
2764 done:
2765 if (su)
2766 soup_uri_free(su);
2767 if (res)
2768 freeaddrinfo(res);
2770 return (s);
2774 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2776 if (gsession)
2777 gnutls_deinit(gsession);
2778 if (xcred)
2779 gnutls_certificate_free_credentials(xcred);
2781 return (0);
2785 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2786 gnutls_certificate_credentials_t *xc)
2788 gnutls_certificate_credentials_t xcred;
2789 gnutls_session_t gsession;
2790 int rv = 1;
2792 if (gs == NULL || xc == NULL)
2793 goto done;
2795 *gs = NULL;
2796 *xc = NULL;
2798 gnutls_certificate_allocate_credentials(&xcred);
2799 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2800 GNUTLS_X509_FMT_PEM);
2801 gnutls_init(&gsession, GNUTLS_CLIENT);
2802 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2803 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2804 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2805 if ((rv = gnutls_handshake(gsession)) < 0) {
2806 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2808 gnutls_error_is_fatal(rv),
2809 gnutls_strerror_name(rv));
2810 stop_tls(gsession, xcred);
2811 goto done;
2814 gnutls_credentials_type_t cred;
2815 cred = gnutls_auth_get_type(gsession);
2816 if (cred != GNUTLS_CRD_CERTIFICATE) {
2817 stop_tls(gsession, xcred);
2818 goto done;
2821 *gs = gsession;
2822 *xc = xcred;
2823 rv = 0;
2824 done:
2825 return (rv);
2829 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2830 size_t *cert_count)
2832 unsigned int len;
2833 const gnutls_datum_t *cl;
2834 gnutls_x509_crt_t *all_certs;
2835 int i, rv = 1;
2837 if (certs == NULL || cert_count == NULL)
2838 goto done;
2839 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2840 goto done;
2841 cl = gnutls_certificate_get_peers(gsession, &len);
2842 if (len == 0)
2843 goto done;
2845 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2846 for (i = 0; i < len; i++) {
2847 gnutls_x509_crt_init(&all_certs[i]);
2848 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2849 GNUTLS_X509_FMT_PEM < 0)) {
2850 g_free(all_certs);
2851 goto done;
2855 *certs = all_certs;
2856 *cert_count = len;
2857 rv = 0;
2858 done:
2859 return (rv);
2862 void
2863 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2865 int i;
2867 for (i = 0; i < cert_count; i++)
2868 gnutls_x509_crt_deinit(certs[i]);
2869 g_free(certs);
2872 void
2873 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2874 size_t cert_count, char *domain)
2876 size_t cert_buf_sz;
2877 char cert_buf[64 * 1024], file[PATH_MAX];
2878 int i;
2879 FILE *f;
2880 GdkColor color;
2882 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2883 return;
2885 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2886 if ((f = fopen(file, "w")) == NULL) {
2887 show_oops(t, "Can't create cert file %s %s",
2888 file, strerror(errno));
2889 return;
2892 for (i = 0; i < cert_count; i++) {
2893 cert_buf_sz = sizeof cert_buf;
2894 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2895 cert_buf, &cert_buf_sz)) {
2896 show_oops(t, "gnutls_x509_crt_export failed");
2897 goto done;
2899 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2900 show_oops(t, "Can't write certs: %s", strerror(errno));
2901 goto done;
2905 /* not the best spot but oh well */
2906 gdk_color_parse("lightblue", &color);
2907 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2908 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2909 gdk_color_parse("black", &color);
2910 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2911 done:
2912 fclose(f);
2916 load_compare_cert(struct tab *t, struct karg *args)
2918 const gchar *uri;
2919 char domain[8182], file[PATH_MAX];
2920 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2921 int s = -1, rv = 1, i;
2922 size_t cert_count;
2923 FILE *f = NULL;
2924 size_t cert_buf_sz;
2925 gnutls_session_t gsession;
2926 gnutls_x509_crt_t *certs;
2927 gnutls_certificate_credentials_t xcred;
2929 if (t == NULL)
2930 return (1);
2932 if ((uri = get_uri(t->wv)) == NULL)
2933 return (1);
2935 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2936 return (1);
2938 /* go ssl/tls */
2939 if (start_tls(t, s, &gsession, &xcred)) {
2940 show_oops(t, "Start TLS failed");
2941 goto done;
2944 /* get certs */
2945 if (get_connection_certs(gsession, &certs, &cert_count)) {
2946 show_oops(t, "Can't get connection certificates");
2947 goto done;
2950 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2951 if ((f = fopen(file, "r")) == NULL)
2952 goto freeit;
2954 for (i = 0; i < cert_count; i++) {
2955 cert_buf_sz = sizeof cert_buf;
2956 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2957 cert_buf, &cert_buf_sz)) {
2958 goto freeit;
2960 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2961 rv = -1; /* critical */
2962 goto freeit;
2964 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2965 rv = -1; /* critical */
2966 goto freeit;
2970 rv = 0;
2971 freeit:
2972 if (f)
2973 fclose(f);
2974 free_connection_certs(certs, cert_count);
2975 done:
2976 /* we close the socket first for speed */
2977 if (s != -1)
2978 close(s);
2979 stop_tls(gsession, xcred);
2981 return (rv);
2985 cert_cmd(struct tab *t, struct karg *args)
2987 const gchar *uri;
2988 char *action, domain[8182];
2989 int s = -1;
2990 size_t cert_count;
2991 gnutls_session_t gsession;
2992 gnutls_x509_crt_t *certs;
2993 gnutls_certificate_credentials_t xcred;
2995 if (t == NULL)
2996 return (1);
2998 if ((action = getparams(args->s, "cert")))
3000 else
3001 action = "show";
3003 if ((uri = get_uri(t->wv)) == NULL) {
3004 show_oops(t, "Invalid URI");
3005 return (1);
3008 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3009 show_oops(t, "Invalid certidicate URI: %s", uri);
3010 return (1);
3013 /* go ssl/tls */
3014 if (start_tls(t, s, &gsession, &xcred)) {
3015 show_oops(t, "Start TLS failed");
3016 goto done;
3019 /* get certs */
3020 if (get_connection_certs(gsession, &certs, &cert_count)) {
3021 show_oops(t, "get_connection_certs failed");
3022 goto done;
3025 if (!strcmp(action, "show"))
3026 show_certs(t, certs, cert_count, "Certificate Chain");
3027 else if (!strcmp(action, "save"))
3028 save_certs(t, certs, cert_count, domain);
3029 else
3030 show_oops(t, "Invalid command: %s", action);
3032 free_connection_certs(certs, cert_count);
3033 done:
3034 /* we close the socket first for speed */
3035 if (s != -1)
3036 close(s);
3037 stop_tls(gsession, xcred);
3039 return (0);
3043 remove_cookie(int index)
3045 int i, rv = 1;
3046 GSList *cf;
3047 SoupCookie *c;
3049 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3051 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3053 for (i = 1; cf; cf = cf->next, i++) {
3054 if (i != index)
3055 continue;
3056 c = cf->data;
3057 print_cookie("remove cookie", c);
3058 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3059 rv = 0;
3060 break;
3063 soup_cookies_free(cf);
3065 return (rv);
3069 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
3071 struct domain *d;
3072 char *tmp, *header, *body, *footer;
3073 int p_js = 0, s_js = 0;
3075 /* we set this to indicate we want to manually do navaction */
3076 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3078 if (g_str_has_prefix(args, "show a") ||
3079 !strcmp(args, "show")) {
3080 /* show all */
3081 p_js = 1;
3082 s_js = 1;
3083 } else if (g_str_has_prefix(args, "show p")) {
3084 /* show persistent */
3085 p_js = 1;
3086 } else if (g_str_has_prefix(args, "show s")) {
3087 /* show session */
3088 s_js = 1;
3089 } else
3090 return (1);
3092 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3093 title, title);
3094 footer = g_strdup("</body></html>");
3095 body = g_strdup("");
3097 /* p list */
3098 if (p_js) {
3099 tmp = body;
3100 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3101 g_free(tmp);
3102 RB_FOREACH(d, domain_list, wl) {
3103 if (d->handy == 0)
3104 continue;
3105 tmp = body;
3106 body = g_strdup_printf("%s%s<br>", body, d->d);
3107 g_free(tmp);
3111 /* s list */
3112 if (s_js) {
3113 tmp = body;
3114 body = g_strdup_printf("%s<h2>Session</h2>", body);
3115 g_free(tmp);
3116 RB_FOREACH(d, domain_list, wl) {
3117 if (d->handy == 1)
3118 continue;
3119 tmp = body;
3120 body = g_strdup_printf("%s%s<br>", body, d->d);
3121 g_free(tmp);
3125 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3126 g_free(header);
3127 g_free(body);
3128 g_free(footer);
3129 if (wl == &js_wl)
3130 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3131 else
3132 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3133 g_free(tmp);
3134 return (0);
3138 wl_save(struct tab *t, struct karg *args, int js)
3140 char file[PATH_MAX];
3141 FILE *f;
3142 char *line = NULL, *lt = NULL;
3143 size_t linelen;
3144 const gchar *uri;
3145 char *dom = NULL, *dom_save = NULL;
3146 struct karg a;
3147 struct domain *d;
3148 GSList *cf;
3149 SoupCookie *ci, *c;
3150 int flags;
3152 if (t == NULL || args == NULL)
3153 return (1);
3155 if (runtime_settings[0] == '\0')
3156 return (1);
3158 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3159 if ((f = fopen(file, "r+")) == NULL)
3160 return (1);
3162 uri = get_uri(t->wv);
3163 dom = find_domain(uri, 1);
3164 if (uri == NULL || dom == NULL) {
3165 show_oops(t, "Can't add domain to %s white list",
3166 js ? "JavaScript" : "cookie");
3167 goto done;
3170 if (g_str_has_prefix(args->s, "save d")) {
3171 /* save domain */
3172 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3173 show_oops(t, "invalid domain: %s", dom);
3174 goto done;
3176 flags = XT_WL_TOPLEVEL;
3177 } else if (g_str_has_prefix(args->s, "save f") ||
3178 !strcmp(args->s, "save")) {
3179 /* save fqdn */
3180 dom_save = dom;
3181 flags = XT_WL_FQDN;
3182 } else {
3183 show_oops(t, "invalid command: %s", args->s);
3184 goto done;
3187 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3189 while (!feof(f)) {
3190 line = fparseln(f, &linelen, NULL, NULL, 0);
3191 if (line == NULL)
3192 continue;
3193 if (!strcmp(line, lt))
3194 goto done;
3195 free(line);
3196 line = NULL;
3199 fprintf(f, "%s\n", lt);
3201 a.i = XT_WL_ENABLE;
3202 a.i |= flags;
3203 if (js) {
3204 d = wl_find(dom_save, &js_wl);
3205 if (!d) {
3206 settings_add("js_wl", dom_save);
3207 d = wl_find(dom_save, &js_wl);
3209 toggle_js(t, &a);
3210 } else {
3211 d = wl_find(dom_save, &c_wl);
3212 if (!d) {
3213 settings_add("cookie_wl", dom_save);
3214 d = wl_find(dom_save, &c_wl);
3216 toggle_cwl(t, &a);
3218 /* find and add to persistent jar */
3219 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3220 for (;cf; cf = cf->next) {
3221 ci = cf->data;
3222 if (!strcmp(dom_save, ci->domain) ||
3223 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3224 c = soup_cookie_copy(ci);
3225 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3228 soup_cookies_free(cf);
3230 if (d)
3231 d->handy = 1;
3233 done:
3234 if (line)
3235 free(line);
3236 if (dom)
3237 g_free(dom);
3238 if (lt)
3239 g_free(lt);
3240 fclose(f);
3242 return (0);
3246 js_show_wl(struct tab *t, struct karg *args)
3248 wl_show(t, "show all", "JavaScript White List", &js_wl);
3250 return (0);
3254 cookie_show_wl(struct tab *t, struct karg *args)
3256 wl_show(t, "show all", "Cookie White List", &c_wl);
3258 return (0);
3262 cookie_cmd(struct tab *t, struct karg *args)
3264 char *cmd;
3265 struct karg a;
3267 if ((cmd = getparams(args->s, "cookie")))
3269 else
3270 cmd = "show all";
3273 if (g_str_has_prefix(cmd, "show")) {
3274 wl_show(t, cmd, "Cookie White List", &c_wl);
3275 } else if (g_str_has_prefix(cmd, "save")) {
3276 a.s = cmd;
3277 wl_save(t, &a, 0);
3278 } else if (g_str_has_prefix(cmd, "toggle")) {
3279 a.i = XT_WL_TOGGLE;
3280 if (g_str_has_prefix(cmd, "toggle d"))
3281 a.i |= XT_WL_TOPLEVEL;
3282 else
3283 a.i |= XT_WL_FQDN;
3284 toggle_cwl(t, &a);
3285 } else if (g_str_has_prefix(cmd, "delete")) {
3286 show_oops(t, "'cookie delete' currently unimplemented");
3287 } else
3288 show_oops(t, "unknown cookie command: %s", cmd);
3290 return (0);
3294 js_cmd(struct tab *t, struct karg *args)
3296 char *cmd;
3297 struct karg a;
3299 if ((cmd = getparams(args->s, "js")))
3301 else
3302 cmd = "show all";
3304 if (g_str_has_prefix(cmd, "show")) {
3305 wl_show(t, cmd, "JavaScript White List", &js_wl);
3306 } else if (g_str_has_prefix(cmd, "save")) {
3307 a.s = cmd;
3308 wl_save(t, &a, 1);
3309 } else if (g_str_has_prefix(cmd, "toggle")) {
3310 a.i = XT_WL_TOGGLE;
3311 if (g_str_has_prefix(cmd, "toggle d"))
3312 a.i |= XT_WL_TOPLEVEL;
3313 else
3314 a.i |= XT_WL_FQDN;
3315 toggle_js(t, &a);
3316 } else if (g_str_has_prefix(cmd, "delete")) {
3317 show_oops(t, "'js delete' currently unimplemented");
3318 } else
3319 show_oops(t, "unknown js command: %s", cmd);
3321 return (0);
3325 add_favorite(struct tab *t, struct karg *args)
3327 char file[PATH_MAX];
3328 FILE *f;
3329 char *line = NULL;
3330 size_t urilen, linelen;
3331 const gchar *uri, *title;
3333 if (t == NULL)
3334 return (1);
3336 /* don't allow adding of xtp pages to favorites */
3337 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3338 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3339 return (1);
3342 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3343 if ((f = fopen(file, "r+")) == NULL) {
3344 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3345 return (1);
3348 title = webkit_web_view_get_title(t->wv);
3349 uri = get_uri(t->wv);
3351 if (title == NULL)
3352 title = uri;
3354 if (title == NULL || uri == NULL) {
3355 show_oops(t, "can't add page to favorites");
3356 goto done;
3359 urilen = strlen(uri);
3361 for (;;) {
3362 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3363 if (feof(f) || ferror(f))
3364 break;
3366 if (linelen == urilen && !strcmp(line, uri))
3367 goto done;
3369 free(line);
3370 line = NULL;
3373 fprintf(f, "\n%s\n%s", title, uri);
3374 done:
3375 if (line)
3376 free(line);
3377 fclose(f);
3379 update_favorite_tabs(NULL);
3381 return (0);
3385 navaction(struct tab *t, struct karg *args)
3387 WebKitWebHistoryItem *item;
3389 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3390 t->tab_id, args->i);
3392 if (t->item) {
3393 if (args->i == XT_NAV_BACK)
3394 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3395 else
3396 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3397 if (item == NULL)
3398 return (XT_CB_PASSTHROUGH);
3399 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3400 t->item = NULL;
3401 return (XT_CB_PASSTHROUGH);
3404 switch (args->i) {
3405 case XT_NAV_BACK:
3406 webkit_web_view_go_back(t->wv);
3407 break;
3408 case XT_NAV_FORWARD:
3409 webkit_web_view_go_forward(t->wv);
3410 break;
3411 case XT_NAV_RELOAD:
3412 webkit_web_view_reload(t->wv);
3413 break;
3414 case XT_NAV_RELOAD_CACHE:
3415 webkit_web_view_reload_bypass_cache(t->wv);
3416 break;
3418 return (XT_CB_PASSTHROUGH);
3422 move(struct tab *t, struct karg *args)
3424 GtkAdjustment *adjust;
3425 double pi, si, pos, ps, upper, lower, max;
3427 switch (args->i) {
3428 case XT_MOVE_DOWN:
3429 case XT_MOVE_UP:
3430 case XT_MOVE_BOTTOM:
3431 case XT_MOVE_TOP:
3432 case XT_MOVE_PAGEDOWN:
3433 case XT_MOVE_PAGEUP:
3434 case XT_MOVE_HALFDOWN:
3435 case XT_MOVE_HALFUP:
3436 adjust = t->adjust_v;
3437 break;
3438 default:
3439 adjust = t->adjust_h;
3440 break;
3443 pos = gtk_adjustment_get_value(adjust);
3444 ps = gtk_adjustment_get_page_size(adjust);
3445 upper = gtk_adjustment_get_upper(adjust);
3446 lower = gtk_adjustment_get_lower(adjust);
3447 si = gtk_adjustment_get_step_increment(adjust);
3448 pi = gtk_adjustment_get_page_increment(adjust);
3449 max = upper - ps;
3451 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3452 "max %f si %f pi %f\n",
3453 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3454 pos, ps, upper, lower, max, si, pi);
3456 switch (args->i) {
3457 case XT_MOVE_DOWN:
3458 case XT_MOVE_RIGHT:
3459 pos += si;
3460 gtk_adjustment_set_value(adjust, MIN(pos, max));
3461 break;
3462 case XT_MOVE_UP:
3463 case XT_MOVE_LEFT:
3464 pos -= si;
3465 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3466 break;
3467 case XT_MOVE_BOTTOM:
3468 case XT_MOVE_FARRIGHT:
3469 gtk_adjustment_set_value(adjust, max);
3470 break;
3471 case XT_MOVE_TOP:
3472 case XT_MOVE_FARLEFT:
3473 gtk_adjustment_set_value(adjust, lower);
3474 break;
3475 case XT_MOVE_PAGEDOWN:
3476 pos += pi;
3477 gtk_adjustment_set_value(adjust, MIN(pos, max));
3478 break;
3479 case XT_MOVE_PAGEUP:
3480 pos -= pi;
3481 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3482 break;
3483 case XT_MOVE_HALFDOWN:
3484 pos += pi / 2;
3485 gtk_adjustment_set_value(adjust, MIN(pos, max));
3486 break;
3487 case XT_MOVE_HALFUP:
3488 pos -= pi / 2;
3489 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3490 break;
3491 default:
3492 return (XT_CB_PASSTHROUGH);
3495 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3497 return (XT_CB_HANDLED);
3500 void
3501 url_set_visibility(void)
3503 struct tab *t;
3505 TAILQ_FOREACH(t, &tabs, entry) {
3506 if (show_url == 0) {
3507 gtk_widget_hide(t->toolbar);
3508 focus_webview(t);
3509 } else
3510 gtk_widget_show(t->toolbar);
3514 void
3515 notebook_tab_set_visibility(GtkNotebook *notebook)
3517 if (show_tabs == 0)
3518 gtk_notebook_set_show_tabs(notebook, FALSE);
3519 else
3520 gtk_notebook_set_show_tabs(notebook, TRUE);
3523 void
3524 statusbar_set_visibility(void)
3526 struct tab *t;
3528 TAILQ_FOREACH(t, &tabs, entry) {
3529 if (show_statusbar == 0) {
3530 gtk_widget_hide(t->statusbar);
3531 focus_webview(t);
3532 } else
3533 gtk_widget_show(t->statusbar);
3537 void
3538 url_set(struct tab *t, int enable_url_entry)
3540 GdkPixbuf *pixbuf;
3541 int progress;
3543 show_url = enable_url_entry;
3545 if (enable_url_entry) {
3546 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3547 GTK_ENTRY_ICON_PRIMARY, NULL);
3548 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3549 } else {
3550 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3551 GTK_ENTRY_ICON_PRIMARY);
3552 progress =
3553 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3554 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3555 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3556 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3557 progress);
3562 fullscreen(struct tab *t, struct karg *args)
3564 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3566 if (t == NULL)
3567 return (XT_CB_PASSTHROUGH);
3569 if (show_url == 0) {
3570 url_set(t, 1);
3571 show_tabs = 1;
3572 } else {
3573 url_set(t, 0);
3574 show_tabs = 0;
3577 url_set_visibility();
3578 notebook_tab_set_visibility(notebook);
3580 return (XT_CB_HANDLED);
3584 statusaction(struct tab *t, struct karg *args)
3586 int rv = XT_CB_HANDLED;
3588 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3590 if (t == NULL)
3591 return (XT_CB_PASSTHROUGH);
3593 switch (args->i) {
3594 case XT_STATUSBAR_SHOW:
3595 if (show_statusbar == 0) {
3596 show_statusbar = 1;
3597 statusbar_set_visibility();
3599 break;
3600 case XT_STATUSBAR_HIDE:
3601 if (show_statusbar == 1) {
3602 show_statusbar = 0;
3603 statusbar_set_visibility();
3605 break;
3607 return (rv);
3611 urlaction(struct tab *t, struct karg *args)
3613 int rv = XT_CB_HANDLED;
3615 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3617 if (t == NULL)
3618 return (XT_CB_PASSTHROUGH);
3620 switch (args->i) {
3621 case XT_URL_SHOW:
3622 if (show_url == 0) {
3623 url_set(t, 1);
3624 url_set_visibility();
3626 break;
3627 case XT_URL_HIDE:
3628 if (show_url == 1) {
3629 url_set(t, 0);
3630 url_set_visibility();
3632 break;
3634 return (rv);
3638 tabaction(struct tab *t, struct karg *args)
3640 int rv = XT_CB_HANDLED;
3641 char *url = NULL;
3642 struct undo *u;
3644 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3646 if (t == NULL)
3647 return (XT_CB_PASSTHROUGH);
3649 switch (args->i) {
3650 case XT_TAB_NEW:
3651 if ((url = getparams(args->s, "tabnew")))
3652 create_new_tab(url, NULL, 1);
3653 else
3654 create_new_tab(NULL, NULL, 1);
3655 break;
3656 case XT_TAB_DELETE:
3657 delete_tab(t);
3658 break;
3659 case XT_TAB_DELQUIT:
3660 if (gtk_notebook_get_n_pages(notebook) > 1)
3661 delete_tab(t);
3662 else
3663 quit(t, args);
3664 break;
3665 case XT_TAB_OPEN:
3666 if ((url = getparams(args->s, "open")) ||
3667 ((url = getparams(args->s, "op"))) ||
3668 ((url = getparams(args->s, "o"))))
3670 else {
3671 rv = XT_CB_PASSTHROUGH;
3672 goto done;
3674 load_uri(t, url);
3675 break;
3676 case XT_TAB_SHOW:
3677 if (show_tabs == 0) {
3678 show_tabs = 1;
3679 notebook_tab_set_visibility(notebook);
3681 break;
3682 case XT_TAB_HIDE:
3683 if (show_tabs == 1) {
3684 show_tabs = 0;
3685 notebook_tab_set_visibility(notebook);
3687 break;
3688 case XT_TAB_UNDO_CLOSE:
3689 if (undo_count == 0) {
3690 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3691 goto done;
3692 } else {
3693 undo_count--;
3694 u = TAILQ_FIRST(&undos);
3695 create_new_tab(u->uri, u, 1);
3697 TAILQ_REMOVE(&undos, u, entry);
3698 g_free(u->uri);
3699 /* u->history is freed in create_new_tab() */
3700 g_free(u);
3702 break;
3703 default:
3704 rv = XT_CB_PASSTHROUGH;
3705 goto done;
3708 done:
3709 if (args->s) {
3710 g_free(args->s);
3711 args->s = NULL;
3714 return (rv);
3718 resizetab(struct tab *t, struct karg *args)
3720 if (t == NULL || args == NULL) {
3721 show_oops_s("resizetab invalid parameters");
3722 return (XT_CB_PASSTHROUGH);
3725 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3726 t->tab_id, args->i);
3728 adjustfont_webkit(t, args->i);
3730 return (XT_CB_HANDLED);
3734 movetab(struct tab *t, struct karg *args)
3736 struct tab *tt;
3737 int x;
3739 if (t == NULL || args == NULL) {
3740 show_oops_s("movetab invalid parameters");
3741 return (XT_CB_PASSTHROUGH);
3744 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3745 t->tab_id, args->i);
3747 if (args->i == XT_TAB_INVALID)
3748 return (XT_CB_PASSTHROUGH);
3750 if (args->i < XT_TAB_INVALID) {
3751 /* next or previous tab */
3752 if (TAILQ_EMPTY(&tabs))
3753 return (XT_CB_PASSTHROUGH);
3755 switch (args->i) {
3756 case XT_TAB_NEXT:
3757 /* if at the last page, loop around to the first */
3758 if (gtk_notebook_get_current_page(notebook) ==
3759 gtk_notebook_get_n_pages(notebook) - 1)
3760 gtk_notebook_set_current_page(notebook, 0);
3761 else
3762 gtk_notebook_next_page(notebook);
3763 break;
3764 case XT_TAB_PREV:
3765 /* if at the first page, loop around to the last */
3766 if (gtk_notebook_current_page(notebook) == 0)
3767 gtk_notebook_set_current_page(notebook,
3768 gtk_notebook_get_n_pages(notebook) - 1);
3769 else
3770 gtk_notebook_prev_page(notebook);
3771 break;
3772 case XT_TAB_FIRST:
3773 gtk_notebook_set_current_page(notebook, 0);
3774 break;
3775 case XT_TAB_LAST:
3776 gtk_notebook_set_current_page(notebook, -1);
3777 break;
3778 default:
3779 return (XT_CB_PASSTHROUGH);
3782 return (XT_CB_HANDLED);
3785 /* jump to tab */
3786 x = args->i - 1;
3787 if (t->tab_id == x) {
3788 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3789 return (XT_CB_HANDLED);
3792 TAILQ_FOREACH(tt, &tabs, entry) {
3793 if (tt->tab_id == x) {
3794 gtk_notebook_set_current_page(notebook, x);
3795 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3796 if (tt->focus_wv)
3797 focus_webview(tt);
3801 return (XT_CB_HANDLED);
3805 command(struct tab *t, struct karg *args)
3807 char *s = NULL, *ss = NULL;
3808 GdkColor color;
3809 const gchar *uri;
3811 if (t == NULL || args == NULL) {
3812 show_oops_s("command invalid parameters");
3813 return (XT_CB_PASSTHROUGH);
3816 switch (args->i) {
3817 case '/':
3818 s = "/";
3819 break;
3820 case '?':
3821 s = "?";
3822 break;
3823 case ':':
3824 s = ":";
3825 break;
3826 case XT_CMD_OPEN:
3827 s = ":open ";
3828 break;
3829 case XT_CMD_TABNEW:
3830 s = ":tabnew ";
3831 break;
3832 case XT_CMD_OPEN_CURRENT:
3833 s = ":open ";
3834 /* FALL THROUGH */
3835 case XT_CMD_TABNEW_CURRENT:
3836 if (!s) /* FALL THROUGH? */
3837 s = ":tabnew ";
3838 if ((uri = get_uri(t->wv)) != NULL) {
3839 ss = g_strdup_printf("%s%s", s, uri);
3840 s = ss;
3842 break;
3843 default:
3844 show_oops(t, "command: invalid opcode %d", args->i);
3845 return (XT_CB_PASSTHROUGH);
3848 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3850 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3851 gdk_color_parse("white", &color);
3852 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3853 show_cmd(t);
3854 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3855 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3857 if (ss)
3858 g_free(ss);
3860 return (XT_CB_HANDLED);
3864 * Return a new string with a download row (in html)
3865 * appended. Old string is freed.
3867 char *
3868 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3871 WebKitDownloadStatus stat;
3872 char *status_html = NULL, *cmd_html = NULL, *new_html;
3873 gdouble progress;
3874 char cur_sz[FMT_SCALED_STRSIZE];
3875 char tot_sz[FMT_SCALED_STRSIZE];
3876 char *xtp_prefix;
3878 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3880 /* All actions wil take this form:
3881 * xxxt://class/seskey
3883 xtp_prefix = g_strdup_printf("%s%d/%s/",
3884 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3886 stat = webkit_download_get_status(dl->download);
3888 switch (stat) {
3889 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3890 status_html = g_strdup_printf("Finished");
3891 cmd_html = g_strdup_printf(
3892 "<a href='%s%d/%d'>Remove</a>",
3893 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3894 break;
3895 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3896 /* gather size info */
3897 progress = 100 * webkit_download_get_progress(dl->download);
3899 fmt_scaled(
3900 webkit_download_get_current_size(dl->download), cur_sz);
3901 fmt_scaled(
3902 webkit_download_get_total_size(dl->download), tot_sz);
3904 status_html = g_strdup_printf(
3905 "<div style='width: 100%%' align='center'>"
3906 "<div class='progress-outer'>"
3907 "<div class='progress-inner' style='width: %.2f%%'>"
3908 "</div></div></div>"
3909 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3910 progress, cur_sz, tot_sz, progress);
3912 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3913 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3915 break;
3916 /* LLL */
3917 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3918 status_html = g_strdup_printf("Cancelled");
3919 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3920 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3921 break;
3922 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3923 status_html = g_strdup_printf("Error!");
3924 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3925 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3926 break;
3927 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3928 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3929 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3930 status_html = g_strdup_printf("Starting");
3931 break;
3932 default:
3933 show_oops(t, "%s: unknown download status", __func__);
3936 new_html = g_strdup_printf(
3937 "%s\n<tr><td>%s</td><td>%s</td>"
3938 "<td style='text-align:center'>%s</td></tr>\n",
3939 html, basename(webkit_download_get_uri(dl->download)),
3940 status_html, cmd_html);
3941 g_free(html);
3943 if (status_html)
3944 g_free(status_html);
3946 if (cmd_html)
3947 g_free(cmd_html);
3949 g_free(xtp_prefix);
3951 return new_html;
3955 * update all download tabs apart from one. Pass NULL if
3956 * you want to update all.
3958 void
3959 update_download_tabs(struct tab *apart_from)
3961 struct tab *t;
3962 if (!updating_dl_tabs) {
3963 updating_dl_tabs = 1; /* stop infinite recursion */
3964 TAILQ_FOREACH(t, &tabs, entry)
3965 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3966 && (t != apart_from))
3967 xtp_page_dl(t, NULL);
3968 updating_dl_tabs = 0;
3973 * update all cookie tabs apart from one. Pass NULL if
3974 * you want to update all.
3976 void
3977 update_cookie_tabs(struct tab *apart_from)
3979 struct tab *t;
3980 if (!updating_cl_tabs) {
3981 updating_cl_tabs = 1; /* stop infinite recursion */
3982 TAILQ_FOREACH(t, &tabs, entry)
3983 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3984 && (t != apart_from))
3985 xtp_page_cl(t, NULL);
3986 updating_cl_tabs = 0;
3991 * update all history tabs apart from one. Pass NULL if
3992 * you want to update all.
3994 void
3995 update_history_tabs(struct tab *apart_from)
3997 struct tab *t;
3999 if (!updating_hl_tabs) {
4000 updating_hl_tabs = 1; /* stop infinite recursion */
4001 TAILQ_FOREACH(t, &tabs, entry)
4002 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4003 && (t != apart_from))
4004 xtp_page_hl(t, NULL);
4005 updating_hl_tabs = 0;
4009 /* cookie management XTP page */
4011 xtp_page_cl(struct tab *t, struct karg *args)
4013 char *header, *body, *footer, *page, *tmp;
4014 int i = 1; /* all ids start 1 */
4015 GSList *sc, *pc, *pc_start;
4016 SoupCookie *c;
4017 char *type, *table_headers;
4018 char *last_domain = strdup("");
4020 DNPRINTF(XT_D_CMD, "%s", __func__);
4022 if (t == NULL) {
4023 show_oops_s("%s invalid parameters", __func__);
4024 return (1);
4026 /* mark this tab as cookie jar */
4027 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4029 /* Generate a new session key */
4030 if (!updating_cl_tabs)
4031 generate_xtp_session_key(&cl_session_key);
4033 /* header */
4034 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4035 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4036 "</head><body><h1>Cookie Jar</h1>\n");
4038 /* table headers */
4039 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4040 "<th>Type</th>"
4041 "<th>Name</th>"
4042 "<th>Value</th>"
4043 "<th>Path</th>"
4044 "<th>Expires</th>"
4045 "<th>Secure</th>"
4046 "<th>HTTP<br />only</th>"
4047 "<th>Rm</th></tr>\n");
4049 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4050 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4051 pc_start = pc;
4053 body = NULL;
4054 for (; sc; sc = sc->next) {
4055 c = sc->data;
4057 if (strcmp(last_domain, c->domain) != 0) {
4058 /* new domain */
4059 free(last_domain);
4060 last_domain = strdup(c->domain);
4062 if (body != NULL) {
4063 tmp = body;
4064 body = g_strdup_printf("%s</table></div>"
4065 "<h2>%s</h2>%s\n",
4066 body, c->domain, table_headers);
4067 g_free(tmp);
4068 } else {
4069 /* first domain */
4070 body = g_strdup_printf("<h2>%s</h2>%s\n",
4071 c->domain, table_headers);
4075 type = "Session";
4076 for (pc = pc_start; pc; pc = pc->next)
4077 if (soup_cookie_equal(pc->data, c)) {
4078 type = "Session + Persistent";
4079 break;
4082 tmp = body;
4083 body = g_strdup_printf(
4084 "%s\n<tr>"
4085 "<td style='width: text-align: center'>%s</td>"
4086 "<td style='width: 1px'>%s</td>"
4087 "<td style='width=70%%;overflow: visible'>"
4088 " <textarea rows='4'>%s</textarea>"
4089 "</td>"
4090 "<td>%s</td>"
4091 "<td>%s</td>"
4092 "<td style='width: 1px; text-align: center'>%d</td>"
4093 "<td style='width: 1px; text-align: center'>%d</td>"
4094 "<td style='width: 1px; text-align: center'>"
4095 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4096 body,
4097 type,
4098 c->name,
4099 c->value,
4100 c->path,
4101 c->expires ?
4102 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4103 c->secure,
4104 c->http_only,
4106 XT_XTP_STR,
4107 XT_XTP_CL,
4108 cl_session_key,
4109 XT_XTP_CL_REMOVE,
4113 g_free(tmp);
4114 i++;
4117 soup_cookies_free(sc);
4118 soup_cookies_free(pc);
4120 /* small message if there are none */
4121 if (i == 1) {
4122 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4123 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4126 /* footer */
4127 footer = g_strdup_printf("</table></div></body></html>");
4129 page = g_strdup_printf("%s%s%s", header, body, footer);
4131 g_free(header);
4132 g_free(body);
4133 g_free(footer);
4134 g_free(table_headers);
4135 g_free(last_domain);
4137 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4138 update_cookie_tabs(t);
4140 g_free(page);
4142 return (0);
4146 xtp_page_hl(struct tab *t, struct karg *args)
4148 char *header, *body, *footer, *page, *tmp;
4149 struct history *h;
4150 int i = 1; /* all ids start 1 */
4152 DNPRINTF(XT_D_CMD, "%s", __func__);
4154 if (t == NULL) {
4155 show_oops_s("%s invalid parameters", __func__);
4156 return (1);
4159 /* mark this tab as history manager */
4160 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4162 /* Generate a new session key */
4163 if (!updating_hl_tabs)
4164 generate_xtp_session_key(&hl_session_key);
4166 /* header */
4167 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4168 "<title>History</title>\n"
4169 "%s"
4170 "</head>"
4171 "<h1>History</h1>\n",
4172 XT_PAGE_STYLE);
4174 /* body */
4175 body = g_strdup_printf("<div align='center'><table><tr>"
4176 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4178 RB_FOREACH_REVERSE(h, history_list, &hl) {
4179 tmp = body;
4180 body = g_strdup_printf(
4181 "%s\n<tr>"
4182 "<td><a href='%s'>%s</a></td>"
4183 "<td>%s</td>"
4184 "<td style='text-align: center'>"
4185 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4186 body, h->uri, h->uri, h->title,
4187 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4188 XT_XTP_HL_REMOVE, i);
4190 g_free(tmp);
4191 i++;
4194 /* small message if there are none */
4195 if (i == 1) {
4196 tmp = body;
4197 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4198 "colspan='3'>No History</td></tr>\n", body);
4199 g_free(tmp);
4202 /* footer */
4203 footer = g_strdup_printf("</table></div></body></html>");
4205 page = g_strdup_printf("%s%s%s", header, body, footer);
4208 * update all history manager tabs as the xtp session
4209 * key has now changed. No need to update the current tab.
4210 * Already did that above.
4212 update_history_tabs(t);
4214 g_free(header);
4215 g_free(body);
4216 g_free(footer);
4218 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4219 g_free(page);
4221 return (0);
4225 * Generate a web page detailing the status of any downloads
4228 xtp_page_dl(struct tab *t, struct karg *args)
4230 struct download *dl;
4231 char *header, *body, *footer, *page, *tmp;
4232 char *ref;
4233 int n_dl = 1;
4235 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4237 if (t == NULL) {
4238 show_oops_s("%s invalid parameters", __func__);
4239 return (1);
4241 /* mark as a download manager tab */
4242 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4245 * Generate a new session key for next page instance.
4246 * This only happens for the top level call to xtp_page_dl()
4247 * in which case updating_dl_tabs is 0.
4249 if (!updating_dl_tabs)
4250 generate_xtp_session_key(&dl_session_key);
4252 /* header - with refresh so as to update */
4253 if (refresh_interval >= 1)
4254 ref = g_strdup_printf(
4255 "<meta http-equiv='refresh' content='%u"
4256 ";url=%s%d/%s/%d' />\n",
4257 refresh_interval,
4258 XT_XTP_STR,
4259 XT_XTP_DL,
4260 dl_session_key,
4261 XT_XTP_DL_LIST);
4262 else
4263 ref = g_strdup("");
4266 header = g_strdup_printf(
4267 "%s\n<head>"
4268 "<title>Downloads</title>\n%s%s</head>\n",
4269 XT_DOCTYPE XT_HTML_TAG,
4270 ref,
4271 XT_PAGE_STYLE);
4273 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4274 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4275 "</p><table><tr><th style='width: 60%%'>"
4276 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4277 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4279 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4280 body = xtp_page_dl_row(t, body, dl);
4281 n_dl++;
4284 /* message if no downloads in list */
4285 if (n_dl == 1) {
4286 tmp = body;
4287 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4288 " style='text-align: center'>"
4289 "No downloads</td></tr>\n", body);
4290 g_free(tmp);
4293 /* footer */
4294 footer = g_strdup_printf("</table></div></body></html>");
4296 page = g_strdup_printf("%s%s%s", header, body, footer);
4300 * update all download manager tabs as the xtp session
4301 * key has now changed. No need to update the current tab.
4302 * Already did that above.
4304 update_download_tabs(t);
4306 g_free(ref);
4307 g_free(header);
4308 g_free(body);
4309 g_free(footer);
4311 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4312 g_free(page);
4314 return (0);
4318 search(struct tab *t, struct karg *args)
4320 gboolean d;
4322 if (t == NULL || args == NULL) {
4323 show_oops_s("search invalid parameters");
4324 return (1);
4326 if (t->search_text == NULL) {
4327 if (global_search == NULL)
4328 return (XT_CB_PASSTHROUGH);
4329 else {
4330 t->search_text = g_strdup(global_search);
4331 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4332 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4336 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4337 t->tab_id, args->i, t->search_forward, t->search_text);
4339 switch (args->i) {
4340 case XT_SEARCH_NEXT:
4341 d = t->search_forward;
4342 break;
4343 case XT_SEARCH_PREV:
4344 d = !t->search_forward;
4345 break;
4346 default:
4347 return (XT_CB_PASSTHROUGH);
4350 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4352 return (XT_CB_HANDLED);
4355 struct settings_args {
4356 char **body;
4357 int i;
4360 void
4361 print_setting(struct settings *s, char *val, void *cb_args)
4363 char *tmp, *color;
4364 struct settings_args *sa = cb_args;
4366 if (sa == NULL)
4367 return;
4369 if (s->flags & XT_SF_RUNTIME)
4370 color = "#22cc22";
4371 else
4372 color = "#cccccc";
4374 tmp = *sa->body;
4375 *sa->body = g_strdup_printf(
4376 "%s\n<tr>"
4377 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4378 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4379 *sa->body,
4380 color,
4381 s->name,
4382 color,
4385 g_free(tmp);
4386 sa->i++;
4390 set(struct tab *t, struct karg *args)
4392 char *header, *body, *footer, *page, *tmp, *pars;
4393 int i = 1;
4394 struct settings_args sa;
4396 if ((pars = getparams(args->s, "set")) == NULL) {
4397 bzero(&sa, sizeof sa);
4398 sa.body = &body;
4400 /* header */
4401 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4402 "\n<head><title>Settings</title>\n"
4403 "</head><body><h1>Settings</h1>\n");
4405 /* body */
4406 body = g_strdup_printf("<div align='center'><table><tr>"
4407 "<th align='left'>Setting</th>"
4408 "<th align='left'>Value</th></tr>\n");
4410 settings_walk(print_setting, &sa);
4411 i = sa.i;
4413 /* small message if there are none */
4414 if (i == 1) {
4415 tmp = body;
4416 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4417 "colspan='2'>No settings</td></tr>\n", body);
4418 g_free(tmp);
4421 /* footer */
4422 footer = g_strdup_printf("</table></div></body></html>");
4424 page = g_strdup_printf("%s%s%s", header, body, footer);
4426 g_free(header);
4427 g_free(body);
4428 g_free(footer);
4430 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4431 } else
4432 show_oops(t, "Invalid command: %s", pars);
4434 return (XT_CB_PASSTHROUGH);
4438 session_save(struct tab *t, char *filename, char **ret)
4440 struct karg a;
4441 char *f = filename;
4442 int rv = 1;
4444 f += strlen("save");
4445 while (*f == ' ' && *f != '\0')
4446 f++;
4447 if (strlen(f) == 0)
4448 goto done;
4450 *ret = f;
4451 if (f[0] == '.' || f[0] == '/')
4452 goto done;
4454 a.s = f;
4455 if (save_tabs(t, &a))
4456 goto done;
4457 strlcpy(named_session, f, sizeof named_session);
4459 rv = 0;
4460 done:
4461 return (rv);
4465 session_open(struct tab *t, char *filename, char **ret)
4467 struct karg a;
4468 char *f = filename;
4469 int rv = 1;
4471 f += strlen("open");
4472 while (*f == ' ' && *f != '\0')
4473 f++;
4474 if (strlen(f) == 0)
4475 goto done;
4477 *ret = f;
4478 if (f[0] == '.' || f[0] == '/')
4479 goto done;
4481 a.s = f;
4482 a.i = XT_SES_CLOSETABS;
4483 if (open_tabs(t, &a))
4484 goto done;
4486 strlcpy(named_session, f, sizeof named_session);
4488 rv = 0;
4489 done:
4490 return (rv);
4494 session_delete(struct tab *t, char *filename, char **ret)
4496 char file[PATH_MAX];
4497 char *f = filename;
4498 int rv = 1;
4500 f += strlen("delete");
4501 while (*f == ' ' && *f != '\0')
4502 f++;
4503 if (strlen(f) == 0)
4504 goto done;
4506 *ret = f;
4507 if (f[0] == '.' || f[0] == '/')
4508 goto done;
4510 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4511 if (unlink(file))
4512 goto done;
4514 if (!strcmp(f, named_session))
4515 strlcpy(named_session, XT_SAVED_TABS_FILE,
4516 sizeof named_session);
4518 rv = 0;
4519 done:
4520 return (rv);
4524 session_cmd(struct tab *t, struct karg *args)
4526 char *action = NULL;
4527 char *filename = NULL;
4529 if (t == NULL)
4530 return (1);
4532 if ((action = getparams(args->s, "session")))
4534 else
4535 action = "show";
4537 if (!strcmp(action, "show"))
4538 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4539 XT_SAVED_TABS_FILE : named_session);
4540 else if (g_str_has_prefix(action, "save ")) {
4541 if (session_save(t, action, &filename)) {
4542 show_oops(t, "Can't save session: %s",
4543 filename ? filename : "INVALID");
4544 goto done;
4546 } else if (g_str_has_prefix(action, "open ")) {
4547 if (session_open(t, action, &filename)) {
4548 show_oops(t, "Can't open session: %s",
4549 filename ? filename : "INVALID");
4550 goto done;
4552 } else if (g_str_has_prefix(action, "delete ")) {
4553 if (session_delete(t, action, &filename)) {
4554 show_oops(t, "Can't delete session: %s",
4555 filename ? filename : "INVALID");
4556 goto done;
4558 } else
4559 show_oops(t, "Invalid command: %s", action);
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 *name;
4644 guint mask;
4645 guint use_in_entry;
4646 guint key;
4647 int (*func)(struct tab *, struct karg *);
4648 struct karg arg;
4649 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4650 } keys[] = {
4651 { "cookiejar", MOD1, 0, GDK_j, xtp_page_cl, {0} },
4652 { "downloadmgr", MOD1, 0, GDK_d, xtp_page_dl, {0} },
4653 { "history", MOD1, 0, GDK_h, xtp_page_hl, {0} },
4654 { "print", CTRL, 0, GDK_p, print_page, {0}},
4655 { NULL, 0, 0, GDK_slash, command, {.i = '/'} },
4656 { NULL, 0, 0, GDK_question, command, {.i = '?'} },
4657 { NULL, 0, 0, GDK_colon, command, {.i = ':'} },
4658 { "quit", CTRL, 0, GDK_q, quit, {0} },
4659 { "restart", MOD1, 0, GDK_q, restart, {0} },
4660 { "togglejs", CTRL, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4661 { "togglecookie", MOD1, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4662 { "togglesrc", CTRL, 0, GDK_s, toggle_src, {0} },
4663 { "yankuri", 0, 0, GDK_y, yank_uri, {0} },
4664 { "pasteuricur", 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4665 { "pasteurinew", 0, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4667 /* search */
4668 { "searchnext", 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4669 { "searchprev", 0, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4671 /* focus */
4672 { "focusaddress", 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4673 { "focussearch", 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4675 /* command aliases (handy when -S flag is used) */
4676 { NULL, 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4677 { NULL, 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4678 { NULL, 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4679 { NULL, 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4681 /* hinting */
4682 { "hinting", 0, 0, GDK_f, hint, {.i = 0} },
4684 /* custom stylesheet */
4685 { "userstyle", 0, 0, GDK_i, userstyle, {.i = 0 } },
4687 /* navigation */
4688 { "goback", 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4689 { "goback", MOD1, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4690 { "goforward", SHFT, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4691 { "goforward", MOD1, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4692 { "reload", 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4693 { "reload", CTRL, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4694 { "reloadforce", CTRL, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4695 { "reload" , CTRL, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4696 { "favorites", MOD1, 1, GDK_f, xtp_page_fl, {0} },
4698 /* vertical movement */
4699 { "scrolldown", 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4700 { "scrolldown", 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4701 { "scrollup", 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4702 { "scrollup", 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4703 { "scrollbottom", 0, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4704 { "scrollbottom", 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4705 { "scrolltop", 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4706 { "scrolltop", 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} },
4707 { "scrollpagedown", 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4708 { "scrollpagedown", CTRL, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4709 { "scrollhalfdown", CTRL, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4710 { "scrollpagedown", 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4711 { "scrollpageup", 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4712 { "scrollpageup", CTRL, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4713 { "scrollhalfup", CTRL, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4714 /* horizontal movement */
4715 { "scrollright", 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4716 { "scrollright", 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4717 { "scrollleft", 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4718 { "scrollleft", 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4719 { "scrollfarright", 0, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4720 { "scrollfarleft", 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4722 /* tabs */
4723 { "tabnew", CTRL, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4724 { "tabclose", CTRL, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4725 { "tabundoclose", 0, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4726 { "tabgoto1", CTRL, 0, GDK_1, movetab, {.i = 1} },
4727 { "tabgoto2", CTRL, 0, GDK_2, movetab, {.i = 2} },
4728 { "tabgoto3", CTRL, 0, GDK_3, movetab, {.i = 3} },
4729 { "tabgoto4", CTRL, 0, GDK_4, movetab, {.i = 4} },
4730 { "tabgoto5", CTRL, 0, GDK_5, movetab, {.i = 5} },
4731 { "tabgoto6", CTRL, 0, GDK_6, movetab, {.i = 6} },
4732 { "tabgoto7", CTRL, 0, GDK_7, movetab, {.i = 7} },
4733 { "tabgoto8", CTRL, 0, GDK_8, movetab, {.i = 8} },
4734 { "tabgoto9", CTRL, 0, GDK_9, movetab, {.i = 9} },
4735 { "tabgoto10", CTRL, 0, GDK_0, movetab, {.i = 10} },
4736 { "tabgotofirst", CTRL, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4737 { "tabgotolast", CTRL, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4738 { "tabgotoprev", CTRL, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4739 { "tabgotonext", CTRL, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4740 { "focusout", CTRL, 0, GDK_minus, resizetab, {.i = -1} },
4741 { "focusin", CTRL, 0, GDK_plus, resizetab, {.i = 1} },
4742 { "focusin", CTRL, 0, GDK_equal, resizetab, {.i = 1} },
4744 TAILQ_HEAD(keybinding_list, key_binding);
4746 void
4747 walk_kb(struct settings *s,
4748 void (*cb)(struct settings *, char *, void *), void *cb_args)
4750 struct key_binding *k;
4751 char str[1024];
4753 if (s == NULL || cb == NULL) {
4754 show_oops_s("walk_kb invalid parameters");
4755 return;
4758 TAILQ_FOREACH(k, &kbl, entry) {
4759 if (k->name == NULL)
4760 continue;
4761 str[0] = '\0';
4763 /* sanity */
4764 if (gdk_keyval_name(k->key) == NULL)
4765 continue;
4767 strlcat(str, k->name, sizeof str);
4768 strlcat(str, ",", sizeof str);
4770 if (k->mask & GDK_SHIFT_MASK)
4771 strlcat(str, "S-", sizeof str);
4772 if (k->mask & GDK_CONTROL_MASK)
4773 strlcat(str, "C-", sizeof str);
4774 if (k->mask & GDK_MOD1_MASK)
4775 strlcat(str, "M1-", sizeof str);
4776 if (k->mask & GDK_MOD2_MASK)
4777 strlcat(str, "M2-", sizeof str);
4778 if (k->mask & GDK_MOD3_MASK)
4779 strlcat(str, "M3-", sizeof str);
4780 if (k->mask & GDK_MOD4_MASK)
4781 strlcat(str, "M4-", sizeof str);
4782 if (k->mask & GDK_MOD5_MASK)
4783 strlcat(str, "M5-", sizeof str);
4785 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4786 cb(s, str, cb_args);
4789 void
4790 init_keybindings(void)
4792 int i;
4793 struct key_binding *k;
4795 for (i = 0; i < LENGTH(keys); i++) {
4796 k = g_malloc0(sizeof *k);
4797 k->name = keys[i].name;
4798 k->mask = keys[i].mask;
4799 k->use_in_entry = keys[i].use_in_entry;
4800 k->key = keys[i].key;
4801 k->func = keys[i].func;
4802 bcopy(&keys[i].arg, &k->arg, sizeof k->arg);
4803 TAILQ_INSERT_HEAD(&kbl, k, entry);
4805 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4806 k->name ? k->name : "unnamed key");
4810 void
4811 keybinding_clearall(void)
4813 struct key_binding *k, *next;
4815 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4816 next = TAILQ_NEXT(k, entry);
4817 if (k->name == NULL)
4818 continue;
4820 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4821 k->name ? k->name : "unnamed key");
4822 TAILQ_REMOVE(&kbl, k, entry);
4823 g_free(k);
4828 keybinding_add(char *kb, char *value, struct key_binding *orig)
4830 struct key_binding *k;
4831 guint keyval, mask = 0;
4832 int i;
4834 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->name);
4836 if (orig == NULL)
4837 return (1);
4838 if (strcmp(kb, orig->name))
4839 return (1);
4841 /* find modifier keys */
4842 if (strstr(value, "S-"))
4843 mask |= GDK_SHIFT_MASK;
4844 if (strstr(value, "C-"))
4845 mask |= GDK_CONTROL_MASK;
4846 if (strstr(value, "M1-"))
4847 mask |= GDK_MOD1_MASK;
4848 if (strstr(value, "M2-"))
4849 mask |= GDK_MOD2_MASK;
4850 if (strstr(value, "M3-"))
4851 mask |= GDK_MOD3_MASK;
4852 if (strstr(value, "M4-"))
4853 mask |= GDK_MOD4_MASK;
4854 if (strstr(value, "M5-"))
4855 mask |= GDK_MOD5_MASK;
4857 /* find keyname */
4858 for (i = strlen(value) - 1; i > 0; i--)
4859 if (value[i] == '-')
4860 value = &value[i + 1];
4862 /* validate keyname */
4863 keyval = gdk_keyval_from_name(value);
4864 if (keyval == GDK_VoidSymbol) {
4865 warnx("invalid keybinding name %s", value);
4866 return (1);
4868 /* must run this test too, gtk+ doesn't handle 10 for example */
4869 if (gdk_keyval_name(keyval) == NULL) {
4870 warnx("invalid keybinding name %s", value);
4871 return (1);
4874 /* make sure it isn't a dupe */
4875 TAILQ_FOREACH(k, &kbl, entry)
4876 if (k->key == keyval && k->mask == mask) {
4877 warnx("duplicate keybinding for %s", value);
4878 return (1);
4881 /* add keyname */
4882 k = g_malloc0(sizeof *k);
4883 k->name = orig->name;
4884 k->mask = mask;
4885 k->use_in_entry = orig->use_in_entry;
4886 k->key = keyval;
4887 k->func = orig->func;
4888 bcopy(&orig->arg, &k->arg, sizeof k->arg);
4890 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4891 k->name,
4892 k->mask,
4893 k->use_in_entry,
4894 k->key);
4895 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4896 k->name, gdk_keyval_name(keyval));
4898 TAILQ_INSERT_HEAD(&kbl, k, entry);
4900 return (0);
4904 add_kb(struct settings *s, char *entry)
4906 int i;
4907 char *kb, *value;
4909 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4911 /* clearall is special */
4912 if (!strcmp(entry, "clearall")) {
4913 keybinding_clearall();
4914 return (0);
4917 kb = strstr(entry, ",");
4918 if (kb == NULL)
4919 return (1);
4920 *kb = '\0';
4921 value = kb + 1;
4923 /* make sure it is a valid keybinding */
4924 for (i = 0; i < LENGTH(keys); i++)
4925 if (keys[i].name && !strcmp(entry, keys[i].name)) {
4926 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4927 keys[i].name,
4928 keys[i].mask,
4929 keys[i].use_in_entry,
4930 keys[i].key);
4932 return (keybinding_add(entry, value, &keys[i]));
4935 return (1);
4938 struct cmd {
4939 char *cmd;
4940 int params;
4941 int (*func)(struct tab *, struct karg *);
4942 struct karg arg;
4943 } cmds[] = {
4944 { "q!", 0, quit, {0} },
4945 { "qa", 0, quit, {0} },
4946 { "qa!", 0, quit, {0} },
4947 { "w", 0, save_tabs, {0} },
4948 { "wq", 0, save_tabs_and_quit, {0} },
4949 { "wq!", 0, save_tabs_and_quit, {0} },
4950 { "help", 0, help, {0} },
4951 { "about", 0, about, {0} },
4952 { "stats", 0, stats, {0} },
4953 { "version", 0, about, {0} },
4954 { "cookies", 0, xtp_page_cl, {0} },
4955 { "fav", 0, xtp_page_fl, {0} },
4956 { "favadd", 0, add_favorite, {0} },
4957 { "js", 2, js_cmd, {0} },
4958 { "cookie", 2, cookie_cmd, {0} },
4959 { "cert", 1, cert_cmd, {0} },
4960 { "ca", 0, ca_cmd, {0} },
4961 { "dl", 0, xtp_page_dl, {0} },
4962 { "h", 0, xtp_page_hl, {0} },
4963 { "hist", 0, xtp_page_hl, {0} },
4964 { "history", 0, xtp_page_hl, {0} },
4965 { "home", 0, go_home, {0} },
4966 { "restart", 0, restart, {0} },
4967 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4968 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4969 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4970 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4971 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4972 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4973 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4974 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4976 { "1", 0, move, {.i = XT_MOVE_TOP} },
4977 { "print", 0, print_page, {0} },
4979 /* tabs */
4980 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4981 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4982 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4983 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4984 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4985 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4986 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4987 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4988 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4989 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4990 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4991 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4992 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4993 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4994 /* XXX add count to these commands */
4995 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4996 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4997 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4998 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4999 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
5000 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
5001 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
5002 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
5003 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
5004 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
5006 /* settings */
5007 { "set", 1, set, {0} },
5008 { "fullscreen", 0, fullscreen, {0} },
5009 { "f", 0, fullscreen, {0} },
5011 /* sessions */
5012 { "session", 1, session_cmd, {0} },
5015 gboolean
5016 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5018 hide_oops(t);
5020 return (FALSE);
5023 gboolean
5024 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5026 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5028 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5029 delete_tab(t);
5031 return (FALSE);
5035 * cancel, remove, etc. downloads
5037 void
5038 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5040 struct download find, *d;
5042 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5044 /* some commands require a valid download id */
5045 if (cmd != XT_XTP_DL_LIST) {
5046 /* lookup download in question */
5047 find.id = id;
5048 d = RB_FIND(download_list, &downloads, &find);
5050 if (d == NULL) {
5051 show_oops(t, "%s: no such download", __func__);
5052 return;
5056 /* decide what to do */
5057 switch (cmd) {
5058 case XT_XTP_DL_CANCEL:
5059 webkit_download_cancel(d->download);
5060 break;
5061 case XT_XTP_DL_REMOVE:
5062 webkit_download_cancel(d->download); /* just incase */
5063 g_object_unref(d->download);
5064 RB_REMOVE(download_list, &downloads, d);
5065 break;
5066 case XT_XTP_DL_LIST:
5067 /* Nothing */
5068 break;
5069 default:
5070 show_oops(t, "%s: unknown command", __func__);
5071 break;
5073 xtp_page_dl(t, NULL);
5077 * Actions on history, only does one thing for now, but
5078 * we provide the function for future actions
5080 void
5081 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5083 struct history *h, *next;
5084 int i = 1;
5086 switch (cmd) {
5087 case XT_XTP_HL_REMOVE:
5088 /* walk backwards, as listed in reverse */
5089 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5090 next = RB_PREV(history_list, &hl, h);
5091 if (id == i) {
5092 RB_REMOVE(history_list, &hl, h);
5093 g_free((gpointer) h->title);
5094 g_free((gpointer) h->uri);
5095 g_free(h);
5096 break;
5098 i++;
5100 break;
5101 case XT_XTP_HL_LIST:
5102 /* Nothing - just xtp_page_hl() below */
5103 break;
5104 default:
5105 show_oops(t, "%s: unknown command", __func__);
5106 break;
5109 xtp_page_hl(t, NULL);
5112 /* remove a favorite */
5113 void
5114 remove_favorite(struct tab *t, int index)
5116 char file[PATH_MAX], *title, *uri;
5117 char *new_favs, *tmp;
5118 FILE *f;
5119 int i;
5120 size_t len, lineno;
5122 /* open favorites */
5123 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5125 if ((f = fopen(file, "r")) == NULL) {
5126 show_oops(t, "%s: can't open favorites: %s",
5127 __func__, strerror(errno));
5128 return;
5131 /* build a string which will become the new favroites file */
5132 new_favs = g_strdup_printf("%s", "");
5134 for (i = 1;;) {
5135 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5136 if (feof(f) || ferror(f))
5137 break;
5138 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5139 if (len == 0) {
5140 free(title);
5141 title = NULL;
5142 continue;
5145 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5146 if (feof(f) || ferror(f)) {
5147 show_oops(t, "%s: can't parse favorites %s",
5148 __func__, strerror(errno));
5149 goto clean;
5153 /* as long as this isn't the one we are deleting add to file */
5154 if (i != index) {
5155 tmp = new_favs;
5156 new_favs = g_strdup_printf("%s%s\n%s\n",
5157 new_favs, title, uri);
5158 g_free(tmp);
5161 free(uri);
5162 uri = NULL;
5163 free(title);
5164 title = NULL;
5165 i++;
5167 fclose(f);
5169 /* write back new favorites file */
5170 if ((f = fopen(file, "w")) == NULL) {
5171 show_oops(t, "%s: can't open favorites: %s",
5172 __func__, strerror(errno));
5173 goto clean;
5176 fwrite(new_favs, strlen(new_favs), 1, f);
5177 fclose(f);
5179 clean:
5180 if (uri)
5181 free(uri);
5182 if (title)
5183 free(title);
5185 g_free(new_favs);
5188 void
5189 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5191 switch (cmd) {
5192 case XT_XTP_FL_LIST:
5193 /* nothing, just the below call to xtp_page_fl() */
5194 break;
5195 case XT_XTP_FL_REMOVE:
5196 remove_favorite(t, arg);
5197 break;
5198 default:
5199 show_oops(t, "%s: invalid favorites command", __func__);
5200 break;
5203 xtp_page_fl(t, NULL);
5206 void
5207 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5209 switch (cmd) {
5210 case XT_XTP_CL_LIST:
5211 /* nothing, just xtp_page_cl() */
5212 break;
5213 case XT_XTP_CL_REMOVE:
5214 remove_cookie(arg);
5215 break;
5216 default:
5217 show_oops(t, "%s: unknown cookie xtp command", __func__);
5218 break;
5221 xtp_page_cl(t, NULL);
5224 /* link an XTP class to it's session key and handler function */
5225 struct xtp_despatch {
5226 uint8_t xtp_class;
5227 char **session_key;
5228 void (*handle_func)(struct tab *, uint8_t, int);
5231 struct xtp_despatch xtp_despatches[] = {
5232 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5233 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5234 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5235 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5236 { NULL, NULL, NULL }
5240 * is the url xtp protocol? (xxxt://)
5241 * if so, parse and despatch correct bahvior
5244 parse_xtp_url(struct tab *t, const char *url)
5246 char *dup = NULL, *p, *last;
5247 uint8_t n_tokens = 0;
5248 char *tokens[4] = {NULL, NULL, NULL, ""};
5249 struct xtp_despatch *dsp, *dsp_match = NULL;
5250 uint8_t req_class;
5253 * tokens array meaning:
5254 * tokens[0] = class
5255 * tokens[1] = session key
5256 * tokens[2] = action
5257 * tokens[3] = optional argument
5260 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5262 /*xtp tab meaning is normal unless proven special */
5263 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5265 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5266 return 0;
5268 dup = g_strdup(url + strlen(XT_XTP_STR));
5270 /* split out the url */
5271 for ((p = strtok_r(dup, "/", &last)); p;
5272 (p = strtok_r(NULL, "/", &last))) {
5273 if (n_tokens < 4)
5274 tokens[n_tokens++] = p;
5277 /* should be atleast three fields 'class/seskey/command/arg' */
5278 if (n_tokens < 3)
5279 goto clean;
5281 dsp = xtp_despatches;
5282 req_class = atoi(tokens[0]);
5283 while (dsp->xtp_class != NULL) {
5284 if (dsp->xtp_class == req_class) {
5285 dsp_match = dsp;
5286 break;
5288 dsp++;
5291 /* did we find one atall? */
5292 if (dsp_match == NULL) {
5293 show_oops(t, "%s: no matching xtp despatch found", __func__);
5294 goto clean;
5297 /* check session key and call despatch function */
5298 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5299 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5302 clean:
5303 if (dup)
5304 g_free(dup);
5306 return 1;
5311 void
5312 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5314 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5316 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5318 if (t == NULL) {
5319 show_oops_s("activate_uri_entry_cb invalid parameters");
5320 return;
5323 if (uri == NULL) {
5324 show_oops(t, "activate_uri_entry_cb no uri");
5325 return;
5328 uri += strspn(uri, "\t ");
5330 /* if xxxt:// treat specially */
5331 if (!parse_xtp_url(t, uri)) {
5332 load_uri(t, (gchar *)uri);
5333 focus_webview(t);
5337 void
5338 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5340 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5341 char *newuri = NULL;
5342 gchar *enc_search;
5344 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5346 if (t == NULL) {
5347 show_oops_s("activate_search_entry_cb invalid parameters");
5348 return;
5351 if (search_string == NULL) {
5352 show_oops(t, "no search_string");
5353 return;
5356 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5357 newuri = g_strdup_printf(search_string, enc_search);
5358 g_free(enc_search);
5360 webkit_web_view_load_uri(t->wv, newuri);
5361 focus_webview(t);
5363 if (newuri)
5364 g_free(newuri);
5367 void
5368 check_and_set_js(const gchar *uri, struct tab *t)
5370 struct domain *d = NULL;
5371 int es = 0;
5373 if (uri == NULL || t == NULL)
5374 return;
5376 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5377 es = 0;
5378 else
5379 es = 1;
5381 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5382 es ? "enable" : "disable", uri);
5384 g_object_set(G_OBJECT(t->settings),
5385 "enable-scripts", es, (char *)NULL);
5386 g_object_set(G_OBJECT(t->settings),
5387 "javascript-can-open-windows-automatically", es, (char *)NULL);
5388 webkit_web_view_set_settings(t->wv, t->settings);
5390 button_set_stockid(t->js_toggle,
5391 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5394 void
5395 show_ca_status(struct tab *t, const char *uri)
5397 WebKitWebFrame *frame;
5398 WebKitWebDataSource *source;
5399 WebKitNetworkRequest *request;
5400 SoupMessage *message;
5401 GdkColor color;
5402 gchar *col_str = "white";
5403 int r;
5405 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5406 ssl_strict_certs, ssl_ca_file, uri);
5408 if (uri == NULL)
5409 goto done;
5410 if (ssl_ca_file == NULL) {
5411 if (g_str_has_prefix(uri, "http://"))
5412 goto done;
5413 if (g_str_has_prefix(uri, "https://")) {
5414 col_str = "red";
5415 goto done;
5417 return;
5419 if (g_str_has_prefix(uri, "http://") ||
5420 !g_str_has_prefix(uri, "https://"))
5421 goto done;
5423 frame = webkit_web_view_get_main_frame(t->wv);
5424 source = webkit_web_frame_get_data_source(frame);
5425 request = webkit_web_data_source_get_request(source);
5426 message = webkit_network_request_get_message(request);
5428 if (message && (soup_message_get_flags(message) &
5429 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5430 col_str = "green";
5431 goto done;
5432 } else {
5433 r = load_compare_cert(t, NULL);
5434 if (r == 0)
5435 col_str = "lightblue";
5436 else if (r == 1)
5437 col_str = "yellow";
5438 else
5439 col_str = "red";
5440 goto done;
5442 done:
5443 if (col_str) {
5444 gdk_color_parse(col_str, &color);
5445 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5447 if (!strcmp(col_str, "white")) {
5448 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5449 &color);
5450 gdk_color_parse("black", &color);
5451 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5452 &color);
5453 } else {
5454 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5455 &color);
5456 gdk_color_parse("black", &color);
5457 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5458 &color);
5463 void
5464 free_favicon(struct tab *t)
5466 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5467 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5469 if (t->icon_request)
5470 g_object_unref(t->icon_request);
5471 if (t->icon_pixbuf)
5472 g_object_unref(t->icon_pixbuf);
5473 if (t->icon_dest_uri)
5474 g_free(t->icon_dest_uri);
5476 t->icon_pixbuf = NULL;
5477 t->icon_request = NULL;
5478 t->icon_dest_uri = NULL;
5481 void
5482 xt_icon_from_name(struct tab *t, gchar *name)
5484 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5485 GTK_ENTRY_ICON_PRIMARY, "text-html");
5486 if (show_url == 0)
5487 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5488 GTK_ENTRY_ICON_PRIMARY, "text-html");
5489 else
5490 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5491 GTK_ENTRY_ICON_PRIMARY, NULL);
5494 void
5495 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5497 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5498 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5499 if (show_url == 0)
5500 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5501 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5502 else
5503 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5504 GTK_ENTRY_ICON_PRIMARY, NULL);
5507 gboolean
5508 is_valid_icon(char *file)
5510 gboolean valid = 0;
5511 const char *mime_type;
5512 GFileInfo *fi;
5513 GFile *gf;
5515 gf = g_file_new_for_path(file);
5516 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5517 NULL, NULL);
5518 mime_type = g_file_info_get_content_type(fi);
5519 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5520 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5521 g_strcmp0(mime_type, "image/png") == 0 ||
5522 g_strcmp0(mime_type, "image/gif") == 0 ||
5523 g_strcmp0(mime_type, "application/octet-stream") == 0;
5524 g_object_unref(fi);
5525 g_object_unref(gf);
5527 return (valid);
5530 void
5531 set_favicon_from_file(struct tab *t, char *file)
5533 gint width, height;
5534 GdkPixbuf *pixbuf, *scaled;
5535 struct stat sb;
5537 if (t == NULL || file == NULL)
5538 return;
5539 if (t->icon_pixbuf) {
5540 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5541 return;
5544 if (g_str_has_prefix(file, "file://"))
5545 file += strlen("file://");
5546 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5548 if (!stat(file, &sb)) {
5549 if (sb.st_size == 0 || !is_valid_icon(file)) {
5550 /* corrupt icon so trash it */
5551 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5552 __func__, file);
5553 unlink(file);
5554 /* no need to set icon to default here */
5555 return;
5559 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5560 if (pixbuf == NULL) {
5561 xt_icon_from_name(t, "text-html");
5562 return;
5565 g_object_get(pixbuf, "width", &width, "height", &height,
5566 (char *)NULL);
5567 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5568 __func__, t->tab_id, width, height);
5570 if (width > 16 || height > 16) {
5571 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5572 GDK_INTERP_BILINEAR);
5573 g_object_unref(pixbuf);
5574 } else
5575 scaled = pixbuf;
5577 if (scaled == NULL) {
5578 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5579 GDK_INTERP_BILINEAR);
5580 return;
5583 t->icon_pixbuf = scaled;
5584 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5587 void
5588 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5589 WebKitWebView *wv)
5591 WebKitDownloadStatus status = webkit_download_get_status(download);
5592 struct tab *tt = NULL, *t = NULL;
5595 * find the webview instead of passing in the tab as it could have been
5596 * deleted from underneath us.
5598 TAILQ_FOREACH(tt, &tabs, entry) {
5599 if (tt->wv == wv) {
5600 t = tt;
5601 break;
5604 if (t == NULL)
5605 return;
5607 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5608 __func__, t->tab_id, status);
5610 switch (status) {
5611 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5612 /* -1 */
5613 t->icon_download = NULL;
5614 free_favicon(t);
5615 break;
5616 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5617 /* 0 */
5618 break;
5619 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5620 /* 1 */
5621 break;
5622 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5623 /* 2 */
5624 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5625 __func__, t->tab_id);
5626 t->icon_download = NULL;
5627 free_favicon(t);
5628 break;
5629 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5630 /* 3 */
5632 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5633 __func__, t->icon_dest_uri);
5634 set_favicon_from_file(t, t->icon_dest_uri);
5635 /* these will be freed post callback */
5636 t->icon_request = NULL;
5637 t->icon_download = NULL;
5638 break;
5639 default:
5640 break;
5644 void
5645 abort_favicon_download(struct tab *t)
5647 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5649 if (t->icon_download) {
5650 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5651 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5652 webkit_download_cancel(t->icon_download);
5653 t->icon_download = NULL;
5654 } else
5655 free_favicon(t);
5657 xt_icon_from_name(t, "text-html");
5660 void
5661 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5663 gchar *name_hash, file[PATH_MAX];
5664 struct stat sb;
5666 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5668 if (uri == NULL || t == NULL)
5669 return;
5671 if (t->icon_request) {
5672 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5673 return;
5676 /* check to see if we got the icon in cache */
5677 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5678 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5679 g_free(name_hash);
5681 if (!stat(file, &sb)) {
5682 if (sb.st_size > 0) {
5683 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5684 __func__, file);
5685 set_favicon_from_file(t, file);
5686 return;
5689 /* corrupt icon so trash it */
5690 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5691 __func__, file);
5692 unlink(file);
5695 /* create download for icon */
5696 t->icon_request = webkit_network_request_new(uri);
5697 if (t->icon_request == NULL) {
5698 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5699 __func__, uri);
5700 return;
5703 t->icon_download = webkit_download_new(t->icon_request);
5704 if (t->icon_download == NULL) {
5705 fprintf(stderr, "%s: icon_download", __func__);
5706 return;
5709 /* we have to free icon_dest_uri later */
5710 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5711 webkit_download_set_destination_uri(t->icon_download,
5712 t->icon_dest_uri);
5714 if (webkit_download_get_status(t->icon_download) ==
5715 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5716 fprintf(stderr, "%s: download failed to start", __func__);
5717 g_object_unref(t->icon_request);
5718 g_free(t->icon_dest_uri);
5719 t->icon_request = NULL;
5720 t->icon_dest_uri = NULL;
5721 return;
5724 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5725 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5727 webkit_download_start(t->icon_download);
5730 void
5731 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5733 const gchar *set = NULL, *uri = NULL, *title = NULL;
5734 struct history *h, find;
5735 const gchar *s_loading;
5736 struct karg a;
5738 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
5739 webkit_web_view_get_load_status(wview));
5741 if (t == NULL) {
5742 show_oops_s("notify_load_status_cb invalid paramters");
5743 return;
5746 switch (webkit_web_view_get_load_status(wview)) {
5747 case WEBKIT_LOAD_PROVISIONAL:
5748 /* 0 */
5749 abort_favicon_download(t);
5750 #if GTK_CHECK_VERSION(2, 20, 0)
5751 gtk_widget_show(t->spinner);
5752 gtk_spinner_start(GTK_SPINNER(t->spinner));
5753 #endif
5754 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5756 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5758 t->focus_wv = 1;
5760 break;
5762 case WEBKIT_LOAD_COMMITTED:
5763 /* 1 */
5764 if ((uri = get_uri(wview)) != NULL) {
5765 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5766 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5768 if (t->status) {
5769 g_free(t->status);
5770 t->status = NULL;
5772 set_status(t, (char *)uri, XT_STATUS_LOADING);
5775 /* check if js white listing is enabled */
5776 if (enable_js_whitelist) {
5777 uri = get_uri(wview);
5778 check_and_set_js(uri, t);
5781 if (t->styled)
5782 apply_style(t);
5784 show_ca_status(t, uri);
5786 /* we know enough to autosave the session */
5787 if (session_autosave) {
5788 a.s = NULL;
5789 save_tabs(t, &a);
5791 break;
5793 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5794 /* 3 */
5795 break;
5797 case WEBKIT_LOAD_FINISHED:
5798 /* 2 */
5799 uri = get_uri(wview);
5801 if (!strncmp(uri, "http://", strlen("http://")) ||
5802 !strncmp(uri, "https://", strlen("https://")) ||
5803 !strncmp(uri, "file://", strlen("file://"))) {
5804 find.uri = uri;
5805 h = RB_FIND(history_list, &hl, &find);
5806 if (!h) {
5807 title = webkit_web_view_get_title(wview);
5808 set = title ? title: uri;
5809 h = g_malloc(sizeof *h);
5810 h->uri = g_strdup(uri);
5811 h->title = g_strdup(set);
5812 RB_INSERT(history_list, &hl, h);
5813 completion_add_uri(h->uri);
5814 update_history_tabs(NULL);
5818 set_status(t, (char *)uri, XT_STATUS_URI);
5819 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5820 case WEBKIT_LOAD_FAILED:
5821 /* 4 */
5822 #endif
5823 #if GTK_CHECK_VERSION(2, 20, 0)
5824 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5825 gtk_widget_hide(t->spinner);
5826 #endif
5827 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5828 if (s_loading && !strcmp(s_loading, "Loading"))
5829 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5830 default:
5831 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5832 break;
5835 if (t->item)
5836 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5837 else
5838 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5839 webkit_web_view_can_go_back(wview));
5841 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5842 webkit_web_view_can_go_forward(wview));
5844 /* take focus if we are visible */
5845 focus_webview(t);
5848 void
5849 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5851 const gchar *set = NULL, *title = NULL;
5853 title = webkit_web_view_get_title(wview);
5854 set = title ? title: get_uri(wview);
5855 gtk_label_set_text(GTK_LABEL(t->label), set);
5856 gtk_window_set_title(GTK_WINDOW(main_window), set);
5859 void
5860 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5862 run_script(t, JS_HINTING);
5865 void
5866 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5868 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5869 progress == 100 ? 0 : (double)progress / 100);
5870 if (show_url == 0) {
5871 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5872 progress == 100 ? 0 : (double)progress / 100);
5877 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5878 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5879 WebKitWebPolicyDecision *pd, struct tab *t)
5881 char *uri;
5883 if (t == NULL) {
5884 show_oops_s("webview_npd_cb invalid parameters");
5885 return (FALSE);
5888 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5889 t->ctrl_click,
5890 webkit_network_request_get_uri(request));
5892 uri = (char *)webkit_network_request_get_uri(request);
5894 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
5895 t->ctrl_click = 0;
5896 create_new_tab(uri, NULL, ctrl_click_focus);
5897 webkit_web_policy_decision_ignore(pd);
5898 return (TRUE); /* we made the decission */
5901 webkit_web_policy_decision_use(pd);
5902 return (TRUE); /* we made the decission */
5905 WebKitWebView *
5906 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5908 struct tab *tt;
5909 struct domain *d = NULL;
5910 const gchar *uri;
5911 WebKitWebView *webview = NULL;
5913 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5914 webkit_web_view_get_uri(wv));
5916 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
5917 uri = webkit_web_view_get_uri(wv);
5918 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
5919 return (NULL);
5921 tt = create_new_tab(NULL, NULL, 1);
5922 webview = tt->wv;
5923 } else if (enable_scripts == 1) {
5924 tt = create_new_tab(NULL, NULL, 1);
5925 webview = tt->wv;
5928 return (webview);
5931 gboolean
5932 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
5934 const gchar *uri;
5935 struct domain *d = NULL;
5937 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
5939 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
5940 uri = webkit_web_view_get_uri(wv);
5941 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
5942 return (FALSE);
5944 delete_tab(t);
5945 } else if (enable_scripts == 1)
5946 delete_tab(t);
5948 return (TRUE);
5952 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5954 /* we can not eat the event without throwing gtk off so defer it */
5956 /* catch middle click */
5957 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5958 t->ctrl_click = 1;
5959 goto done;
5962 /* catch ctrl click */
5963 if (e->type == GDK_BUTTON_RELEASE &&
5964 CLEAN(e->state) == GDK_CONTROL_MASK)
5965 t->ctrl_click = 1;
5966 else
5967 t->ctrl_click = 0;
5968 done:
5969 return (XT_CB_PASSTHROUGH);
5973 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5975 struct mime_type *m;
5977 m = find_mime_type(mime_type);
5978 if (m == NULL)
5979 return (1);
5981 switch (fork()) {
5982 case -1:
5983 show_oops(t, "can't fork mime handler");
5984 /* NOTREACHED */
5985 case 0:
5986 break;
5987 default:
5988 return (0);
5991 /* child */
5992 execlp(m->mt_action, m->mt_action,
5993 webkit_network_request_get_uri(request), (void *)NULL);
5995 _exit(0);
5997 /* NOTREACHED */
5998 return (0);
6002 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6003 WebKitNetworkRequest *request, char *mime_type,
6004 WebKitWebPolicyDecision *decision, struct tab *t)
6006 if (t == NULL) {
6007 show_oops_s("webview_mimetype_cb invalid parameters");
6008 return (FALSE);
6011 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6012 t->tab_id, mime_type);
6014 if (run_mimehandler(t, mime_type, request) == 0) {
6015 webkit_web_policy_decision_ignore(decision);
6016 focus_webview(t);
6017 return (TRUE);
6020 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6021 webkit_web_policy_decision_download(decision);
6022 return (TRUE);
6025 return (FALSE);
6029 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6030 struct tab *t)
6032 const gchar *filename;
6033 char *uri = NULL;
6034 struct download *download_entry;
6035 int ret = TRUE;
6037 if (wk_download == NULL || t == NULL) {
6038 show_oops_s("%s invalid parameters", __func__);
6039 return (FALSE);
6042 filename = webkit_download_get_suggested_filename(wk_download);
6043 if (filename == NULL)
6044 return (FALSE); /* abort download */
6046 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6048 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6049 "local %s\n", __func__, t->tab_id, filename, uri);
6051 webkit_download_set_destination_uri(wk_download, uri);
6053 if (webkit_download_get_status(wk_download) ==
6054 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6055 show_oops(t, "%s: download failed to start", __func__);
6056 ret = FALSE;
6057 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6058 } else {
6059 download_entry = g_malloc(sizeof(struct download));
6060 download_entry->download = wk_download;
6061 download_entry->tab = t;
6062 download_entry->id = next_download_id++;
6063 RB_INSERT(download_list, &downloads, download_entry);
6064 /* get from history */
6065 g_object_ref(wk_download);
6066 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6069 if (uri)
6070 g_free(uri);
6072 /* sync other download manager tabs */
6073 update_download_tabs(NULL);
6076 * NOTE: never redirect/render the current tab before this
6077 * function returns. This will cause the download to never start.
6079 return (ret); /* start download */
6082 void
6083 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6085 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6087 if (t == NULL) {
6088 show_oops_s("webview_hover_cb");
6089 return;
6092 if (uri)
6093 set_status(t, uri, XT_STATUS_LINK);
6094 else {
6095 if (t->status)
6096 set_status(t, t->status, XT_STATUS_NOTHING);
6101 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6103 char s[2], buf[128];
6104 const char *errstr = NULL;
6105 long long link;
6107 /* don't use w directly; use t->whatever instead */
6109 if (t == NULL) {
6110 show_oops_s("wv_keypress_after_cb");
6111 return (XT_CB_PASSTHROUGH);
6114 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6115 e->keyval, e->state, t);
6117 if (t->hints_on) {
6118 /* ESC */
6119 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6120 disable_hints(t);
6121 return (XT_CB_HANDLED);
6124 /* RETURN */
6125 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6126 link = strtonum(t->hint_num, 1, 1000, &errstr);
6127 if (errstr) {
6128 /* we have a string */
6129 } else {
6130 /* we have a number */
6131 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6132 t->hint_num);
6133 run_script(t, buf);
6135 disable_hints(t);
6138 /* BACKSPACE */
6139 /* XXX unfuck this */
6140 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6141 if (t->hint_mode == XT_HINT_NUMERICAL) {
6142 /* last input was numerical */
6143 int l;
6144 l = strlen(t->hint_num);
6145 if (l > 0) {
6146 l--;
6147 if (l == 0) {
6148 disable_hints(t);
6149 enable_hints(t);
6150 } else {
6151 t->hint_num[l] = '\0';
6152 goto num;
6155 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6156 /* last input was alphanumerical */
6157 int l;
6158 l = strlen(t->hint_buf);
6159 if (l > 0) {
6160 l--;
6161 if (l == 0) {
6162 disable_hints(t);
6163 enable_hints(t);
6164 } else {
6165 t->hint_buf[l] = '\0';
6166 goto anum;
6169 } else {
6170 /* bogus */
6171 disable_hints(t);
6175 /* numerical input */
6176 if (CLEAN(e->state) == 0 &&
6177 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6178 snprintf(s, sizeof s, "%c", e->keyval);
6179 strlcat(t->hint_num, s, sizeof t->hint_num);
6180 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6181 t->hint_num);
6182 num:
6183 link = strtonum(t->hint_num, 1, 1000, &errstr);
6184 if (errstr) {
6185 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6186 disable_hints(t);
6187 } else {
6188 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6189 t->hint_num);
6190 t->hint_mode = XT_HINT_NUMERICAL;
6191 run_script(t, buf);
6194 /* empty the counter buffer */
6195 bzero(t->hint_buf, sizeof t->hint_buf);
6196 return (XT_CB_HANDLED);
6199 /* alphanumerical input */
6200 if (
6201 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6202 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6203 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6204 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6205 snprintf(s, sizeof s, "%c", e->keyval);
6206 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6207 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6208 t->hint_buf);
6209 anum:
6210 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6211 run_script(t, buf);
6213 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6214 t->hint_buf);
6215 t->hint_mode = XT_HINT_ALPHANUM;
6216 run_script(t, buf);
6218 /* empty the counter buffer */
6219 bzero(t->hint_num, sizeof t->hint_num);
6220 return (XT_CB_HANDLED);
6223 return (XT_CB_HANDLED);
6226 struct key_binding *k;
6227 TAILQ_FOREACH(k, &kbl, entry)
6228 if (e->keyval == k->key) {
6229 if (k->mask == 0) {
6230 if ((e->state & (CTRL | MOD1)) == 0) {
6231 k->func(t, &k->arg);
6232 return (XT_CB_HANDLED);
6235 else if ((e->state & k->mask) == k->mask) {
6236 k->func(t, &k->arg);
6237 return (XT_CB_HANDLED);
6241 return (XT_CB_PASSTHROUGH);
6245 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6247 hide_oops(t);
6249 return (XT_CB_PASSTHROUGH);
6253 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6255 const gchar *c = gtk_entry_get_text(w);
6256 GdkColor color;
6257 int forward = TRUE;
6259 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6260 e->keyval, e->state, t);
6262 if (t == NULL) {
6263 show_oops_s("cmd_keyrelease_cb invalid parameters");
6264 return (XT_CB_PASSTHROUGH);
6267 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6268 e->keyval, e->state, t);
6270 if (c[0] == ':')
6271 goto done;
6272 if (strlen(c) == 1) {
6273 webkit_web_view_unmark_text_matches(t->wv);
6274 goto done;
6277 if (c[0] == '/')
6278 forward = TRUE;
6279 else if (c[0] == '?')
6280 forward = FALSE;
6281 else
6282 goto done;
6284 /* search */
6285 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6286 FALSE) {
6287 /* not found, mark red */
6288 gdk_color_parse("red", &color);
6289 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6290 /* unmark and remove selection */
6291 webkit_web_view_unmark_text_matches(t->wv);
6292 /* my kingdom for a way to unselect text in webview */
6293 } else {
6294 /* found, highlight all */
6295 webkit_web_view_unmark_text_matches(t->wv);
6296 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6297 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6298 gdk_color_parse("white", &color);
6299 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6301 done:
6302 return (XT_CB_PASSTHROUGH);
6305 #if 0
6307 cmd_complete(struct tab *t, char *s)
6309 int i;
6310 GtkEntry *w = GTK_ENTRY(t->cmd);
6312 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
6314 for (i = 0; i < LENGTH(cmds); i++) {
6315 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
6316 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
6317 #if 0
6318 gtk_entry_set_text(w, ":");
6319 gtk_entry_append_text(w, cmds[i].cmd);
6320 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6321 #endif
6325 return (0);
6327 #endif
6330 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6332 if (t == NULL) {
6333 show_oops_s("entry_key_cb invalid parameters");
6334 return (XT_CB_PASSTHROUGH);
6337 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6338 e->keyval, e->state, t);
6340 hide_oops(t);
6342 if (e->keyval == GDK_Escape) {
6343 /* don't use focus_webview(t) because we want to type :cmds */
6344 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6347 struct key_binding *k;
6348 TAILQ_FOREACH(k, &kbl, entry)
6349 if (e->keyval == k->key && k->use_in_entry) {
6350 if (k->mask == 0) {
6351 if ((e->state & (CTRL | MOD1)) == 0) {
6352 k->func(t, &k->arg);
6353 return (XT_CB_HANDLED);
6356 else if ((e->state & k->mask) == k->mask) {
6357 k->func(t, &k->arg);
6358 return (XT_CB_HANDLED);
6362 return (XT_CB_PASSTHROUGH);
6366 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6368 int rv = XT_CB_HANDLED;
6369 const gchar *c = gtk_entry_get_text(w);
6371 if (t == NULL) {
6372 show_oops_s("cmd_keypress_cb parameters");
6373 return (XT_CB_PASSTHROUGH);
6376 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6377 e->keyval, e->state, t);
6379 /* sanity */
6380 if (c == NULL)
6381 e->keyval = GDK_Escape;
6382 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6383 e->keyval = GDK_Escape;
6385 switch (e->keyval) {
6386 #if 0
6387 case GDK_Tab:
6388 if (c[0] != ':')
6389 goto done;
6391 if (strchr (c, ' ')) {
6392 /* par completion */
6393 fprintf(stderr, "completeme par\n");
6394 goto done;
6397 cmd_complete(t, (char *)&c[1]);
6399 goto done;
6400 #endif
6401 case GDK_BackSpace:
6402 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6403 break;
6404 /* FALLTHROUGH */
6405 case GDK_Escape:
6406 hide_cmd(t);
6407 focus_webview(t);
6409 /* cancel search */
6410 if (c[0] == '/' || c[0] == '?')
6411 webkit_web_view_unmark_text_matches(t->wv);
6412 goto done;
6415 rv = XT_CB_PASSTHROUGH;
6416 done:
6417 return (rv);
6421 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6423 if (t == NULL) {
6424 show_oops_s("cmd_focusout_cb invalid parameters");
6425 return (XT_CB_PASSTHROUGH);
6427 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6429 hide_cmd(t);
6430 hide_oops(t);
6432 if (show_url == 0 || t->focus_wv)
6433 focus_webview(t);
6434 else
6435 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6437 return (XT_CB_PASSTHROUGH);
6440 void
6441 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6443 int i;
6444 char *s;
6445 const gchar *c = gtk_entry_get_text(entry);
6447 if (t == NULL) {
6448 show_oops_s("cmd_activate_cb invalid parameters");
6449 return;
6452 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6454 /* sanity */
6455 if (c == NULL)
6456 goto done;
6457 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6458 goto done;
6459 if (strlen(c) < 2)
6460 goto done;
6461 s = (char *)&c[1];
6463 if (c[0] == '/' || c[0] == '?') {
6464 if (t->search_text) {
6465 g_free(t->search_text);
6466 t->search_text = NULL;
6469 t->search_text = g_strdup(s);
6470 if (global_search)
6471 g_free(global_search);
6472 global_search = g_strdup(s);
6473 t->search_forward = c[0] == '/';
6475 goto done;
6478 for (i = 0; i < LENGTH(cmds); i++)
6479 if (cmds[i].params) {
6480 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
6481 cmds[i].arg.s = g_strdup(s);
6482 goto execute_command;
6484 } else {
6485 if (!strcmp(s, cmds[i].cmd))
6486 goto execute_command;
6488 show_oops(t, "Invalid command: %s", s);
6489 done:
6490 hide_cmd(t);
6491 return;
6493 execute_command:
6494 hide_cmd(t);
6495 cmds[i].func(t, &cmds[i].arg);
6497 void
6498 backward_cb(GtkWidget *w, struct tab *t)
6500 struct karg a;
6502 if (t == NULL) {
6503 show_oops_s("backward_cb invalid parameters");
6504 return;
6507 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6509 a.i = XT_NAV_BACK;
6510 navaction(t, &a);
6513 void
6514 forward_cb(GtkWidget *w, struct tab *t)
6516 struct karg a;
6518 if (t == NULL) {
6519 show_oops_s("forward_cb invalid parameters");
6520 return;
6523 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6525 a.i = XT_NAV_FORWARD;
6526 navaction(t, &a);
6529 void
6530 stop_cb(GtkWidget *w, struct tab *t)
6532 WebKitWebFrame *frame;
6534 if (t == NULL) {
6535 show_oops_s("stop_cb invalid parameters");
6536 return;
6539 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6541 frame = webkit_web_view_get_main_frame(t->wv);
6542 if (frame == NULL) {
6543 show_oops(t, "stop_cb: no frame");
6544 return;
6547 webkit_web_frame_stop_loading(frame);
6548 abort_favicon_download(t);
6551 void
6552 setup_webkit(struct tab *t)
6554 g_object_set(G_OBJECT(t->settings),
6555 "user-agent", t->user_agent, (char *)NULL);
6556 g_object_set(G_OBJECT(t->settings),
6557 "enable-scripts", enable_scripts, (char *)NULL);
6558 g_object_set(G_OBJECT(t->settings),
6559 "enable-plugins", enable_plugins, (char *)NULL);
6560 g_object_set(G_OBJECT(t->settings),
6561 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6562 g_object_set(G_OBJECT(t->wv),
6563 "full-content-zoom", TRUE, (char *)NULL);
6564 adjustfont_webkit(t, XT_FONT_SET);
6566 webkit_web_view_set_settings(t->wv, t->settings);
6569 GtkWidget *
6570 create_browser(struct tab *t)
6572 GtkWidget *w;
6573 gchar *strval;
6575 if (t == NULL) {
6576 show_oops_s("create_browser invalid parameters");
6577 return (NULL);
6580 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6581 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6582 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6583 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6585 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6586 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6587 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6589 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6590 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6592 /* set defaults */
6593 t->settings = webkit_web_settings_new();
6595 if (user_agent == NULL) {
6596 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6597 (char *)NULL);
6598 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6599 g_free(strval);
6600 } else
6601 t->user_agent = g_strdup(user_agent);
6603 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
6605 setup_webkit(t);
6607 return (w);
6610 GtkWidget *
6611 create_window(void)
6613 GtkWidget *w;
6615 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6616 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6617 gtk_widget_set_name(w, "xxxterm");
6618 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6619 g_signal_connect(G_OBJECT(w), "delete_event",
6620 G_CALLBACK (gtk_main_quit), NULL);
6622 return (w);
6625 GtkWidget *
6626 create_toolbar(struct tab *t)
6628 GtkWidget *toolbar = NULL, *b, *eb1;
6630 b = gtk_hbox_new(FALSE, 0);
6631 toolbar = b;
6632 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6634 if (fancy_bar) {
6635 /* backward button */
6636 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
6637 gtk_widget_set_sensitive(t->backward, FALSE);
6638 g_signal_connect(G_OBJECT(t->backward), "clicked",
6639 G_CALLBACK(backward_cb), t);
6640 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
6642 /* forward button */
6643 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
6644 gtk_widget_set_sensitive(t->forward, FALSE);
6645 g_signal_connect(G_OBJECT(t->forward), "clicked",
6646 G_CALLBACK(forward_cb), t);
6647 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
6648 FALSE, 0);
6650 /* stop button */
6651 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
6652 gtk_widget_set_sensitive(t->stop, FALSE);
6653 g_signal_connect(G_OBJECT(t->stop), "clicked",
6654 G_CALLBACK(stop_cb), t);
6655 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
6656 FALSE, 0);
6658 /* JS button */
6659 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
6660 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
6661 gtk_widget_set_sensitive(t->js_toggle, TRUE);
6662 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
6663 G_CALLBACK(js_toggle_cb), t);
6664 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
6667 t->uri_entry = gtk_entry_new();
6668 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
6669 G_CALLBACK(activate_uri_entry_cb), t);
6670 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
6671 G_CALLBACK(entry_key_cb), t);
6672 completion_add(t);
6673 eb1 = gtk_hbox_new(FALSE, 0);
6674 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
6675 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
6676 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
6678 /* search entry */
6679 if (fancy_bar && search_string) {
6680 GtkWidget *eb2;
6681 t->search_entry = gtk_entry_new();
6682 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
6683 g_signal_connect(G_OBJECT(t->search_entry), "activate",
6684 G_CALLBACK(activate_search_entry_cb), t);
6685 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
6686 G_CALLBACK(entry_key_cb), t);
6687 gtk_widget_set_size_request(t->search_entry, -1, -1);
6688 eb2 = gtk_hbox_new(FALSE, 0);
6689 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
6690 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
6692 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
6694 return (toolbar);
6697 void
6698 recalc_tabs(void)
6700 struct tab *t;
6702 TAILQ_FOREACH(t, &tabs, entry)
6703 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
6707 undo_close_tab_save(struct tab *t)
6709 int m, n;
6710 const gchar *uri;
6711 struct undo *u1, *u2;
6712 GList *items;
6713 WebKitWebHistoryItem *item;
6715 if ((uri = get_uri(t->wv)) == NULL)
6716 return (1);
6718 u1 = g_malloc0(sizeof(struct undo));
6719 u1->uri = g_strdup(uri);
6721 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6723 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
6724 n = webkit_web_back_forward_list_get_back_length(t->bfl);
6725 u1->back = n;
6727 /* forward history */
6728 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
6730 while (items) {
6731 item = items->data;
6732 u1->history = g_list_prepend(u1->history,
6733 webkit_web_history_item_copy(item));
6734 items = g_list_next(items);
6737 /* current item */
6738 if (m) {
6739 item = webkit_web_back_forward_list_get_current_item(t->bfl);
6740 u1->history = g_list_prepend(u1->history,
6741 webkit_web_history_item_copy(item));
6744 /* back history */
6745 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
6747 while (items) {
6748 item = items->data;
6749 u1->history = g_list_prepend(u1->history,
6750 webkit_web_history_item_copy(item));
6751 items = g_list_next(items);
6754 TAILQ_INSERT_HEAD(&undos, u1, entry);
6756 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
6757 u2 = TAILQ_LAST(&undos, undo_tailq);
6758 TAILQ_REMOVE(&undos, u2, entry);
6759 g_free(u2->uri);
6760 g_list_free(u2->history);
6761 g_free(u2);
6762 } else
6763 undo_count++;
6765 return (0);
6768 void
6769 delete_tab(struct tab *t)
6771 struct karg a;
6773 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
6775 if (t == NULL)
6776 return;
6778 TAILQ_REMOVE(&tabs, t, entry);
6780 /* halt all webkit activity */
6781 abort_favicon_download(t);
6782 webkit_web_view_stop_loading(t->wv);
6783 undo_close_tab_save(t);
6785 gtk_widget_destroy(t->vbox);
6786 g_free(t->user_agent);
6787 g_free(t->stylesheet);
6788 g_free(t);
6790 recalc_tabs();
6791 if (TAILQ_EMPTY(&tabs))
6792 create_new_tab(NULL, NULL, 1);
6795 /* recreate session */
6796 if (session_autosave) {
6797 a.s = NULL;
6798 save_tabs(t, &a);
6802 void
6803 adjustfont_webkit(struct tab *t, int adjust)
6805 gfloat zoom;
6807 if (t == NULL) {
6808 show_oops_s("adjustfont_webkit invalid parameters");
6809 return;
6812 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
6813 if (adjust == XT_FONT_SET) {
6814 t->font_size = default_font_size;
6815 zoom = default_zoom_level;
6816 t->font_size += adjust;
6817 g_object_set(G_OBJECT(t->settings), "default-font-size",
6818 t->font_size, (char *)NULL);
6819 g_object_get(G_OBJECT(t->settings), "default-font-size",
6820 &t->font_size, (char *)NULL);
6821 } else {
6822 t->font_size += adjust;
6823 zoom += adjust/25.0;
6824 if (zoom < 0.0) {
6825 zoom = 0.04;
6828 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
6829 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
6832 void
6833 append_tab(struct tab *t)
6835 if (t == NULL)
6836 return;
6838 TAILQ_INSERT_TAIL(&tabs, t, entry);
6839 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
6842 struct tab *
6843 create_new_tab(char *title, struct undo *u, int focus)
6845 struct tab *t, *tt;
6846 int load = 1, id, notfound;
6847 GtkWidget *b, *bb;
6848 WebKitWebHistoryItem *item;
6849 GList *items;
6850 GdkColor color;
6852 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
6854 if (tabless && !TAILQ_EMPTY(&tabs)) {
6855 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
6856 return (NULL);
6859 t = g_malloc0(sizeof *t);
6861 if (title == NULL) {
6862 title = "(untitled)";
6863 load = 0;
6866 t->vbox = gtk_vbox_new(FALSE, 0);
6868 /* label + button for tab */
6869 b = gtk_hbox_new(FALSE, 0);
6870 t->tab_content = b;
6872 #if GTK_CHECK_VERSION(2, 20, 0)
6873 t->spinner = gtk_spinner_new ();
6874 #endif
6875 t->label = gtk_label_new(title);
6876 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
6877 gtk_widget_set_size_request(t->label, 100, 0);
6878 gtk_widget_set_size_request(b, 130, 0);
6880 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
6881 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
6882 #if GTK_CHECK_VERSION(2, 20, 0)
6883 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
6884 #endif
6886 /* toolbar */
6887 t->toolbar = create_toolbar(t);
6888 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
6890 /* browser */
6891 t->browser_win = create_browser(t);
6892 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
6894 /* oops message for user feedback */
6895 t->oops = gtk_entry_new();
6896 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
6897 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
6898 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
6899 gdk_color_parse("red", &color);
6900 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
6901 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
6903 /* command entry */
6904 t->cmd = gtk_entry_new();
6905 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
6906 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
6907 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
6909 /* status bar */
6910 t->statusbar = gtk_entry_new();
6911 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
6912 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
6913 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
6914 gdk_color_parse("black", &color);
6915 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
6916 gdk_color_parse("white", &color);
6917 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
6918 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
6920 /* xtp meaning is normal by default */
6921 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6923 /* set empty favicon */
6924 xt_icon_from_name(t, "text-html");
6926 /* and show it all */
6927 gtk_widget_show_all(b);
6928 gtk_widget_show_all(t->vbox);
6930 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
6931 append_tab(t);
6932 else {
6933 notfound = 1;
6934 id = gtk_notebook_get_current_page(notebook);
6935 TAILQ_FOREACH(tt, &tabs, entry) {
6936 if (tt->tab_id == id) {
6937 notfound = 0;
6938 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
6939 gtk_notebook_insert_page(notebook, t->vbox, b,
6940 id + 1);
6941 recalc_tabs();
6942 break;
6945 if (notfound)
6946 append_tab(t);
6949 #if GTK_CHECK_VERSION(2, 20, 0)
6950 /* turn spinner off if we are a new tab without uri */
6951 if (!load) {
6952 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6953 gtk_widget_hide(t->spinner);
6955 #endif
6956 /* make notebook tabs reorderable */
6957 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
6959 g_object_connect(G_OBJECT(t->cmd),
6960 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
6961 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
6962 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
6963 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
6964 (char *)NULL);
6966 /* reuse wv_button_cb to hide oops */
6967 g_object_connect(G_OBJECT(t->oops),
6968 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6969 (char *)NULL);
6971 g_object_connect(G_OBJECT(t->wv),
6972 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
6973 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6974 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
6975 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
6976 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
6977 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
6978 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
6979 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
6980 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
6981 "signal::event", G_CALLBACK(webview_event_cb), t,
6982 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
6983 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
6984 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6985 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
6986 #endif
6987 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6988 (char *)NULL);
6989 g_signal_connect(t->wv,
6990 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
6991 g_signal_connect(t->wv,
6992 "notify::title", G_CALLBACK(notify_title_cb), t);
6994 /* hijack the unused keys as if we were the browser */
6995 g_object_connect(G_OBJECT(t->toolbar),
6996 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6997 (char *)NULL);
6999 g_signal_connect(G_OBJECT(bb), "button_press_event",
7000 G_CALLBACK(tab_close_cb), t);
7002 /* hide stuff */
7003 hide_cmd(t);
7004 hide_oops(t);
7005 url_set_visibility();
7006 statusbar_set_visibility();
7008 if (focus) {
7009 gtk_notebook_set_current_page(notebook, t->tab_id);
7010 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7011 t->tab_id);
7014 if (load) {
7015 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7016 load_uri(t, title);
7017 } else {
7018 if (show_url == 1)
7019 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7020 else
7021 focus_webview(t);
7024 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7025 /* restore the tab's history */
7026 if (u && u->history) {
7027 items = u->history;
7028 while (items) {
7029 item = items->data;
7030 webkit_web_back_forward_list_add_item(t->bfl, item);
7031 items = g_list_next(items);
7034 item = g_list_nth_data(u->history, u->back);
7035 if (item)
7036 webkit_web_view_go_to_back_forward_item(t->wv, item);
7038 g_list_free(items);
7039 g_list_free(u->history);
7040 } else
7041 webkit_web_back_forward_list_clear(t->bfl);
7043 return (t);
7046 void
7047 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7048 gpointer *udata)
7050 struct tab *t;
7051 const gchar *uri;
7053 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7055 TAILQ_FOREACH(t, &tabs, entry) {
7056 if (t->tab_id == pn) {
7057 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7058 "%d\n", pn);
7060 uri = webkit_web_view_get_title(t->wv);
7061 if (uri == NULL)
7062 uri = XT_NAME;
7063 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7065 hide_cmd(t);
7066 hide_oops(t);
7068 if (t->focus_wv)
7069 focus_webview(t);
7074 void
7075 menuitem_response(struct tab *t)
7077 gtk_notebook_set_current_page(notebook, t->tab_id);
7080 gboolean
7081 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7083 GtkWidget *menu, *menu_items;
7084 GdkEventButton *bevent;
7085 const gchar *uri;
7086 struct tab *ti;
7088 if (event->type == GDK_BUTTON_PRESS) {
7089 bevent = (GdkEventButton *) event;
7090 menu = gtk_menu_new();
7092 TAILQ_FOREACH(ti, &tabs, entry) {
7093 if ((uri = get_uri(ti->wv)) == NULL)
7094 /* XXX make sure there is something to print */
7095 /* XXX add gui pages in here to look purdy */
7096 uri = "(untitled)";
7097 menu_items = gtk_menu_item_new_with_label(uri);
7098 gtk_menu_append(GTK_MENU (menu), menu_items);
7099 gtk_widget_show(menu_items);
7101 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7102 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7103 (gpointer)ti);
7106 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7107 bevent->button, bevent->time);
7109 /* unref object so it'll free itself when popped down */
7110 g_object_ref_sink(menu);
7111 g_object_unref(menu);
7113 return (TRUE /* eat event */);
7116 return (FALSE /* propagate */);
7120 icon_size_map(int icon_size)
7122 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7123 icon_size > GTK_ICON_SIZE_DIALOG)
7124 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7126 return (icon_size);
7129 GtkWidget *
7130 create_button(char *name, char *stockid, int size)
7132 GtkWidget *button, *image;
7133 gchar *rcstring;
7134 int gtk_icon_size;
7135 rcstring = g_strdup_printf(
7136 "style \"%s-style\"\n"
7137 "{\n"
7138 " GtkWidget::focus-padding = 0\n"
7139 " GtkWidget::focus-line-width = 0\n"
7140 " xthickness = 0\n"
7141 " ythickness = 0\n"
7142 "}\n"
7143 "widget \"*.%s\" style \"%s-style\"",name,name,name);
7144 gtk_rc_parse_string(rcstring);
7145 g_free(rcstring);
7146 button = gtk_button_new();
7147 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7148 gtk_icon_size = icon_size_map(size?size:icon_size);
7150 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7151 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7152 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7153 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7154 gtk_widget_set_name(button, name);
7155 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7156 gtk_widget_set_tooltip_text(button, name);
7158 return button;
7161 void
7162 button_set_stockid(GtkWidget *button, char *stockid)
7164 GtkWidget *image;
7165 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7166 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7167 gtk_button_set_image(GTK_BUTTON(button), image);
7170 void
7171 create_canvas(void)
7173 GtkWidget *vbox;
7174 GList *l = NULL;
7175 GdkPixbuf *pb;
7176 char file[PATH_MAX];
7177 int i;
7179 vbox = gtk_vbox_new(FALSE, 0);
7180 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7181 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7182 gtk_notebook_set_tab_hborder(notebook, 0);
7183 gtk_notebook_set_tab_vborder(notebook, 0);
7184 gtk_notebook_set_scrollable(notebook, TRUE);
7185 notebook_tab_set_visibility(notebook);
7186 gtk_notebook_set_show_border(notebook, FALSE);
7187 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7189 abtn = gtk_button_new();
7190 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7191 gtk_widget_set_size_request(arrow, -1, -1);
7192 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7193 gtk_widget_set_size_request(abtn, -1, 20);
7194 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7196 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7197 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7198 gtk_widget_set_size_request(vbox, -1, -1);
7200 g_object_connect(G_OBJECT(notebook),
7201 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7202 (char *)NULL);
7203 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7204 G_CALLBACK(arrow_cb), NULL);
7206 main_window = create_window();
7207 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7208 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7210 /* icons */
7211 for (i = 0; i < LENGTH(icons); i++) {
7212 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7213 pb = gdk_pixbuf_new_from_file(file, NULL);
7214 l = g_list_append(l, pb);
7216 gtk_window_set_default_icon_list(l);
7218 gtk_widget_show_all(abtn);
7219 gtk_widget_show_all(main_window);
7222 void
7223 set_hook(void **hook, char *name)
7225 if (hook == NULL)
7226 errx(1, "set_hook");
7228 if (*hook == NULL) {
7229 *hook = dlsym(RTLD_NEXT, name);
7230 if (*hook == NULL)
7231 errx(1, "can't hook %s", name);
7235 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7236 gboolean
7237 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7239 g_return_val_if_fail(cookie1, FALSE);
7240 g_return_val_if_fail(cookie2, FALSE);
7242 return (!strcmp (cookie1->name, cookie2->name) &&
7243 !strcmp (cookie1->value, cookie2->value) &&
7244 !strcmp (cookie1->path, cookie2->path) &&
7245 !strcmp (cookie1->domain, cookie2->domain));
7248 void
7249 transfer_cookies(void)
7251 GSList *cf;
7252 SoupCookie *sc, *pc;
7254 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7256 for (;cf; cf = cf->next) {
7257 pc = cf->data;
7258 sc = soup_cookie_copy(pc);
7259 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7262 soup_cookies_free(cf);
7265 void
7266 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7268 GSList *cf;
7269 SoupCookie *ci;
7271 print_cookie("soup_cookie_jar_delete_cookie", c);
7273 if (cookies_enabled == 0)
7274 return;
7276 if (jar == NULL || c == NULL)
7277 return;
7279 /* find and remove from persistent jar */
7280 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7282 for (;cf; cf = cf->next) {
7283 ci = cf->data;
7284 if (soup_cookie_equal(ci, c)) {
7285 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7286 break;
7290 soup_cookies_free(cf);
7292 /* delete from session jar */
7293 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7296 void
7297 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7299 struct domain *d = NULL;
7300 SoupCookie *c;
7301 FILE *r_cookie_f;
7303 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7304 jar, p_cookiejar, s_cookiejar);
7306 if (cookies_enabled == 0)
7307 return;
7309 /* see if we are up and running */
7310 if (p_cookiejar == NULL) {
7311 _soup_cookie_jar_add_cookie(jar, cookie);
7312 return;
7314 /* disallow p_cookiejar adds, shouldn't happen */
7315 if (jar == p_cookiejar)
7316 return;
7318 if (enable_cookie_whitelist &&
7319 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7320 blocked_cookies++;
7321 DNPRINTF(XT_D_COOKIE,
7322 "soup_cookie_jar_add_cookie: reject %s\n",
7323 cookie->domain);
7324 if (save_rejected_cookies) {
7325 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7326 show_oops_s("can't open reject cookie file");
7327 return;
7329 fseek(r_cookie_f, 0, SEEK_END);
7330 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7331 cookie->http_only ? "#HttpOnly_" : "",
7332 cookie->domain,
7333 *cookie->domain == '.' ? "TRUE" : "FALSE",
7334 cookie->path,
7335 cookie->secure ? "TRUE" : "FALSE",
7336 cookie->expires ?
7337 (gulong)soup_date_to_time_t(cookie->expires) :
7339 cookie->name,
7340 cookie->value);
7341 fflush(r_cookie_f);
7342 fclose(r_cookie_f);
7344 if (!allow_volatile_cookies)
7345 return;
7348 if (cookie->expires == NULL && session_timeout) {
7349 soup_cookie_set_expires(cookie,
7350 soup_date_new_from_now(session_timeout));
7351 print_cookie("modified add cookie", cookie);
7354 /* see if we are white listed for persistence */
7355 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7356 /* add to persistent jar */
7357 c = soup_cookie_copy(cookie);
7358 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7359 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7362 /* add to session jar */
7363 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7364 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7367 void
7368 setup_cookies(void)
7370 char file[PATH_MAX];
7372 set_hook((void *)&_soup_cookie_jar_add_cookie,
7373 "soup_cookie_jar_add_cookie");
7374 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7375 "soup_cookie_jar_delete_cookie");
7377 if (cookies_enabled == 0)
7378 return;
7381 * the following code is intricate due to overriding several libsoup
7382 * functions.
7383 * do not alter order of these operations.
7386 /* rejected cookies */
7387 if (save_rejected_cookies)
7388 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7390 /* persistent cookies */
7391 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7392 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7394 /* session cookies */
7395 s_cookiejar = soup_cookie_jar_new();
7396 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7397 cookie_policy, (void *)NULL);
7398 transfer_cookies();
7400 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7403 void
7404 setup_proxy(char *uri)
7406 if (proxy_uri) {
7407 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7408 soup_uri_free(proxy_uri);
7409 proxy_uri = NULL;
7411 if (http_proxy) {
7412 if (http_proxy != uri) {
7413 g_free(http_proxy);
7414 http_proxy = NULL;
7418 if (uri) {
7419 http_proxy = g_strdup(uri);
7420 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7421 proxy_uri = soup_uri_new(http_proxy);
7422 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7427 send_url_to_socket(char *url)
7429 int s, len, rv = -1;
7430 struct sockaddr_un sa;
7432 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7433 warnx("send_url_to_socket: socket");
7434 return (-1);
7437 sa.sun_family = AF_UNIX;
7438 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7439 work_dir, XT_SOCKET_FILE);
7440 len = SUN_LEN(&sa);
7442 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7443 warnx("send_url_to_socket: connect");
7444 goto done;
7447 if (send(s, url, strlen(url) + 1, 0) == -1) {
7448 warnx("send_url_to_socket: send");
7449 goto done;
7451 done:
7452 close(s);
7453 return (rv);
7456 void
7457 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7459 int s, n;
7460 char str[XT_MAX_URL_LENGTH];
7461 socklen_t t = sizeof(struct sockaddr_un);
7462 struct sockaddr_un sa;
7463 struct passwd *p;
7464 uid_t uid;
7465 gid_t gid;
7467 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7468 warn("socket_watcher: accept");
7469 return;
7472 if (getpeereid(s, &uid, &gid) == -1) {
7473 warn("socket_watcher: getpeereid");
7474 return;
7476 if (uid != getuid() || gid != getgid()) {
7477 warnx("socket_watcher: unauthorized user");
7478 return;
7481 p = getpwuid(uid);
7482 if (p == NULL) {
7483 warnx("socket_watcher: not a valid user");
7484 return;
7487 n = recv(s, str, sizeof(str), 0);
7488 if (n <= 0)
7489 return;
7491 create_new_tab(str, NULL, 1);
7495 is_running(void)
7497 int s, len, rv = 1;
7498 struct sockaddr_un sa;
7500 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7501 warn("is_running: socket");
7502 return (-1);
7505 sa.sun_family = AF_UNIX;
7506 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7507 work_dir, XT_SOCKET_FILE);
7508 len = SUN_LEN(&sa);
7510 /* connect to see if there is a listener */
7511 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7512 rv = 0; /* not running */
7513 else
7514 rv = 1; /* already running */
7516 close(s);
7518 return (rv);
7522 build_socket(void)
7524 int s, len;
7525 struct sockaddr_un sa;
7527 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7528 warn("build_socket: socket");
7529 return (-1);
7532 sa.sun_family = AF_UNIX;
7533 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7534 work_dir, XT_SOCKET_FILE);
7535 len = SUN_LEN(&sa);
7537 /* connect to see if there is a listener */
7538 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7539 /* no listener so we will */
7540 unlink(sa.sun_path);
7542 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7543 warn("build_socket: bind");
7544 goto done;
7547 if (listen(s, 1) == -1) {
7548 warn("build_socket: listen");
7549 goto done;
7552 return (s);
7555 done:
7556 close(s);
7557 return (-1);
7560 static gboolean
7561 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7562 GtkTreeIter *iter, struct tab *t)
7564 gchar *value;
7566 gtk_tree_model_get(model, iter, 0, &value, -1);
7567 load_uri(t, value);
7569 return (FALSE);
7572 void
7573 completion_add_uri(const gchar *uri)
7575 GtkTreeIter iter;
7577 /* add uri to list_store */
7578 gtk_list_store_append(completion_model, &iter);
7579 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
7582 gboolean
7583 completion_match(GtkEntryCompletion *completion, const gchar *key,
7584 GtkTreeIter *iter, gpointer user_data)
7586 gchar *value, *voffset;
7587 size_t len;
7588 gboolean match = FALSE;
7590 len = strlen(key);
7592 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
7593 -1);
7595 if (value == NULL)
7596 return FALSE;
7598 if (!strncmp(key, value, len))
7599 match = TRUE;
7600 else {
7601 voffset = strstr(value, "/") + 2;
7602 if (!strncmp(key, voffset, len))
7603 match = TRUE;
7604 else if (g_str_has_prefix(voffset, "www.")) {
7605 voffset = voffset + strlen("www.");
7606 if (!strncmp(key, voffset, len))
7607 match = TRUE;
7611 g_free(value);
7612 return (match);
7615 void
7616 completion_add(struct tab *t)
7618 /* enable completion for tab */
7619 t->completion = gtk_entry_completion_new();
7620 gtk_entry_completion_set_text_column(t->completion, 0);
7621 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
7622 gtk_entry_completion_set_model(t->completion,
7623 GTK_TREE_MODEL(completion_model));
7624 gtk_entry_completion_set_match_func(t->completion, completion_match,
7625 NULL, NULL);
7626 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
7627 g_signal_connect(G_OBJECT (t->completion), "match-selected",
7628 G_CALLBACK(completion_select_cb), t);
7631 void
7632 usage(void)
7634 fprintf(stderr,
7635 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
7636 exit(0);
7640 main(int argc, char *argv[])
7642 struct stat sb;
7643 int c, s, optn = 0, focus = 1;
7644 char conf[PATH_MAX] = { '\0' };
7645 char file[PATH_MAX];
7646 char *env_proxy = NULL;
7647 FILE *f = NULL;
7648 struct karg a;
7649 struct sigaction sact;
7651 start_argv = argv;
7653 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
7655 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
7656 switch (c) {
7657 case 'S':
7658 show_url = 0;
7659 break;
7660 case 'T':
7661 show_tabs = 0;
7662 break;
7663 case 'V':
7664 errx(0 , "Version: %s", version);
7665 break;
7666 case 'f':
7667 strlcpy(conf, optarg, sizeof(conf));
7668 break;
7669 case 's':
7670 strlcpy(named_session, optarg, sizeof(named_session));
7671 break;
7672 case 't':
7673 tabless = 1;
7674 break;
7675 case 'n':
7676 optn = 1;
7677 break;
7678 default:
7679 usage();
7680 /* NOTREACHED */
7683 argc -= optind;
7684 argv += optind;
7686 RB_INIT(&hl);
7687 RB_INIT(&js_wl);
7688 RB_INIT(&downloads);
7690 TAILQ_INIT(&tabs);
7691 TAILQ_INIT(&mtl);
7692 TAILQ_INIT(&aliases);
7693 TAILQ_INIT(&undos);
7694 TAILQ_INIT(&kbl);
7696 init_keybindings();
7698 gnutls_global_init();
7700 /* generate session keys for xtp pages */
7701 generate_xtp_session_key(&dl_session_key);
7702 generate_xtp_session_key(&hl_session_key);
7703 generate_xtp_session_key(&cl_session_key);
7704 generate_xtp_session_key(&fl_session_key);
7706 /* prepare gtk */
7707 gtk_init(&argc, &argv);
7708 if (!g_thread_supported())
7709 g_thread_init(NULL);
7711 /* signals */
7712 bzero(&sact, sizeof(sact));
7713 sigemptyset(&sact.sa_mask);
7714 sact.sa_handler = sigchild;
7715 sact.sa_flags = SA_NOCLDSTOP;
7716 sigaction(SIGCHLD, &sact, NULL);
7718 /* set download dir */
7719 pwd = getpwuid(getuid());
7720 if (pwd == NULL)
7721 errx(1, "invalid user %d", getuid());
7722 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
7724 /* set default string settings */
7725 home = g_strdup("http://www.peereboom.us");
7726 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
7727 resource_dir = g_strdup("/usr/local/share/xxxterm/");
7728 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
7730 /* read config file */
7731 if (strlen(conf) == 0)
7732 snprintf(conf, sizeof conf, "%s/.%s",
7733 pwd->pw_dir, XT_CONF_FILE);
7734 config_parse(conf, 0);
7736 /* working directory */
7737 if (strlen(work_dir) == 0)
7738 snprintf(work_dir, sizeof work_dir, "%s/%s",
7739 pwd->pw_dir, XT_DIR);
7740 if (stat(work_dir, &sb)) {
7741 if (mkdir(work_dir, S_IRWXU) == -1)
7742 err(1, "mkdir work_dir");
7743 if (stat(work_dir, &sb))
7744 err(1, "stat work_dir");
7746 if (S_ISDIR(sb.st_mode) == 0)
7747 errx(1, "%s not a dir", work_dir);
7748 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7749 warnx("fixing invalid permissions on %s", work_dir);
7750 if (chmod(work_dir, S_IRWXU) == -1)
7751 err(1, "chmod");
7754 /* icon cache dir */
7755 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
7756 if (stat(cache_dir, &sb)) {
7757 if (mkdir(cache_dir, S_IRWXU) == -1)
7758 err(1, "mkdir cache_dir");
7759 if (stat(cache_dir, &sb))
7760 err(1, "stat cache_dir");
7762 if (S_ISDIR(sb.st_mode) == 0)
7763 errx(1, "%s not a dir", cache_dir);
7764 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7765 warnx("fixing invalid permissions on %s", cache_dir);
7766 if (chmod(cache_dir, S_IRWXU) == -1)
7767 err(1, "chmod");
7770 /* certs dir */
7771 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
7772 if (stat(certs_dir, &sb)) {
7773 if (mkdir(certs_dir, S_IRWXU) == -1)
7774 err(1, "mkdir certs_dir");
7775 if (stat(certs_dir, &sb))
7776 err(1, "stat certs_dir");
7778 if (S_ISDIR(sb.st_mode) == 0)
7779 errx(1, "%s not a dir", certs_dir);
7780 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7781 warnx("fixing invalid permissions on %s", certs_dir);
7782 if (chmod(certs_dir, S_IRWXU) == -1)
7783 err(1, "chmod");
7786 /* sessions dir */
7787 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
7788 work_dir, XT_SESSIONS_DIR);
7789 if (stat(sessions_dir, &sb)) {
7790 if (mkdir(sessions_dir, S_IRWXU) == -1)
7791 err(1, "mkdir sessions_dir");
7792 if (stat(sessions_dir, &sb))
7793 err(1, "stat sessions_dir");
7795 if (S_ISDIR(sb.st_mode) == 0)
7796 errx(1, "%s not a dir", sessions_dir);
7797 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7798 warnx("fixing invalid permissions on %s", sessions_dir);
7799 if (chmod(sessions_dir, S_IRWXU) == -1)
7800 err(1, "chmod");
7802 /* runtime settings that can override config file */
7803 if (runtime_settings[0] != '\0')
7804 config_parse(runtime_settings, 1);
7806 /* download dir */
7807 if (!strcmp(download_dir, pwd->pw_dir))
7808 strlcat(download_dir, "/downloads", sizeof download_dir);
7809 if (stat(download_dir, &sb)) {
7810 if (mkdir(download_dir, S_IRWXU) == -1)
7811 err(1, "mkdir download_dir");
7812 if (stat(download_dir, &sb))
7813 err(1, "stat download_dir");
7815 if (S_ISDIR(sb.st_mode) == 0)
7816 errx(1, "%s not a dir", download_dir);
7817 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7818 warnx("fixing invalid permissions on %s", download_dir);
7819 if (chmod(download_dir, S_IRWXU) == -1)
7820 err(1, "chmod");
7823 /* favorites file */
7824 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
7825 if (stat(file, &sb)) {
7826 warnx("favorites file doesn't exist, creating it");
7827 if ((f = fopen(file, "w")) == NULL)
7828 err(1, "favorites");
7829 fclose(f);
7832 /* cookies */
7833 session = webkit_get_default_session();
7834 setup_cookies();
7836 /* certs */
7837 if (ssl_ca_file) {
7838 if (stat(ssl_ca_file, &sb)) {
7839 warn("no CA file: %s", ssl_ca_file);
7840 g_free(ssl_ca_file);
7841 ssl_ca_file = NULL;
7842 } else
7843 g_object_set(session,
7844 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
7845 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
7846 (void *)NULL);
7849 /* proxy */
7850 env_proxy = getenv("http_proxy");
7851 if (env_proxy)
7852 setup_proxy(env_proxy);
7853 else
7854 setup_proxy(http_proxy);
7856 /* see if there is already an xxxterm running */
7857 if (single_instance && is_running()) {
7858 optn = 1;
7859 warnx("already running");
7862 if (optn) {
7863 while (argc) {
7864 send_url_to_socket(argv[0]);
7866 argc--;
7867 argv++;
7869 exit(0);
7872 /* uri completion */
7873 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
7875 /* go graphical */
7876 create_canvas();
7878 if (save_global_history)
7879 restore_global_history();
7881 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
7882 restore_saved_tabs();
7883 else {
7884 a.s = named_session;
7885 a.i = XT_SES_DONOTHING;
7886 open_tabs(NULL, &a);
7889 while (argc) {
7890 create_new_tab(argv[0], NULL, focus);
7891 focus = 0;
7893 argc--;
7894 argv++;
7897 if (TAILQ_EMPTY(&tabs))
7898 create_new_tab(home, NULL, 1);
7900 if (enable_socket)
7901 if ((s = build_socket()) != -1)
7902 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
7904 gtk_main();
7906 gnutls_global_deinit();
7908 return (0);