dont always run download first
[xxxterm.git] / xxxterm.c
blob9851e012d8d14d1b7c094067e532f59534c4a904
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 *gohome;
174 GtkWidget *js_toggle;
175 GtkEntryCompletion *completion;
176 guint tab_id;
177 WebKitWebView *wv;
179 WebKitWebHistoryItem *item;
180 WebKitWebBackForwardList *bfl;
182 /* favicon */
183 WebKitNetworkRequest *icon_request;
184 WebKitDownload *icon_download;
185 GdkPixbuf *icon_pixbuf;
186 gchar *icon_dest_uri;
188 /* adjustments for browser */
189 GtkScrollbar *sb_h;
190 GtkScrollbar *sb_v;
191 GtkAdjustment *adjust_h;
192 GtkAdjustment *adjust_v;
194 /* flags */
195 int focus_wv;
196 int ctrl_click;
197 gchar *status;
198 int xtp_meaning; /* identifies dls/favorites */
200 /* hints */
201 int hints_on;
202 int hint_mode;
203 #define XT_HINT_NONE (0)
204 #define XT_HINT_NUMERICAL (1)
205 #define XT_HINT_ALPHANUM (2)
206 char hint_buf[128];
207 char hint_num[128];
209 /* custom stylesheet */
210 int styled;
211 char *stylesheet;
213 /* search */
214 char *search_text;
215 int search_forward;
217 /* settings */
218 WebKitWebSettings *settings;
219 int font_size;
220 gchar *user_agent;
222 TAILQ_HEAD(tab_list, tab);
224 struct history {
225 RB_ENTRY(history) entry;
226 const gchar *uri;
227 const gchar *title;
229 RB_HEAD(history_list, history);
231 struct download {
232 RB_ENTRY(download) entry;
233 int id;
234 WebKitDownload *download;
235 struct tab *tab;
237 RB_HEAD(download_list, download);
239 struct domain {
240 RB_ENTRY(domain) entry;
241 gchar *d;
242 int handy; /* app use */
244 RB_HEAD(domain_list, domain);
246 struct undo {
247 TAILQ_ENTRY(undo) entry;
248 gchar *uri;
249 GList *history;
250 int back; /* Keeps track of how many back
251 * history items there are. */
253 TAILQ_HEAD(undo_tailq, undo);
255 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
256 int next_download_id = 1;
258 struct karg {
259 int i;
260 char *s;
263 /* defines */
264 #define XT_NAME ("XXXTerm")
265 #define XT_DIR (".xxxterm")
266 #define XT_CACHE_DIR ("cache")
267 #define XT_CERT_DIR ("certs/")
268 #define XT_SESSIONS_DIR ("sessions/")
269 #define XT_CONF_FILE ("xxxterm.conf")
270 #define XT_FAVS_FILE ("favorites")
271 #define XT_SAVED_TABS_FILE ("main_session")
272 #define XT_RESTART_TABS_FILE ("restart_tabs")
273 #define XT_SOCKET_FILE ("socket")
274 #define XT_HISTORY_FILE ("history")
275 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
276 #define XT_CB_HANDLED (TRUE)
277 #define XT_CB_PASSTHROUGH (FALSE)
278 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
279 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
280 #define XT_DLMAN_REFRESH "10"
281 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
282 "td {overflow: hidden;" \
283 " padding: 2px 2px 2px 2px;" \
284 " border: 1px solid black}\n" \
285 "tr:hover {background: #ffff99 ;}\n" \
286 "th {background-color: #cccccc;" \
287 " border: 1px solid black}" \
288 "table {border-spacing: 0; " \
289 " width: 90%%;" \
290 " border: 1px black solid;}\n" \
291 ".progress-outer{" \
292 " border: 1px solid black;" \
293 " height: 8px;" \
294 " width: 90%%;}" \
295 ".progress-inner{" \
296 " float: left;" \
297 " height: 8px;" \
298 " background: green;}" \
299 ".dlstatus{" \
300 " font-size: small;" \
301 " text-align: center;}" \
302 "</style>\n\n"
303 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
304 #define XT_MAX_UNDO_CLOSE_TAB (32)
305 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
306 #define XT_PRINT_EXTRA_MARGIN 10
308 /* colors */
309 #define XT_COLOR_RED "#cc0000"
310 #define XT_COLOR_YELLOW "#ffff66"
311 #define XT_COLOR_BLUE "lightblue"
312 #define XT_COLOR_GREEN "#99ff66"
313 #define XT_COLOR_WHITE "white"
314 #define XT_COLOR_BLACK "black"
317 * xxxterm "protocol" (xtp)
318 * We use this for managing stuff like downloads and favorites. They
319 * make magical HTML pages in memory which have xxxt:// links in order
320 * to communicate with xxxterm's internals. These links take the format:
321 * xxxt://class/session_key/action/arg
323 * Don't begin xtp class/actions as 0. atoi returns that on error.
325 * Typically we have not put addition of items in this framework, as
326 * adding items is either done via an ex-command or via a keybinding instead.
329 #define XT_XTP_STR "xxxt://"
331 /* XTP classes (xxxt://<class>) */
332 #define XT_XTP_DL 1 /* downloads */
333 #define XT_XTP_HL 2 /* history */
334 #define XT_XTP_CL 3 /* cookies */
335 #define XT_XTP_FL 4 /* favorites */
337 /* XTP download actions */
338 #define XT_XTP_DL_LIST 1
339 #define XT_XTP_DL_CANCEL 2
340 #define XT_XTP_DL_REMOVE 3
342 /* XTP history actions */
343 #define XT_XTP_HL_LIST 1
344 #define XT_XTP_HL_REMOVE 2
346 /* XTP cookie actions */
347 #define XT_XTP_CL_LIST 1
348 #define XT_XTP_CL_REMOVE 2
350 /* XTP cookie actions */
351 #define XT_XTP_FL_LIST 1
352 #define XT_XTP_FL_REMOVE 2
354 /* xtp tab meanings - identifies which tabs have xtp pages in */
355 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
356 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
357 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
358 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
359 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
361 /* actions */
362 #define XT_MOVE_INVALID (0)
363 #define XT_MOVE_DOWN (1)
364 #define XT_MOVE_UP (2)
365 #define XT_MOVE_BOTTOM (3)
366 #define XT_MOVE_TOP (4)
367 #define XT_MOVE_PAGEDOWN (5)
368 #define XT_MOVE_PAGEUP (6)
369 #define XT_MOVE_HALFDOWN (7)
370 #define XT_MOVE_HALFUP (8)
371 #define XT_MOVE_LEFT (9)
372 #define XT_MOVE_FARLEFT (10)
373 #define XT_MOVE_RIGHT (11)
374 #define XT_MOVE_FARRIGHT (12)
376 #define XT_TAB_LAST (-4)
377 #define XT_TAB_FIRST (-3)
378 #define XT_TAB_PREV (-2)
379 #define XT_TAB_NEXT (-1)
380 #define XT_TAB_INVALID (0)
381 #define XT_TAB_NEW (1)
382 #define XT_TAB_DELETE (2)
383 #define XT_TAB_DELQUIT (3)
384 #define XT_TAB_OPEN (4)
385 #define XT_TAB_UNDO_CLOSE (5)
386 #define XT_TAB_SHOW (6)
387 #define XT_TAB_HIDE (7)
389 #define XT_NAV_INVALID (0)
390 #define XT_NAV_BACK (1)
391 #define XT_NAV_FORWARD (2)
392 #define XT_NAV_RELOAD (3)
393 #define XT_NAV_RELOAD_CACHE (4)
395 #define XT_FOCUS_INVALID (0)
396 #define XT_FOCUS_URI (1)
397 #define XT_FOCUS_SEARCH (2)
399 #define XT_SEARCH_INVALID (0)
400 #define XT_SEARCH_NEXT (1)
401 #define XT_SEARCH_PREV (2)
403 #define XT_PASTE_CURRENT_TAB (0)
404 #define XT_PASTE_NEW_TAB (1)
406 #define XT_FONT_SET (0)
408 #define XT_URL_SHOW (1)
409 #define XT_URL_HIDE (2)
411 #define XT_STATUSBAR_SHOW (1)
412 #define XT_STATUSBAR_HIDE (2)
414 #define XT_WL_TOGGLE (1<<0)
415 #define XT_WL_ENABLE (1<<1)
416 #define XT_WL_DISABLE (1<<2)
417 #define XT_WL_FQDN (1<<3) /* default */
418 #define XT_WL_TOPLEVEL (1<<4)
420 #define XT_CMD_OPEN (0)
421 #define XT_CMD_OPEN_CURRENT (1)
422 #define XT_CMD_TABNEW (2)
423 #define XT_CMD_TABNEW_CURRENT (3)
425 #define XT_STATUS_NOTHING (0)
426 #define XT_STATUS_LINK (1)
427 #define XT_STATUS_URI (2)
428 #define XT_STATUS_LOADING (3)
430 #define XT_SES_DONOTHING (0)
431 #define XT_SES_CLOSETABS (1)
433 #define XT_BM_NORMAL (0)
434 #define XT_BM_WHITELIST (1)
435 #define XT_BM_KIOSK (2)
437 /* mime types */
438 struct mime_type {
439 char *mt_type;
440 char *mt_action;
441 int mt_default;
442 int mt_download;
443 TAILQ_ENTRY(mime_type) entry;
445 TAILQ_HEAD(mime_type_list, mime_type);
447 /* uri aliases */
448 struct alias {
449 char *a_name;
450 char *a_uri;
451 TAILQ_ENTRY(alias) entry;
453 TAILQ_HEAD(alias_list, alias);
455 /* settings that require restart */
456 int tabless = 0; /* allow only 1 tab */
457 int enable_socket = 0;
458 int single_instance = 0; /* only allow one xxxterm to run */
459 int fancy_bar = 1; /* fancy toolbar */
460 int browser_mode = XT_BM_NORMAL;
462 /* runtime settings */
463 int show_tabs = 1; /* show tabs on notebook */
464 int show_url = 1; /* show url toolbar on notebook */
465 int show_statusbar = 0; /* vimperator style status bar */
466 int ctrl_click_focus = 0; /* ctrl click gets focus */
467 int cookies_enabled = 1; /* enable cookies */
468 int read_only_cookies = 0; /* enable to not write cookies */
469 int enable_scripts = 1;
470 int enable_plugins = 0;
471 int default_font_size = 12;
472 gfloat default_zoom_level = 1.0;
473 int window_height = 768;
474 int window_width = 1024;
475 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
476 unsigned refresh_interval = 10; /* download refresh interval */
477 int enable_cookie_whitelist = 0;
478 int enable_js_whitelist = 0;
479 time_t session_timeout = 3600; /* cookie session timeout */
480 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
481 char *ssl_ca_file = NULL;
482 char *resource_dir = NULL;
483 gboolean ssl_strict_certs = FALSE;
484 int append_next = 1; /* append tab after current tab */
485 char *home = NULL;
486 char *search_string = NULL;
487 char *http_proxy = NULL;
488 char download_dir[PATH_MAX];
489 char runtime_settings[PATH_MAX]; /* override of settings */
490 int allow_volatile_cookies = 0;
491 int save_global_history = 0; /* save global history to disk */
492 char *user_agent = NULL;
493 int save_rejected_cookies = 0;
494 time_t session_autosave = 0;
495 int guess_search = 0;
496 int dns_prefetch = FALSE;
498 struct settings;
499 struct key_binding;
500 int set_download_dir(struct settings *, char *);
501 int set_work_dir(struct settings *, char *);
502 int set_runtime_dir(struct settings *, char *);
503 int set_browser_mode(struct settings *, char *);
504 int set_cookie_policy(struct settings *, char *);
505 int add_alias(struct settings *, char *);
506 int add_mime_type(struct settings *, char *);
507 int add_cookie_wl(struct settings *, char *);
508 int add_js_wl(struct settings *, char *);
509 int add_kb(struct settings *, char *);
510 void button_set_stockid(GtkWidget *, char *);
511 GtkWidget * create_button(char *, char *, int);
513 char *get_browser_mode(struct settings *);
514 char *get_cookie_policy(struct settings *);
516 char *get_download_dir(struct settings *);
517 char *get_work_dir(struct settings *);
518 char *get_runtime_dir(struct settings *);
520 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
521 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
522 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
523 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
524 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
526 struct special {
527 int (*set)(struct settings *, char *);
528 char *(*get)(struct settings *);
529 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
532 struct special s_browser_mode = {
533 set_browser_mode,
534 get_browser_mode,
535 NULL
538 struct special s_cookie = {
539 set_cookie_policy,
540 get_cookie_policy,
541 NULL
544 struct special s_alias = {
545 add_alias,
546 NULL,
547 walk_alias
550 struct special s_mime = {
551 add_mime_type,
552 NULL,
553 walk_mime_type
556 struct special s_js = {
557 add_js_wl,
558 NULL,
559 walk_js_wl
562 struct special s_kb = {
563 add_kb,
564 NULL,
565 walk_kb
568 struct special s_cookie_wl = {
569 add_cookie_wl,
570 NULL,
571 walk_cookie_wl
574 struct special s_download_dir = {
575 set_download_dir,
576 get_download_dir,
577 NULL
580 struct special s_work_dir = {
581 set_work_dir,
582 get_work_dir,
583 NULL
586 struct settings {
587 char *name;
588 int type;
589 #define XT_S_INVALID (0)
590 #define XT_S_INT (1)
591 #define XT_S_STR (2)
592 #define XT_S_FLOAT (3)
593 uint32_t flags;
594 #define XT_SF_RESTART (1<<0)
595 #define XT_SF_RUNTIME (1<<1)
596 int *ival;
597 char **sval;
598 struct special *s;
599 gfloat *fval;
600 } rs[] = {
601 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
602 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
603 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
604 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
605 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
606 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
607 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
608 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
609 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
610 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
611 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
612 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
613 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
614 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
615 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
616 { "home", XT_S_STR, 0, NULL, &home, NULL },
617 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
618 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
619 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
620 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
621 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
622 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
623 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
624 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
625 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
626 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
627 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
628 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
629 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
630 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
631 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
632 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
633 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
634 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
635 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
636 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
637 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
639 /* runtime settings */
640 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
641 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
642 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
643 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
644 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
647 int about(struct tab *, struct karg *);
648 int blank(struct tab *, struct karg *);
649 int cookie_show_wl(struct tab *, struct karg *);
650 int js_show_wl(struct tab *, struct karg *);
651 int help(struct tab *, struct karg *);
652 int set(struct tab *, struct karg *);
653 int stats(struct tab *, struct karg *);
654 int xtp_page_cl(struct tab *, struct karg *);
655 int xtp_page_dl(struct tab *, struct karg *);
656 int xtp_page_fl(struct tab *, struct karg *);
657 int xtp_page_hl(struct tab *, struct karg *);
659 #define XT_URI_ABOUT ("about:")
660 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
661 #define XT_URI_ABOUT_ABOUT ("about")
662 #define XT_URI_ABOUT_BLANK ("blank")
663 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
664 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
665 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
666 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
667 #define XT_URI_ABOUT_FAVORITES ("favorites")
668 #define XT_URI_ABOUT_HELP ("help")
669 #define XT_URI_ABOUT_HISTORY ("history")
670 #define XT_URI_ABOUT_JSWL ("jswl")
671 #define XT_URI_ABOUT_SET ("set")
672 #define XT_URI_ABOUT_STATS ("stats")
674 struct about_type {
675 char *name;
676 int (*func)(struct tab *, struct karg *);
677 } about_list[] = {
678 { XT_URI_ABOUT_ABOUT, about },
679 { XT_URI_ABOUT_BLANK, blank },
680 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
681 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
682 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
683 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
684 { XT_URI_ABOUT_HELP, help },
685 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
686 { XT_URI_ABOUT_JSWL, js_show_wl },
687 { XT_URI_ABOUT_SET, set },
688 { XT_URI_ABOUT_STATS, stats },
691 /* globals */
692 extern char *__progname;
693 char **start_argv;
694 struct passwd *pwd;
695 GtkWidget *main_window;
696 GtkNotebook *notebook;
697 GtkWidget *arrow, *abtn;
698 struct tab_list tabs;
699 struct history_list hl;
700 struct download_list downloads;
701 struct domain_list c_wl;
702 struct domain_list js_wl;
703 struct undo_tailq undos;
704 struct keybinding_list kbl;
705 int undo_count;
706 int updating_dl_tabs = 0;
707 int updating_hl_tabs = 0;
708 int updating_cl_tabs = 0;
709 int updating_fl_tabs = 0;
710 char *global_search;
711 uint64_t blocked_cookies = 0;
712 char named_session[PATH_MAX];
713 void update_favicon(struct tab *);
714 int icon_size_map(int);
716 GtkListStore *completion_model;
717 void completion_add(struct tab *);
718 void completion_add_uri(const gchar *);
720 void
721 sigchild(int sig)
723 int saved_errno, status;
724 pid_t pid;
726 saved_errno = errno;
728 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
729 if (pid == -1) {
730 if (errno == EINTR)
731 continue;
732 if (errno != ECHILD) {
734 clog_warn("sigchild: waitpid:");
737 break;
740 if (WIFEXITED(status)) {
741 if (WEXITSTATUS(status) != 0) {
743 clog_warnx("sigchild: child exit status: %d",
744 WEXITSTATUS(status));
747 } else {
749 clog_warnx("sigchild: child is terminated abnormally");
754 errno = saved_errno;
758 is_g_object_setting(GObject *o, char *str)
760 guint n_props = 0, i;
761 GParamSpec **proplist;
763 if (! G_IS_OBJECT(o))
764 return (0);
766 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
767 &n_props);
769 for (i=0; i < n_props; i++) {
770 if (! strcmp(proplist[i]->name, str))
771 return (1);
773 return (0);
776 void
777 load_webkit_string(struct tab *t, const char *str, gchar *title)
779 gchar *uri;
780 char file[PATH_MAX];
781 GdkPixbuf *pb;
783 /* we set this to indicate we want to manually do navaction */
784 if (t->bfl)
785 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
786 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
788 if (title) {
789 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
790 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
791 g_free(uri);
793 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
794 pb = gdk_pixbuf_new_from_file(file, NULL);
795 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
796 GTK_ENTRY_ICON_PRIMARY, pb);
797 gdk_pixbuf_unref(pb);
801 void
802 set_status(struct tab *t, gchar *s, int status)
804 gchar *type = NULL;
806 if (s == NULL)
807 return;
809 switch (status) {
810 case XT_STATUS_LOADING:
811 type = g_strdup_printf("Loading: %s", s);
812 s = type;
813 break;
814 case XT_STATUS_LINK:
815 type = g_strdup_printf("Link: %s", s);
816 if (!t->status)
817 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
818 s = type;
819 break;
820 case XT_STATUS_URI:
821 type = g_strdup_printf("%s", s);
822 if (!t->status) {
823 t->status = g_strdup(type);
825 s = type;
826 if (!t->status)
827 t->status = g_strdup(s);
828 break;
829 case XT_STATUS_NOTHING:
830 /* FALL THROUGH */
831 default:
832 break;
834 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
835 if (type)
836 g_free(type);
839 void
840 hide_oops(struct tab *t)
842 gtk_widget_hide(t->oops);
845 void
846 hide_cmd(struct tab *t)
848 gtk_widget_hide(t->cmd);
851 void
852 show_cmd(struct tab *t)
854 gtk_widget_hide(t->oops);
855 gtk_widget_show(t->cmd);
858 void
859 show_oops(struct tab *t, const char *fmt, ...)
861 va_list ap;
862 char *msg;
864 if (fmt == NULL)
865 return;
867 va_start(ap, fmt);
868 if (vasprintf(&msg, fmt, ap) == -1)
869 errx(1, "show_oops failed");
870 va_end(ap);
872 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
873 gtk_widget_hide(t->cmd);
874 gtk_widget_show(t->oops);
877 /* XXX collapse with show_oops */
878 void
879 show_oops_s(const char *fmt, ...)
881 va_list ap;
882 char *msg;
883 struct tab *ti, *t = NULL;
885 if (fmt == NULL)
886 return;
888 TAILQ_FOREACH(ti, &tabs, entry)
889 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
890 t = ti;
891 break;
893 if (t == NULL)
894 return;
896 va_start(ap, fmt);
897 if (vasprintf(&msg, fmt, ap) == -1)
898 errx(1, "show_oops_s failed");
899 va_end(ap);
901 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
902 gtk_widget_hide(t->cmd);
903 gtk_widget_show(t->oops);
906 char *
907 get_as_string(struct settings *s)
909 char *r = NULL;
911 if (s == NULL)
912 return (NULL);
914 if (s->s) {
915 if (s->s->get)
916 r = s->s->get(s);
917 else
918 warnx("get_as_string skip %s\n", s->name);
919 } else if (s->type == XT_S_INT)
920 r = g_strdup_printf("%d", *s->ival);
921 else if (s->type == XT_S_STR)
922 r = g_strdup(*s->sval);
923 else if (s->type == XT_S_FLOAT)
924 r = g_strdup_printf("%f", *s->fval);
925 else
926 r = g_strdup_printf("INVALID TYPE");
928 return (r);
931 void
932 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
934 int i;
935 char *s;
937 for (i = 0; i < LENGTH(rs); i++) {
938 if (rs[i].s && rs[i].s->walk)
939 rs[i].s->walk(&rs[i], cb, cb_args);
940 else {
941 s = get_as_string(&rs[i]);
942 cb(&rs[i], s, cb_args);
943 g_free(s);
949 set_browser_mode(struct settings *s, char *val)
951 if (!strcmp(val, "whitelist")) {
952 browser_mode = XT_BM_WHITELIST;
953 allow_volatile_cookies = 0;
954 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
955 cookies_enabled = 1;
956 enable_cookie_whitelist = 1;
957 read_only_cookies = 0;
958 save_rejected_cookies = 0;
959 session_timeout = 3600;
960 enable_scripts = 0;
961 enable_js_whitelist = 1;
962 } else if (!strcmp(val, "normal")) {
963 browser_mode = XT_BM_NORMAL;
964 allow_volatile_cookies = 0;
965 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
966 cookies_enabled = 1;
967 enable_cookie_whitelist = 0;
968 read_only_cookies = 0;
969 save_rejected_cookies = 0;
970 session_timeout = 3600;
971 enable_scripts = 1;
972 enable_js_whitelist = 0;
973 } else if (!strcmp(val, "kiosk")) {
974 browser_mode = XT_BM_KIOSK;
975 allow_volatile_cookies = 0;
976 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
977 cookies_enabled = 1;
978 enable_cookie_whitelist = 0;
979 read_only_cookies = 0;
980 save_rejected_cookies = 0;
981 session_timeout = 3600;
982 enable_scripts = 1;
983 enable_js_whitelist = 0;
984 show_tabs = 0;
985 } else
986 return (1);
988 return (0);
991 char *
992 get_browser_mode(struct settings *s)
994 char *r = NULL;
996 if (browser_mode == XT_BM_WHITELIST)
997 r = g_strdup("whitelist");
998 else if (browser_mode == XT_BM_NORMAL)
999 r = g_strdup("normal");
1000 else if (browser_mode == XT_BM_KIOSK)
1001 r = g_strdup("kiosk");
1002 else
1003 return (NULL);
1005 return (r);
1009 set_cookie_policy(struct settings *s, char *val)
1011 if (!strcmp(val, "no3rdparty"))
1012 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1013 else if (!strcmp(val, "accept"))
1014 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1015 else if (!strcmp(val, "reject"))
1016 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1017 else
1018 return (1);
1020 return (0);
1023 char *
1024 get_cookie_policy(struct settings *s)
1026 char *r = NULL;
1028 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1029 r = g_strdup("no3rdparty");
1030 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1031 r = g_strdup("accept");
1032 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1033 r = g_strdup("reject");
1034 else
1035 return (NULL);
1037 return (r);
1040 char *
1041 get_download_dir(struct settings *s)
1043 if (download_dir[0] == '\0')
1044 return (0);
1045 return (g_strdup(download_dir));
1049 set_download_dir(struct settings *s, char *val)
1051 if (val[0] == '~')
1052 snprintf(download_dir, sizeof download_dir, "%s/%s",
1053 pwd->pw_dir, &val[1]);
1054 else
1055 strlcpy(download_dir, val, sizeof download_dir);
1057 return (0);
1061 * Session IDs.
1062 * We use these to prevent people putting xxxt:// URLs on
1063 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1065 #define XT_XTP_SES_KEY_SZ 8
1066 #define XT_XTP_SES_KEY_HEX_FMT \
1067 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1068 char *dl_session_key; /* downloads */
1069 char *hl_session_key; /* history list */
1070 char *cl_session_key; /* cookie list */
1071 char *fl_session_key; /* favorites list */
1073 char work_dir[PATH_MAX];
1074 char certs_dir[PATH_MAX];
1075 char cache_dir[PATH_MAX];
1076 char sessions_dir[PATH_MAX];
1077 char cookie_file[PATH_MAX];
1078 SoupURI *proxy_uri = NULL;
1079 SoupSession *session;
1080 SoupCookieJar *s_cookiejar;
1081 SoupCookieJar *p_cookiejar;
1082 char rc_fname[PATH_MAX];
1084 struct mime_type_list mtl;
1085 struct alias_list aliases;
1087 /* protos */
1088 struct tab *create_new_tab(char *, struct undo *, int);
1089 void delete_tab(struct tab *);
1090 void adjustfont_webkit(struct tab *, int);
1091 int run_script(struct tab *, char *);
1092 int download_rb_cmp(struct download *, struct download *);
1095 history_rb_cmp(struct history *h1, struct history *h2)
1097 return (strcmp(h1->uri, h2->uri));
1099 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1102 domain_rb_cmp(struct domain *d1, struct domain *d2)
1104 return (strcmp(d1->d, d2->d));
1106 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1108 char *
1109 get_work_dir(struct settings *s)
1111 if (work_dir[0] == '\0')
1112 return (0);
1113 return (g_strdup(work_dir));
1117 set_work_dir(struct settings *s, char *val)
1119 if (val[0] == '~')
1120 snprintf(work_dir, sizeof work_dir, "%s/%s",
1121 pwd->pw_dir, &val[1]);
1122 else
1123 strlcpy(work_dir, val, sizeof work_dir);
1125 return (0);
1129 * generate a session key to secure xtp commands.
1130 * pass in a ptr to the key in question and it will
1131 * be modified in place.
1133 void
1134 generate_xtp_session_key(char **key)
1136 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1138 /* free old key */
1139 if (*key)
1140 g_free(*key);
1142 /* make a new one */
1143 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1144 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1145 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1146 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1148 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1152 * validate a xtp session key.
1153 * return 1 if OK
1156 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1158 if (strcmp(trusted, untrusted) != 0) {
1159 show_oops(t, "%s: xtp session key mismatch possible spoof",
1160 __func__);
1161 return (0);
1164 return (1);
1168 download_rb_cmp(struct download *e1, struct download *e2)
1170 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1172 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1174 struct valid_url_types {
1175 char *type;
1176 } vut[] = {
1177 { "http://" },
1178 { "https://" },
1179 { "ftp://" },
1180 { "file://" },
1181 { XT_XTP_STR },
1185 valid_url_type(char *url)
1187 int i;
1189 for (i = 0; i < LENGTH(vut); i++)
1190 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1191 return (0);
1193 return (1);
1196 void
1197 print_cookie(char *msg, SoupCookie *c)
1199 if (c == NULL)
1200 return;
1202 if (msg)
1203 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1204 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1205 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1206 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1207 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1208 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1209 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1210 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1211 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1212 DNPRINTF(XT_D_COOKIE, "====================================\n");
1215 void
1216 walk_alias(struct settings *s,
1217 void (*cb)(struct settings *, char *, void *), void *cb_args)
1219 struct alias *a;
1220 char *str;
1222 if (s == NULL || cb == NULL) {
1223 show_oops_s("walk_alias invalid parameters");
1224 return;
1227 TAILQ_FOREACH(a, &aliases, entry) {
1228 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1229 cb(s, str, cb_args);
1230 g_free(str);
1234 char *
1235 match_alias(char *url_in)
1237 struct alias *a;
1238 char *arg;
1239 char *url_out = NULL, *search, *enc_arg;
1241 search = g_strdup(url_in);
1242 arg = search;
1243 if (strsep(&arg, " \t") == NULL) {
1244 show_oops_s("match_alias: NULL URL");
1245 goto done;
1248 TAILQ_FOREACH(a, &aliases, entry) {
1249 if (!strcmp(search, a->a_name))
1250 break;
1253 if (a != NULL) {
1254 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1255 a->a_name);
1256 if (arg != NULL) {
1257 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1258 url_out = g_strdup_printf(a->a_uri, enc_arg);
1259 g_free(enc_arg);
1260 } else
1261 url_out = g_strdup(a->a_uri);
1263 done:
1264 g_free(search);
1265 return (url_out);
1268 char *
1269 guess_url_type(char *url_in)
1271 struct stat sb;
1272 char *url_out = NULL, *enc_search = NULL;
1274 url_out = match_alias(url_in);
1275 if (url_out != NULL)
1276 return (url_out);
1278 if (guess_search) {
1280 * If there is no dot nor slash in the string and it isn't a
1281 * path to a local file and doesn't resolves to an IP, assume
1282 * that the user wants to search for the string.
1285 if (strchr(url_in, '.') == NULL &&
1286 strchr(url_in, '/') == NULL &&
1287 stat(url_in, &sb) != 0 &&
1288 gethostbyname(url_in) == NULL) {
1290 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1291 url_out = g_strdup_printf(search_string, enc_search);
1292 g_free(enc_search);
1293 return (url_out);
1297 /* XXX not sure about this heuristic */
1298 if (stat(url_in, &sb) == 0)
1299 url_out = g_strdup_printf("file://%s", url_in);
1300 else
1301 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1303 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1305 return (url_out);
1308 void
1309 load_uri(struct tab *t, gchar *uri)
1311 struct karg args;
1312 gchar *newuri = NULL;
1313 int i;
1315 if (uri == NULL)
1316 return;
1318 /* Strip leading spaces. */
1319 while(*uri && isspace(*uri))
1320 uri++;
1322 if (strlen(uri) == 0) {
1323 blank(t, NULL);
1324 return;
1327 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1328 for (i = 0; i < LENGTH(about_list); i++)
1329 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1330 bzero(&args, sizeof args);
1331 about_list[i].func(t, &args);
1332 return;
1334 show_oops(t, "invalid about page");
1335 return;
1338 if (valid_url_type(uri)) {
1339 newuri = guess_url_type(uri);
1340 uri = newuri;
1343 set_status(t, (char *)uri, XT_STATUS_LOADING);
1344 webkit_web_view_load_uri(t->wv, uri);
1346 if (newuri)
1347 g_free(newuri);
1350 const gchar *
1351 get_uri(WebKitWebView *wv)
1353 WebKitWebFrame *frame;
1354 const gchar *uri;
1356 frame = webkit_web_view_get_main_frame(wv);
1357 uri = webkit_web_frame_get_uri(frame);
1359 if (uri && strlen(uri) > 0)
1360 return (uri);
1361 else
1362 return (NULL);
1366 add_alias(struct settings *s, char *line)
1368 char *l, *alias;
1369 struct alias *a = NULL;
1371 if (s == NULL || line == NULL) {
1372 show_oops_s("add_alias invalid parameters");
1373 return (1);
1376 l = line;
1377 a = g_malloc(sizeof(*a));
1379 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1380 show_oops_s("add_alias: incomplete alias definition");
1381 goto bad;
1383 if (strlen(alias) == 0 || strlen(l) == 0) {
1384 show_oops_s("add_alias: invalid alias definition");
1385 goto bad;
1388 a->a_name = g_strdup(alias);
1389 a->a_uri = g_strdup(l);
1391 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1393 TAILQ_INSERT_TAIL(&aliases, a, entry);
1395 return (0);
1396 bad:
1397 if (a)
1398 g_free(a);
1399 return (1);
1403 add_mime_type(struct settings *s, char *line)
1405 char *mime_type;
1406 char *l;
1407 struct mime_type *m = NULL;
1408 int downloadfirst = 0;
1410 /* XXX this could be smarter */
1412 if (line == NULL && strlen(line) == 0) {
1413 show_oops_s("add_mime_type invalid parameters");
1414 return (1);
1417 l = line;
1418 if (*l == '@') {
1419 downloadfirst = 1;
1420 l++;
1422 m = g_malloc(sizeof(*m));
1424 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1425 show_oops_s("add_mime_type: invalid mime_type");
1426 goto bad;
1428 if (mime_type[strlen(mime_type) - 1] == '*') {
1429 mime_type[strlen(mime_type) - 1] = '\0';
1430 m->mt_default = 1;
1431 } else
1432 m->mt_default = 0;
1434 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1435 show_oops_s("add_mime_type: invalid mime_type");
1436 goto bad;
1439 m->mt_type = g_strdup(mime_type);
1440 m->mt_action = g_strdup(l);
1441 m->mt_download = downloadfirst;
1443 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1444 m->mt_type, m->mt_action, m->mt_default);
1446 TAILQ_INSERT_TAIL(&mtl, m, entry);
1448 return (0);
1449 bad:
1450 if (m)
1451 g_free(m);
1452 return (1);
1455 struct mime_type *
1456 find_mime_type(char *mime_type)
1458 struct mime_type *m, *def = NULL, *rv = NULL;
1460 TAILQ_FOREACH(m, &mtl, entry) {
1461 if (m->mt_default &&
1462 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1463 def = m;
1465 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1466 rv = m;
1467 break;
1471 if (rv == NULL)
1472 rv = def;
1474 return (rv);
1477 void
1478 walk_mime_type(struct settings *s,
1479 void (*cb)(struct settings *, char *, void *), void *cb_args)
1481 struct mime_type *m;
1482 char *str;
1484 if (s == NULL || cb == NULL)
1485 show_oops_s("walk_mime_type invalid parameters");
1487 TAILQ_FOREACH(m, &mtl, entry) {
1488 str = g_strdup_printf("%s%s --> %s",
1489 m->mt_type,
1490 m->mt_default ? "*" : "",
1491 m->mt_action);
1492 cb(s, str, cb_args);
1493 g_free(str);
1497 void
1498 wl_add(char *str, struct domain_list *wl, int handy)
1500 struct domain *d;
1501 int add_dot = 0;
1503 if (str == NULL || wl == NULL)
1504 return;
1505 if (strlen(str) < 2)
1506 return;
1508 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1510 /* treat *.moo.com the same as .moo.com */
1511 if (str[0] == '*' && str[1] == '.')
1512 str = &str[1];
1513 else if (str[0] == '.')
1514 str = &str[0];
1515 else
1516 add_dot = 1;
1518 d = g_malloc(sizeof *d);
1519 if (add_dot)
1520 d->d = g_strdup_printf(".%s", str);
1521 else
1522 d->d = g_strdup(str);
1523 d->handy = handy;
1525 if (RB_INSERT(domain_list, wl, d))
1526 goto unwind;
1528 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1529 return;
1530 unwind:
1531 if (d) {
1532 if (d->d)
1533 g_free(d->d);
1534 g_free(d);
1539 add_cookie_wl(struct settings *s, char *entry)
1541 wl_add(entry, &c_wl, 1);
1542 return (0);
1545 void
1546 walk_cookie_wl(struct settings *s,
1547 void (*cb)(struct settings *, char *, void *), void *cb_args)
1549 struct domain *d;
1551 if (s == NULL || cb == NULL) {
1552 show_oops_s("walk_cookie_wl invalid parameters");
1553 return;
1556 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1557 cb(s, d->d, cb_args);
1560 void
1561 walk_js_wl(struct settings *s,
1562 void (*cb)(struct settings *, char *, void *), void *cb_args)
1564 struct domain *d;
1566 if (s == NULL || cb == NULL) {
1567 show_oops_s("walk_js_wl invalid parameters");
1568 return;
1571 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1572 cb(s, d->d, cb_args);
1576 add_js_wl(struct settings *s, char *entry)
1578 wl_add(entry, &js_wl, 1 /* persistent */);
1579 return (0);
1582 struct domain *
1583 wl_find(const gchar *search, struct domain_list *wl)
1585 int i;
1586 struct domain *d = NULL, dfind;
1587 gchar *s = NULL;
1589 if (search == NULL || wl == NULL)
1590 return (NULL);
1591 if (strlen(search) < 2)
1592 return (NULL);
1594 if (search[0] != '.')
1595 s = g_strdup_printf(".%s", search);
1596 else
1597 s = g_strdup(search);
1599 for (i = strlen(s) - 1; i >= 0; i--) {
1600 if (s[i] == '.') {
1601 dfind.d = &s[i];
1602 d = RB_FIND(domain_list, wl, &dfind);
1603 if (d)
1604 goto done;
1608 done:
1609 if (s)
1610 g_free(s);
1612 return (d);
1615 struct domain *
1616 wl_find_uri(const gchar *s, struct domain_list *wl)
1618 int i;
1619 char *ss;
1620 struct domain *r;
1622 if (s == NULL || wl == NULL)
1623 return (NULL);
1625 if (!strncmp(s, "http://", strlen("http://")))
1626 s = &s[strlen("http://")];
1627 else if (!strncmp(s, "https://", strlen("https://")))
1628 s = &s[strlen("https://")];
1630 if (strlen(s) < 2)
1631 return (NULL);
1633 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1634 /* chop string at first slash */
1635 if (s[i] == '/' || s[i] == '\0') {
1636 ss = g_strdup(s);
1637 ss[i] = '\0';
1638 r = wl_find(ss, wl);
1639 g_free(ss);
1640 return (r);
1643 return (NULL);
1646 char *
1647 get_toplevel_domain(char *domain)
1649 char *s;
1650 int found = 0;
1652 if (domain == NULL)
1653 return (NULL);
1654 if (strlen(domain) < 2)
1655 return (NULL);
1657 s = &domain[strlen(domain) - 1];
1658 while (s != domain) {
1659 if (*s == '.') {
1660 found++;
1661 if (found == 2)
1662 return (s);
1664 s--;
1667 if (found)
1668 return (domain);
1670 return (NULL);
1674 settings_add(char *var, char *val)
1676 int i, rv, *p;
1677 gfloat *f;
1678 char **s;
1680 /* get settings */
1681 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1682 if (strcmp(var, rs[i].name))
1683 continue;
1685 if (rs[i].s) {
1686 if (rs[i].s->set(&rs[i], val))
1687 errx(1, "invalid value for %s: %s", var, val);
1688 rv = 1;
1689 break;
1690 } else
1691 switch (rs[i].type) {
1692 case XT_S_INT:
1693 p = rs[i].ival;
1694 *p = atoi(val);
1695 rv = 1;
1696 break;
1697 case XT_S_STR:
1698 s = rs[i].sval;
1699 if (s == NULL)
1700 errx(1, "invalid sval for %s",
1701 rs[i].name);
1702 if (*s)
1703 g_free(*s);
1704 *s = g_strdup(val);
1705 rv = 1;
1706 break;
1707 case XT_S_FLOAT:
1708 f = rs[i].fval;
1709 *f = atof(val);
1710 rv = 1;
1711 break;
1712 case XT_S_INVALID:
1713 default:
1714 errx(1, "invalid type for %s", var);
1716 break;
1718 return (rv);
1721 #define WS "\n= \t"
1722 void
1723 config_parse(char *filename, int runtime)
1725 FILE *config, *f;
1726 char *line, *cp, *var, *val;
1727 size_t len, lineno = 0;
1728 int handled;
1729 char file[PATH_MAX];
1730 struct stat sb;
1732 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1734 if (filename == NULL)
1735 return;
1737 if (runtime && runtime_settings[0] != '\0') {
1738 snprintf(file, sizeof file, "%s/%s",
1739 work_dir, runtime_settings);
1740 if (stat(file, &sb)) {
1741 warnx("runtime file doesn't exist, creating it");
1742 if ((f = fopen(file, "w")) == NULL)
1743 err(1, "runtime");
1744 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1745 fclose(f);
1747 } else
1748 strlcpy(file, filename, sizeof file);
1750 if ((config = fopen(file, "r")) == NULL) {
1751 warn("config_parse: cannot open %s", filename);
1752 return;
1755 for (;;) {
1756 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1757 if (feof(config) || ferror(config))
1758 break;
1760 cp = line;
1761 cp += (long)strspn(cp, WS);
1762 if (cp[0] == '\0') {
1763 /* empty line */
1764 free(line);
1765 continue;
1768 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1769 errx(1, "invalid config file entry: %s", line);
1771 cp += (long)strspn(cp, WS);
1773 if ((val = strsep(&cp, "\0")) == NULL)
1774 break;
1776 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1777 handled = settings_add(var, val);
1778 if (handled == 0)
1779 errx(1, "invalid conf file entry: %s=%s", var, val);
1781 free(line);
1784 fclose(config);
1787 char *
1788 js_ref_to_string(JSContextRef context, JSValueRef ref)
1790 char *s = NULL;
1791 size_t l;
1792 JSStringRef jsref;
1794 jsref = JSValueToStringCopy(context, ref, NULL);
1795 if (jsref == NULL)
1796 return (NULL);
1798 l = JSStringGetMaximumUTF8CStringSize(jsref);
1799 s = g_malloc(l);
1800 if (s)
1801 JSStringGetUTF8CString(jsref, s, l);
1802 JSStringRelease(jsref);
1804 return (s);
1807 void
1808 disable_hints(struct tab *t)
1810 bzero(t->hint_buf, sizeof t->hint_buf);
1811 bzero(t->hint_num, sizeof t->hint_num);
1812 run_script(t, "vimprobable_clear()");
1813 t->hints_on = 0;
1814 t->hint_mode = XT_HINT_NONE;
1817 void
1818 enable_hints(struct tab *t)
1820 bzero(t->hint_buf, sizeof t->hint_buf);
1821 run_script(t, "vimprobable_show_hints()");
1822 t->hints_on = 1;
1823 t->hint_mode = XT_HINT_NONE;
1826 #define XT_JS_OPEN ("open;")
1827 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1828 #define XT_JS_FIRE ("fire;")
1829 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1830 #define XT_JS_FOUND ("found;")
1831 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1834 run_script(struct tab *t, char *s)
1836 JSGlobalContextRef ctx;
1837 WebKitWebFrame *frame;
1838 JSStringRef str;
1839 JSValueRef val, exception;
1840 char *es, buf[128];
1842 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1843 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1845 frame = webkit_web_view_get_main_frame(t->wv);
1846 ctx = webkit_web_frame_get_global_context(frame);
1848 str = JSStringCreateWithUTF8CString(s);
1849 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1850 NULL, 0, &exception);
1851 JSStringRelease(str);
1853 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1854 if (val == NULL) {
1855 es = js_ref_to_string(ctx, exception);
1856 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1857 g_free(es);
1858 return (1);
1859 } else {
1860 es = js_ref_to_string(ctx, val);
1861 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1863 /* handle return value right here */
1864 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1865 disable_hints(t);
1866 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1869 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1870 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1871 &es[XT_JS_FIRE_LEN]);
1872 run_script(t, buf);
1873 disable_hints(t);
1876 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1877 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1878 disable_hints(t);
1881 g_free(es);
1884 return (0);
1888 hint(struct tab *t, struct karg *args)
1891 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1893 if (t->hints_on == 0)
1894 enable_hints(t);
1895 else
1896 disable_hints(t);
1898 return (0);
1901 void
1902 apply_style(struct tab *t)
1904 g_object_set(G_OBJECT(t->settings),
1905 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1909 userstyle(struct tab *t, struct karg *args)
1911 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1913 if (t->styled) {
1914 t->styled = 0;
1915 g_object_set(G_OBJECT(t->settings),
1916 "user-stylesheet-uri", NULL, (char *)NULL);
1917 } else {
1918 t->styled = 1;
1919 apply_style(t);
1921 return (0);
1925 * Doesn't work fully, due to the following bug:
1926 * https://bugs.webkit.org/show_bug.cgi?id=51747
1929 restore_global_history(void)
1931 char file[PATH_MAX];
1932 FILE *f;
1933 struct history *h;
1934 gchar *uri;
1935 gchar *title;
1937 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1939 if ((f = fopen(file, "r")) == NULL) {
1940 warnx("%s: fopen", __func__);
1941 return (1);
1944 for (;;) {
1945 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1946 if (feof(f) || ferror(f))
1947 break;
1949 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1950 if (feof(f) || ferror(f)) {
1951 free(uri);
1952 warnx("%s: broken history file\n", __func__);
1953 return (1);
1956 if (uri && strlen(uri) && title && strlen(title)) {
1957 webkit_web_history_item_new_with_data(uri, title);
1958 h = g_malloc(sizeof(struct history));
1959 h->uri = g_strdup(uri);
1960 h->title = g_strdup(title);
1961 RB_INSERT(history_list, &hl, h);
1962 completion_add_uri(h->uri);
1963 } else {
1964 warnx("%s: failed to restore history\n", __func__);
1965 free(uri);
1966 free(title);
1967 return (1);
1970 free(uri);
1971 free(title);
1972 uri = NULL;
1973 title = NULL;
1976 return (0);
1980 save_global_history_to_disk(struct tab *t)
1982 char file[PATH_MAX];
1983 FILE *f;
1984 struct history *h;
1986 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1988 if ((f = fopen(file, "w")) == NULL) {
1989 show_oops(t, "%s: global history file: %s",
1990 __func__, strerror(errno));
1991 return (1);
1994 RB_FOREACH_REVERSE(h, history_list, &hl) {
1995 if (h->uri && h->title)
1996 fprintf(f, "%s\n%s\n", h->uri, h->title);
1999 fclose(f);
2001 return (0);
2005 quit(struct tab *t, struct karg *args)
2007 if (save_global_history)
2008 save_global_history_to_disk(t);
2010 gtk_main_quit();
2012 return (1);
2016 open_tabs(struct tab *t, struct karg *a)
2018 char file[PATH_MAX];
2019 FILE *f = NULL;
2020 char *uri = NULL;
2021 int rv = 1;
2022 struct tab *ti, *tt;
2024 if (a == NULL)
2025 goto done;
2027 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2028 if ((f = fopen(file, "r")) == NULL)
2029 goto done;
2031 ti = TAILQ_LAST(&tabs, tab_list);
2033 for (;;) {
2034 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2035 if (feof(f) || ferror(f))
2036 break;
2038 /* retrieve session name */
2039 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2040 strlcpy(named_session,
2041 &uri[strlen(XT_SAVE_SESSION_ID)],
2042 sizeof named_session);
2043 continue;
2046 if (uri && strlen(uri))
2047 create_new_tab(uri, NULL, 1);
2049 free(uri);
2050 uri = NULL;
2053 /* close open tabs */
2054 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2055 for (;;) {
2056 tt = TAILQ_FIRST(&tabs);
2057 if (tt != ti) {
2058 delete_tab(tt);
2059 continue;
2061 delete_tab(tt);
2062 break;
2066 rv = 0;
2067 done:
2068 if (f)
2069 fclose(f);
2071 return (rv);
2075 restore_saved_tabs(void)
2077 char file[PATH_MAX];
2078 int unlink_file = 0;
2079 struct stat sb;
2080 struct karg a;
2081 int rv = 0;
2083 snprintf(file, sizeof file, "%s/%s",
2084 sessions_dir, XT_RESTART_TABS_FILE);
2085 if (stat(file, &sb) == -1)
2086 a.s = XT_SAVED_TABS_FILE;
2087 else {
2088 unlink_file = 1;
2089 a.s = XT_RESTART_TABS_FILE;
2092 a.i = XT_SES_DONOTHING;
2093 rv = open_tabs(NULL, &a);
2095 if (unlink_file)
2096 unlink(file);
2098 return (rv);
2102 save_tabs(struct tab *t, struct karg *a)
2104 char file[PATH_MAX];
2105 FILE *f;
2106 struct tab *ti;
2107 const gchar *uri;
2108 int len = 0, i;
2109 const gchar **arr = NULL;
2111 if (a == NULL)
2112 return (1);
2113 if (a->s == NULL)
2114 snprintf(file, sizeof file, "%s/%s",
2115 sessions_dir, named_session);
2116 else
2117 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2119 if ((f = fopen(file, "w")) == NULL) {
2120 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2121 return (1);
2124 /* save session name */
2125 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2127 /* save tabs, in the order they are arranged in the notebook */
2128 TAILQ_FOREACH(ti, &tabs, entry)
2129 len++;
2131 arr = g_malloc0(len * sizeof(gchar *));
2133 TAILQ_FOREACH(ti, &tabs, entry) {
2134 if ((uri = get_uri(ti->wv)) != NULL)
2135 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2138 for (i = 0; i < len; i++)
2139 if (arr[i])
2140 fprintf(f, "%s\n", arr[i]);
2142 g_free(arr);
2143 fclose(f);
2145 return (0);
2149 save_tabs_and_quit(struct tab *t, struct karg *args)
2151 struct karg a;
2153 a.s = NULL;
2154 save_tabs(t, &a);
2155 quit(t, NULL);
2157 return (1);
2161 yank_uri(struct tab *t, struct karg *args)
2163 const gchar *uri;
2164 GtkClipboard *clipboard;
2166 if ((uri = get_uri(t->wv)) == NULL)
2167 return (1);
2169 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2170 gtk_clipboard_set_text(clipboard, uri, -1);
2172 return (0);
2175 struct paste_args {
2176 struct tab *t;
2177 int i;
2180 void
2181 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2183 struct paste_args *pap;
2185 if (data == NULL || text == NULL || !strlen(text))
2186 return;
2188 pap = (struct paste_args *)data;
2190 switch(pap->i) {
2191 case XT_PASTE_CURRENT_TAB:
2192 load_uri(pap->t, (gchar *)text);
2193 break;
2194 case XT_PASTE_NEW_TAB:
2195 create_new_tab((gchar *)text, NULL, 1);
2196 break;
2199 g_free(pap);
2203 paste_uri(struct tab *t, struct karg *args)
2205 GtkClipboard *clipboard;
2206 struct paste_args *pap;
2208 pap = g_malloc(sizeof(struct paste_args));
2210 pap->t = t;
2211 pap->i = args->i;
2213 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2214 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2216 return (0);
2219 char *
2220 find_domain(const gchar *s, int add_dot)
2222 int i;
2223 char *r = NULL, *ss = NULL;
2225 if (s == NULL)
2226 return (NULL);
2228 if (!strncmp(s, "http://", strlen("http://")))
2229 s = &s[strlen("http://")];
2230 else if (!strncmp(s, "https://", strlen("https://")))
2231 s = &s[strlen("https://")];
2233 if (strlen(s) < 2)
2234 return (NULL);
2236 ss = g_strdup(s);
2237 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2238 /* chop string at first slash */
2239 if (ss[i] == '/' || ss[i] == '\0') {
2240 ss[i] = '\0';
2241 if (add_dot)
2242 r = g_strdup_printf(".%s", ss);
2243 else
2244 r = g_strdup(ss);
2245 break;
2247 g_free(ss);
2249 return (r);
2253 toggle_cwl(struct tab *t, struct karg *args)
2255 struct domain *d;
2256 const gchar *uri;
2257 char *dom = NULL, *dom_toggle = NULL;
2258 int es;
2260 if (args == NULL)
2261 return (1);
2263 uri = get_uri(t->wv);
2264 dom = find_domain(uri, 1);
2265 d = wl_find(dom, &c_wl);
2267 if (d == NULL)
2268 es = 0;
2269 else
2270 es = 1;
2272 if (args->i & XT_WL_TOGGLE)
2273 es = !es;
2274 else if ((args->i & XT_WL_ENABLE) && es != 1)
2275 es = 1;
2276 else if ((args->i & XT_WL_DISABLE) && es != 0)
2277 es = 0;
2279 if (args->i & XT_WL_TOPLEVEL)
2280 dom_toggle = get_toplevel_domain(dom);
2281 else
2282 dom_toggle = dom;
2284 if (es)
2285 /* enable cookies for domain */
2286 wl_add(dom_toggle, &c_wl, 0);
2287 else
2288 /* disable cookies for domain */
2289 RB_REMOVE(domain_list, &c_wl, d);
2291 webkit_web_view_reload(t->wv);
2293 g_free(dom);
2294 return (0);
2298 toggle_js(struct tab *t, struct karg *args)
2300 int es;
2301 const gchar *uri;
2302 struct domain *d;
2303 char *dom = NULL, *dom_toggle = NULL;
2305 if (args == NULL)
2306 return (1);
2308 g_object_get(G_OBJECT(t->settings),
2309 "enable-scripts", &es, (char *)NULL);
2310 if (args->i & XT_WL_TOGGLE)
2311 es = !es;
2312 else if ((args->i & XT_WL_ENABLE) && es != 1)
2313 es = 1;
2314 else if ((args->i & XT_WL_DISABLE) && es != 0)
2315 es = 0;
2316 else
2317 return (1);
2319 uri = get_uri(t->wv);
2320 dom = find_domain(uri, 1);
2322 if (uri == NULL || dom == NULL) {
2323 show_oops(t, "Can't toggle domain in JavaScript white list");
2324 goto done;
2327 if (args->i & XT_WL_TOPLEVEL)
2328 dom_toggle = get_toplevel_domain(dom);
2329 else
2330 dom_toggle = dom;
2332 if (es) {
2333 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2334 wl_add(dom_toggle, &js_wl, 0 /* session */);
2335 } else {
2336 d = wl_find(dom_toggle, &js_wl);
2337 if (d)
2338 RB_REMOVE(domain_list, &js_wl, d);
2339 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2341 g_object_set(G_OBJECT(t->settings),
2342 "enable-scripts", es, (char *)NULL);
2343 g_object_set(G_OBJECT(t->settings),
2344 "javascript-can-open-windows-automatically", es, (char *)NULL);
2345 webkit_web_view_set_settings(t->wv, t->settings);
2346 webkit_web_view_reload(t->wv);
2347 done:
2348 if (dom)
2349 g_free(dom);
2350 return (0);
2353 void
2354 js_toggle_cb(GtkWidget *w, struct tab *t)
2356 struct karg a;
2358 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2359 toggle_js(t, &a);
2363 toggle_src(struct tab *t, struct karg *args)
2365 gboolean mode;
2367 if (t == NULL)
2368 return (0);
2370 mode = webkit_web_view_get_view_source_mode(t->wv);
2371 webkit_web_view_set_view_source_mode(t->wv, !mode);
2372 webkit_web_view_reload(t->wv);
2374 return (0);
2377 void
2378 focus_webview(struct tab *t)
2380 if (t == NULL)
2381 return;
2383 /* only grab focus if we are visible */
2384 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2385 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2389 focus(struct tab *t, struct karg *args)
2391 if (t == NULL || args == NULL)
2392 return (1);
2394 if (show_url == 0)
2395 return (0);
2397 if (args->i == XT_FOCUS_URI)
2398 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2399 else if (args->i == XT_FOCUS_SEARCH)
2400 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2402 return (0);
2406 stats(struct tab *t, struct karg *args)
2408 char *stats, *s, line[64 * 1024];
2409 uint64_t line_count = 0;
2410 FILE *r_cookie_f;
2412 if (t == NULL)
2413 show_oops_s("stats invalid parameters");
2415 line[0] = '\0';
2416 if (save_rejected_cookies) {
2417 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2418 for (;;) {
2419 s = fgets(line, sizeof line, r_cookie_f);
2420 if (s == NULL || feof(r_cookie_f) ||
2421 ferror(r_cookie_f))
2422 break;
2423 line_count++;
2425 fclose(r_cookie_f);
2426 snprintf(line, sizeof line,
2427 "<br>Cookies blocked(*) total: %llu", line_count);
2428 } else
2429 show_oops(t, "Can't open blocked cookies file: %s",
2430 strerror(errno));
2433 stats = g_strdup_printf(XT_DOCTYPE
2434 "<html>"
2435 "<head>"
2436 "<title>Statistics</title>"
2437 "</head>"
2438 "<h1>Statistics</h1>"
2439 "<body>"
2440 "Cookies blocked(*) this session: %llu"
2441 "%s"
2442 "<p><small><b>*</b> results vary based on settings"
2443 "</body>"
2444 "</html>",
2445 blocked_cookies,
2446 line);
2448 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2449 g_free(stats);
2451 return (0);
2455 blank(struct tab *t, struct karg *args)
2457 if (t == NULL)
2458 show_oops_s("about invalid parameters");
2460 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2462 return (0);
2465 about(struct tab *t, struct karg *args)
2467 char *about;
2469 if (t == NULL)
2470 show_oops_s("about invalid parameters");
2472 about = g_strdup_printf(XT_DOCTYPE
2473 "<html>"
2474 "<head>"
2475 "<title>About</title>"
2476 "</head>"
2477 "<h1>About</h1>"
2478 "<body>"
2479 "<b>Version: %s</b><p>"
2480 "Authors:"
2481 "<ul>"
2482 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2483 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2484 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2485 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2486 "</ul>"
2487 "Copyrights and licenses can be found on the XXXterm "
2488 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2489 "</body>"
2490 "</html>",
2491 version
2494 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2495 g_free(about);
2497 return (0);
2501 help(struct tab *t, struct karg *args)
2503 char *help;
2505 if (t == NULL)
2506 show_oops_s("help invalid parameters");
2508 help = XT_DOCTYPE
2509 "<html>"
2510 "<head>"
2511 "<title>XXXterm</title>"
2512 "<meta http-equiv=\"REFRESH\" content=\"0;"
2513 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2514 "</head>"
2515 "<body>"
2516 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2517 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2518 "cgi-bin/man-cgi?xxxterm</a>"
2519 "</body>"
2520 "</html>"
2523 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2525 return (0);
2529 * update all favorite tabs apart from one. Pass NULL if
2530 * you want to update all.
2532 void
2533 update_favorite_tabs(struct tab *apart_from)
2535 struct tab *t;
2536 if (!updating_fl_tabs) {
2537 updating_fl_tabs = 1; /* stop infinite recursion */
2538 TAILQ_FOREACH(t, &tabs, entry)
2539 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2540 && (t != apart_from))
2541 xtp_page_fl(t, NULL);
2542 updating_fl_tabs = 0;
2546 /* show a list of favorites (bookmarks) */
2548 xtp_page_fl(struct tab *t, struct karg *args)
2550 char file[PATH_MAX];
2551 FILE *f;
2552 char *uri = NULL, *title = NULL;
2553 size_t len, lineno = 0;
2554 int i, failed = 0;
2555 char *header, *body, *tmp, *html = NULL;
2557 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2559 if (t == NULL)
2560 warn("%s: bad param", __func__);
2562 /* mark tab as favorite list */
2563 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2565 /* new session key */
2566 if (!updating_fl_tabs)
2567 generate_xtp_session_key(&fl_session_key);
2569 /* open favorites */
2570 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2571 if ((f = fopen(file, "r")) == NULL) {
2572 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2573 return (1);
2576 /* header */
2577 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2578 "<title>Favorites</title>\n"
2579 "%s"
2580 "</head>"
2581 "<h1>Favorites</h1>\n",
2582 XT_PAGE_STYLE);
2584 /* body */
2585 body = g_strdup_printf("<div align='center'><table><tr>"
2586 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2587 "<th style='width: 15%%'>Remove</th></tr>\n");
2589 for (i = 1;;) {
2590 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2591 if (feof(f) || ferror(f))
2592 break;
2593 if (len == 0) {
2594 free(title);
2595 title = NULL;
2596 continue;
2599 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2600 if (feof(f) || ferror(f)) {
2601 show_oops(t, "favorites file corrupt");
2602 failed = 1;
2603 break;
2606 tmp = body;
2607 body = g_strdup_printf("%s<tr>"
2608 "<td>%d</td>"
2609 "<td><a href='%s'>%s</a></td>"
2610 "<td style='text-align: center'>"
2611 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2612 "</tr>\n",
2613 body, i, uri, title,
2614 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2616 g_free(tmp);
2618 free(uri);
2619 uri = NULL;
2620 free(title);
2621 title = NULL;
2622 i++;
2624 fclose(f);
2626 /* if none, say so */
2627 if (i == 1) {
2628 tmp = body;
2629 body = g_strdup_printf("%s<tr>"
2630 "<td colspan='3' style='text-align: center'>"
2631 "No favorites - To add one use the 'favadd' command."
2632 "</td></tr>", body);
2633 g_free(tmp);
2636 if (uri)
2637 free(uri);
2638 if (title)
2639 free(title);
2641 /* render */
2642 if (!failed) {
2643 html = g_strdup_printf("%s%s</table></div></html>",
2644 header, body);
2645 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2648 update_favorite_tabs(t);
2650 if (header)
2651 g_free(header);
2652 if (body)
2653 g_free(body);
2654 if (html)
2655 g_free(html);
2657 return (failed);
2660 char *
2661 getparams(char *cmd, char *cmp)
2663 char *rv = NULL;
2665 if (cmd && cmp) {
2666 if (!strncmp(cmd, cmp, strlen(cmp))) {
2667 rv = cmd + strlen(cmp);
2668 while (*rv == ' ')
2669 rv++;
2670 if (strlen(rv) == 0)
2671 rv = NULL;
2675 return (rv);
2678 void
2679 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2680 size_t cert_count, char *title)
2682 gnutls_datum_t cinfo;
2683 char *tmp, *header, *body, *footer;
2684 int i;
2686 header = g_strdup_printf("<title>%s</title><html><body>", title);
2687 footer = g_strdup("</body></html>");
2688 body = g_strdup("");
2690 for (i = 0; i < cert_count; i++) {
2691 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2692 &cinfo))
2693 return;
2695 tmp = body;
2696 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2697 body, i, cinfo.data);
2698 gnutls_free(cinfo.data);
2699 g_free(tmp);
2702 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2703 g_free(header);
2704 g_free(body);
2705 g_free(footer);
2706 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2707 g_free(tmp);
2711 ca_cmd(struct tab *t, struct karg *args)
2713 FILE *f = NULL;
2714 int rv = 1, certs = 0, certs_read;
2715 struct stat sb;
2716 gnutls_datum dt;
2717 gnutls_x509_crt_t *c = NULL;
2718 char *certs_buf = NULL, *s;
2720 /* yeah yeah stat race */
2721 if (stat(ssl_ca_file, &sb)) {
2722 show_oops(t, "no CA file: %s", ssl_ca_file);
2723 goto done;
2726 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2727 show_oops(t, "Can't open CA file: %s", strerror(errno));
2728 return (1);
2731 certs_buf = g_malloc(sb.st_size + 1);
2732 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2733 show_oops(t, "Can't read CA file: %s", strerror(errno));
2734 goto done;
2736 certs_buf[sb.st_size] = '\0';
2738 s = certs_buf;
2739 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2740 certs++;
2741 s += strlen("BEGIN CERTIFICATE");
2744 bzero(&dt, sizeof dt);
2745 dt.data = certs_buf;
2746 dt.size = sb.st_size;
2747 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2748 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2749 GNUTLS_X509_FMT_PEM, 0);
2750 if (certs_read <= 0) {
2751 show_oops(t, "No cert(s) available");
2752 goto done;
2754 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2755 done:
2756 if (c)
2757 g_free(c);
2758 if (certs_buf)
2759 g_free(certs_buf);
2760 if (f)
2761 fclose(f);
2763 return (rv);
2767 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2769 SoupURI *su = NULL;
2770 struct addrinfo hints, *res = NULL, *ai;
2771 int s = -1, on;
2772 char port[8];
2774 if (uri && !g_str_has_prefix(uri, "https://"))
2775 goto done;
2777 su = soup_uri_new(uri);
2778 if (su == NULL)
2779 goto done;
2780 if (!SOUP_URI_VALID_FOR_HTTP(su))
2781 goto done;
2783 snprintf(port, sizeof port, "%d", su->port);
2784 bzero(&hints, sizeof(struct addrinfo));
2785 hints.ai_flags = AI_CANONNAME;
2786 hints.ai_family = AF_UNSPEC;
2787 hints.ai_socktype = SOCK_STREAM;
2789 if (getaddrinfo(su->host, port, &hints, &res))
2790 goto done;
2792 for (ai = res; ai; ai = ai->ai_next) {
2793 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2794 continue;
2796 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2797 if (s < 0)
2798 goto done;
2799 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2800 sizeof(on)) == -1)
2801 goto done;
2803 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2804 goto done;
2807 if (domain)
2808 strlcpy(domain, su->host, domain_sz);
2809 done:
2810 if (su)
2811 soup_uri_free(su);
2812 if (res)
2813 freeaddrinfo(res);
2815 return (s);
2819 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2821 if (gsession)
2822 gnutls_deinit(gsession);
2823 if (xcred)
2824 gnutls_certificate_free_credentials(xcred);
2826 return (0);
2830 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2831 gnutls_certificate_credentials_t *xc)
2833 gnutls_certificate_credentials_t xcred;
2834 gnutls_session_t gsession;
2835 int rv = 1;
2837 if (gs == NULL || xc == NULL)
2838 goto done;
2840 *gs = NULL;
2841 *xc = NULL;
2843 gnutls_certificate_allocate_credentials(&xcred);
2844 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2845 GNUTLS_X509_FMT_PEM);
2846 gnutls_init(&gsession, GNUTLS_CLIENT);
2847 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2848 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2849 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2850 if ((rv = gnutls_handshake(gsession)) < 0) {
2851 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2853 gnutls_error_is_fatal(rv),
2854 gnutls_strerror_name(rv));
2855 stop_tls(gsession, xcred);
2856 goto done;
2859 gnutls_credentials_type_t cred;
2860 cred = gnutls_auth_get_type(gsession);
2861 if (cred != GNUTLS_CRD_CERTIFICATE) {
2862 stop_tls(gsession, xcred);
2863 goto done;
2866 *gs = gsession;
2867 *xc = xcred;
2868 rv = 0;
2869 done:
2870 return (rv);
2874 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2875 size_t *cert_count)
2877 unsigned int len;
2878 const gnutls_datum_t *cl;
2879 gnutls_x509_crt_t *all_certs;
2880 int i, rv = 1;
2882 if (certs == NULL || cert_count == NULL)
2883 goto done;
2884 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2885 goto done;
2886 cl = gnutls_certificate_get_peers(gsession, &len);
2887 if (len == 0)
2888 goto done;
2890 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2891 for (i = 0; i < len; i++) {
2892 gnutls_x509_crt_init(&all_certs[i]);
2893 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2894 GNUTLS_X509_FMT_PEM < 0)) {
2895 g_free(all_certs);
2896 goto done;
2900 *certs = all_certs;
2901 *cert_count = len;
2902 rv = 0;
2903 done:
2904 return (rv);
2907 void
2908 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2910 int i;
2912 for (i = 0; i < cert_count; i++)
2913 gnutls_x509_crt_deinit(certs[i]);
2914 g_free(certs);
2917 void
2918 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2919 size_t cert_count, char *domain)
2921 size_t cert_buf_sz;
2922 char cert_buf[64 * 1024], file[PATH_MAX];
2923 int i;
2924 FILE *f;
2925 GdkColor color;
2927 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2928 return;
2930 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2931 if ((f = fopen(file, "w")) == NULL) {
2932 show_oops(t, "Can't create cert file %s %s",
2933 file, strerror(errno));
2934 return;
2937 for (i = 0; i < cert_count; i++) {
2938 cert_buf_sz = sizeof cert_buf;
2939 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2940 cert_buf, &cert_buf_sz)) {
2941 show_oops(t, "gnutls_x509_crt_export failed");
2942 goto done;
2944 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2945 show_oops(t, "Can't write certs: %s", strerror(errno));
2946 goto done;
2950 /* not the best spot but oh well */
2951 gdk_color_parse("lightblue", &color);
2952 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2953 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2954 gdk_color_parse(XT_COLOR_BLACK, &color);
2955 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2956 done:
2957 fclose(f);
2961 load_compare_cert(struct tab *t, struct karg *args)
2963 const gchar *uri;
2964 char domain[8182], file[PATH_MAX];
2965 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2966 int s = -1, rv = 1, i;
2967 size_t cert_count;
2968 FILE *f = NULL;
2969 size_t cert_buf_sz;
2970 gnutls_session_t gsession;
2971 gnutls_x509_crt_t *certs;
2972 gnutls_certificate_credentials_t xcred;
2974 if (t == NULL)
2975 return (1);
2977 if ((uri = get_uri(t->wv)) == NULL)
2978 return (1);
2980 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2981 return (1);
2983 /* go ssl/tls */
2984 if (start_tls(t, s, &gsession, &xcred)) {
2985 show_oops(t, "Start TLS failed");
2986 goto done;
2989 /* get certs */
2990 if (get_connection_certs(gsession, &certs, &cert_count)) {
2991 show_oops(t, "Can't get connection certificates");
2992 goto done;
2995 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2996 if ((f = fopen(file, "r")) == NULL)
2997 goto freeit;
2999 for (i = 0; i < cert_count; i++) {
3000 cert_buf_sz = sizeof cert_buf;
3001 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3002 cert_buf, &cert_buf_sz)) {
3003 goto freeit;
3005 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3006 rv = -1; /* critical */
3007 goto freeit;
3009 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3010 rv = -1; /* critical */
3011 goto freeit;
3015 rv = 0;
3016 freeit:
3017 if (f)
3018 fclose(f);
3019 free_connection_certs(certs, cert_count);
3020 done:
3021 /* we close the socket first for speed */
3022 if (s != -1)
3023 close(s);
3024 stop_tls(gsession, xcred);
3026 return (rv);
3030 cert_cmd(struct tab *t, struct karg *args)
3032 const gchar *uri;
3033 char *action, domain[8182];
3034 int s = -1;
3035 size_t cert_count;
3036 gnutls_session_t gsession;
3037 gnutls_x509_crt_t *certs;
3038 gnutls_certificate_credentials_t xcred;
3040 if (t == NULL)
3041 return (1);
3043 if ((action = getparams(args->s, "cert")))
3045 else
3046 action = "show";
3048 if ((uri = get_uri(t->wv)) == NULL) {
3049 show_oops(t, "Invalid URI");
3050 return (1);
3053 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3054 show_oops(t, "Invalid certidicate URI: %s", uri);
3055 return (1);
3058 /* go ssl/tls */
3059 if (start_tls(t, s, &gsession, &xcred)) {
3060 show_oops(t, "Start TLS failed");
3061 goto done;
3064 /* get certs */
3065 if (get_connection_certs(gsession, &certs, &cert_count)) {
3066 show_oops(t, "get_connection_certs failed");
3067 goto done;
3070 if (!strcmp(action, "show"))
3071 show_certs(t, certs, cert_count, "Certificate Chain");
3072 else if (!strcmp(action, "save"))
3073 save_certs(t, certs, cert_count, domain);
3074 else
3075 show_oops(t, "Invalid command: %s", action);
3077 free_connection_certs(certs, cert_count);
3078 done:
3079 /* we close the socket first for speed */
3080 if (s != -1)
3081 close(s);
3082 stop_tls(gsession, xcred);
3084 return (0);
3088 remove_cookie(int index)
3090 int i, rv = 1;
3091 GSList *cf;
3092 SoupCookie *c;
3094 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3096 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3098 for (i = 1; cf; cf = cf->next, i++) {
3099 if (i != index)
3100 continue;
3101 c = cf->data;
3102 print_cookie("remove cookie", c);
3103 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3104 rv = 0;
3105 break;
3108 soup_cookies_free(cf);
3110 return (rv);
3114 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
3116 struct domain *d;
3117 char *tmp, *header, *body, *footer;
3118 int p_js = 0, s_js = 0;
3120 /* we set this to indicate we want to manually do navaction */
3121 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3123 if (g_str_has_prefix(args, "show a") ||
3124 !strcmp(args, "show")) {
3125 /* show all */
3126 p_js = 1;
3127 s_js = 1;
3128 } else if (g_str_has_prefix(args, "show p")) {
3129 /* show persistent */
3130 p_js = 1;
3131 } else if (g_str_has_prefix(args, "show s")) {
3132 /* show session */
3133 s_js = 1;
3134 } else
3135 return (1);
3137 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3138 title, title);
3139 footer = g_strdup("</body></html>");
3140 body = g_strdup("");
3142 /* p list */
3143 if (p_js) {
3144 tmp = body;
3145 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3146 g_free(tmp);
3147 RB_FOREACH(d, domain_list, wl) {
3148 if (d->handy == 0)
3149 continue;
3150 tmp = body;
3151 body = g_strdup_printf("%s%s<br>", body, d->d);
3152 g_free(tmp);
3156 /* s list */
3157 if (s_js) {
3158 tmp = body;
3159 body = g_strdup_printf("%s<h2>Session</h2>", body);
3160 g_free(tmp);
3161 RB_FOREACH(d, domain_list, wl) {
3162 if (d->handy == 1)
3163 continue;
3164 tmp = body;
3165 body = g_strdup_printf("%s%s<br>", body, d->d);
3166 g_free(tmp);
3170 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3171 g_free(header);
3172 g_free(body);
3173 g_free(footer);
3174 if (wl == &js_wl)
3175 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3176 else
3177 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3178 g_free(tmp);
3179 return (0);
3183 wl_save(struct tab *t, struct karg *args, int js)
3185 char file[PATH_MAX];
3186 FILE *f;
3187 char *line = NULL, *lt = NULL;
3188 size_t linelen;
3189 const gchar *uri;
3190 char *dom = NULL, *dom_save = NULL;
3191 struct karg a;
3192 struct domain *d;
3193 GSList *cf;
3194 SoupCookie *ci, *c;
3195 int flags;
3197 if (t == NULL || args == NULL)
3198 return (1);
3200 if (runtime_settings[0] == '\0')
3201 return (1);
3203 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3204 if ((f = fopen(file, "r+")) == NULL)
3205 return (1);
3207 uri = get_uri(t->wv);
3208 dom = find_domain(uri, 1);
3209 if (uri == NULL || dom == NULL) {
3210 show_oops(t, "Can't add domain to %s white list",
3211 js ? "JavaScript" : "cookie");
3212 goto done;
3215 if (g_str_has_prefix(args->s, "save d")) {
3216 /* save domain */
3217 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3218 show_oops(t, "invalid domain: %s", dom);
3219 goto done;
3221 flags = XT_WL_TOPLEVEL;
3222 } else if (g_str_has_prefix(args->s, "save f") ||
3223 !strcmp(args->s, "save")) {
3224 /* save fqdn */
3225 dom_save = dom;
3226 flags = XT_WL_FQDN;
3227 } else {
3228 show_oops(t, "invalid command: %s", args->s);
3229 goto done;
3232 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3234 while (!feof(f)) {
3235 line = fparseln(f, &linelen, NULL, NULL, 0);
3236 if (line == NULL)
3237 continue;
3238 if (!strcmp(line, lt))
3239 goto done;
3240 free(line);
3241 line = NULL;
3244 fprintf(f, "%s\n", lt);
3246 a.i = XT_WL_ENABLE;
3247 a.i |= flags;
3248 if (js) {
3249 d = wl_find(dom_save, &js_wl);
3250 if (!d) {
3251 settings_add("js_wl", dom_save);
3252 d = wl_find(dom_save, &js_wl);
3254 toggle_js(t, &a);
3255 } else {
3256 d = wl_find(dom_save, &c_wl);
3257 if (!d) {
3258 settings_add("cookie_wl", dom_save);
3259 d = wl_find(dom_save, &c_wl);
3261 toggle_cwl(t, &a);
3263 /* find and add to persistent jar */
3264 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3265 for (;cf; cf = cf->next) {
3266 ci = cf->data;
3267 if (!strcmp(dom_save, ci->domain) ||
3268 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3269 c = soup_cookie_copy(ci);
3270 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3273 soup_cookies_free(cf);
3275 if (d)
3276 d->handy = 1;
3278 done:
3279 if (line)
3280 free(line);
3281 if (dom)
3282 g_free(dom);
3283 if (lt)
3284 g_free(lt);
3285 fclose(f);
3287 return (0);
3291 js_show_wl(struct tab *t, struct karg *args)
3293 wl_show(t, "show all", "JavaScript White List", &js_wl);
3295 return (0);
3299 cookie_show_wl(struct tab *t, struct karg *args)
3301 wl_show(t, "show all", "Cookie White List", &c_wl);
3303 return (0);
3307 cookie_cmd(struct tab *t, struct karg *args)
3309 char *cmd;
3310 struct karg a;
3312 if ((cmd = getparams(args->s, "cookie")))
3314 else
3315 cmd = "show all";
3318 if (g_str_has_prefix(cmd, "show")) {
3319 wl_show(t, cmd, "Cookie White List", &c_wl);
3320 } else if (g_str_has_prefix(cmd, "save")) {
3321 a.s = cmd;
3322 wl_save(t, &a, 0);
3323 } else if (g_str_has_prefix(cmd, "toggle")) {
3324 a.i = XT_WL_TOGGLE;
3325 if (g_str_has_prefix(cmd, "toggle d"))
3326 a.i |= XT_WL_TOPLEVEL;
3327 else
3328 a.i |= XT_WL_FQDN;
3329 toggle_cwl(t, &a);
3330 } else if (g_str_has_prefix(cmd, "delete")) {
3331 show_oops(t, "'cookie delete' currently unimplemented");
3332 } else
3333 show_oops(t, "unknown cookie command: %s", cmd);
3335 return (0);
3339 js_cmd(struct tab *t, struct karg *args)
3341 char *cmd;
3342 struct karg a;
3344 if ((cmd = getparams(args->s, "js")))
3346 else
3347 cmd = "show all";
3349 if (g_str_has_prefix(cmd, "show")) {
3350 wl_show(t, cmd, "JavaScript White List", &js_wl);
3351 } else if (g_str_has_prefix(cmd, "save")) {
3352 a.s = cmd;
3353 wl_save(t, &a, 1);
3354 } else if (g_str_has_prefix(cmd, "toggle")) {
3355 a.i = XT_WL_TOGGLE;
3356 if (g_str_has_prefix(cmd, "toggle d"))
3357 a.i |= XT_WL_TOPLEVEL;
3358 else
3359 a.i |= XT_WL_FQDN;
3360 toggle_js(t, &a);
3361 } else if (g_str_has_prefix(cmd, "delete")) {
3362 show_oops(t, "'js delete' currently unimplemented");
3363 } else
3364 show_oops(t, "unknown js command: %s", cmd);
3366 return (0);
3370 add_favorite(struct tab *t, struct karg *args)
3372 char file[PATH_MAX];
3373 FILE *f;
3374 char *line = NULL;
3375 size_t urilen, linelen;
3376 const gchar *uri, *title;
3378 if (t == NULL)
3379 return (1);
3381 /* don't allow adding of xtp pages to favorites */
3382 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3383 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3384 return (1);
3387 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3388 if ((f = fopen(file, "r+")) == NULL) {
3389 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3390 return (1);
3393 title = webkit_web_view_get_title(t->wv);
3394 uri = get_uri(t->wv);
3396 if (title == NULL)
3397 title = uri;
3399 if (title == NULL || uri == NULL) {
3400 show_oops(t, "can't add page to favorites");
3401 goto done;
3404 urilen = strlen(uri);
3406 for (;;) {
3407 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3408 if (feof(f) || ferror(f))
3409 break;
3411 if (linelen == urilen && !strcmp(line, uri))
3412 goto done;
3414 free(line);
3415 line = NULL;
3418 fprintf(f, "\n%s\n%s", title, uri);
3419 done:
3420 if (line)
3421 free(line);
3422 fclose(f);
3424 update_favorite_tabs(NULL);
3426 return (0);
3430 navaction(struct tab *t, struct karg *args)
3432 WebKitWebHistoryItem *item;
3434 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3435 t->tab_id, args->i);
3437 if (t->item) {
3438 if (args->i == XT_NAV_BACK)
3439 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3440 else
3441 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3442 if (item == NULL)
3443 return (XT_CB_PASSTHROUGH);
3444 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3445 t->item = NULL;
3446 return (XT_CB_PASSTHROUGH);
3449 switch (args->i) {
3450 case XT_NAV_BACK:
3451 webkit_web_view_go_back(t->wv);
3452 break;
3453 case XT_NAV_FORWARD:
3454 webkit_web_view_go_forward(t->wv);
3455 break;
3456 case XT_NAV_RELOAD:
3457 webkit_web_view_reload(t->wv);
3458 break;
3459 case XT_NAV_RELOAD_CACHE:
3460 webkit_web_view_reload_bypass_cache(t->wv);
3461 break;
3463 return (XT_CB_PASSTHROUGH);
3467 move(struct tab *t, struct karg *args)
3469 GtkAdjustment *adjust;
3470 double pi, si, pos, ps, upper, lower, max;
3472 switch (args->i) {
3473 case XT_MOVE_DOWN:
3474 case XT_MOVE_UP:
3475 case XT_MOVE_BOTTOM:
3476 case XT_MOVE_TOP:
3477 case XT_MOVE_PAGEDOWN:
3478 case XT_MOVE_PAGEUP:
3479 case XT_MOVE_HALFDOWN:
3480 case XT_MOVE_HALFUP:
3481 adjust = t->adjust_v;
3482 break;
3483 default:
3484 adjust = t->adjust_h;
3485 break;
3488 pos = gtk_adjustment_get_value(adjust);
3489 ps = gtk_adjustment_get_page_size(adjust);
3490 upper = gtk_adjustment_get_upper(adjust);
3491 lower = gtk_adjustment_get_lower(adjust);
3492 si = gtk_adjustment_get_step_increment(adjust);
3493 pi = gtk_adjustment_get_page_increment(adjust);
3494 max = upper - ps;
3496 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3497 "max %f si %f pi %f\n",
3498 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3499 pos, ps, upper, lower, max, si, pi);
3501 switch (args->i) {
3502 case XT_MOVE_DOWN:
3503 case XT_MOVE_RIGHT:
3504 pos += si;
3505 gtk_adjustment_set_value(adjust, MIN(pos, max));
3506 break;
3507 case XT_MOVE_UP:
3508 case XT_MOVE_LEFT:
3509 pos -= si;
3510 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3511 break;
3512 case XT_MOVE_BOTTOM:
3513 case XT_MOVE_FARRIGHT:
3514 gtk_adjustment_set_value(adjust, max);
3515 break;
3516 case XT_MOVE_TOP:
3517 case XT_MOVE_FARLEFT:
3518 gtk_adjustment_set_value(adjust, lower);
3519 break;
3520 case XT_MOVE_PAGEDOWN:
3521 pos += pi;
3522 gtk_adjustment_set_value(adjust, MIN(pos, max));
3523 break;
3524 case XT_MOVE_PAGEUP:
3525 pos -= pi;
3526 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3527 break;
3528 case XT_MOVE_HALFDOWN:
3529 pos += pi / 2;
3530 gtk_adjustment_set_value(adjust, MIN(pos, max));
3531 break;
3532 case XT_MOVE_HALFUP:
3533 pos -= pi / 2;
3534 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3535 break;
3536 default:
3537 return (XT_CB_PASSTHROUGH);
3540 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3542 return (XT_CB_HANDLED);
3545 void
3546 url_set_visibility(void)
3548 struct tab *t;
3550 TAILQ_FOREACH(t, &tabs, entry) {
3551 if (show_url == 0) {
3552 gtk_widget_hide(t->toolbar);
3553 focus_webview(t);
3554 } else
3555 gtk_widget_show(t->toolbar);
3559 void
3560 notebook_tab_set_visibility(GtkNotebook *notebook)
3562 if (show_tabs == 0)
3563 gtk_notebook_set_show_tabs(notebook, FALSE);
3564 else
3565 gtk_notebook_set_show_tabs(notebook, TRUE);
3568 void
3569 statusbar_set_visibility(void)
3571 struct tab *t;
3573 TAILQ_FOREACH(t, &tabs, entry) {
3574 if (show_statusbar == 0) {
3575 gtk_widget_hide(t->statusbar);
3576 focus_webview(t);
3577 } else
3578 gtk_widget_show(t->statusbar);
3582 void
3583 url_set(struct tab *t, int enable_url_entry)
3585 GdkPixbuf *pixbuf;
3586 int progress;
3588 show_url = enable_url_entry;
3590 if (enable_url_entry) {
3591 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3592 GTK_ENTRY_ICON_PRIMARY, NULL);
3593 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3594 } else {
3595 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3596 GTK_ENTRY_ICON_PRIMARY);
3597 progress =
3598 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3599 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3600 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3601 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3602 progress);
3607 fullscreen(struct tab *t, struct karg *args)
3609 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3611 if (t == NULL)
3612 return (XT_CB_PASSTHROUGH);
3614 if (show_url == 0) {
3615 url_set(t, 1);
3616 show_tabs = 1;
3617 } else {
3618 url_set(t, 0);
3619 show_tabs = 0;
3622 url_set_visibility();
3623 notebook_tab_set_visibility(notebook);
3625 return (XT_CB_HANDLED);
3629 statusaction(struct tab *t, struct karg *args)
3631 int rv = XT_CB_HANDLED;
3633 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3635 if (t == NULL)
3636 return (XT_CB_PASSTHROUGH);
3638 switch (args->i) {
3639 case XT_STATUSBAR_SHOW:
3640 if (show_statusbar == 0) {
3641 show_statusbar = 1;
3642 statusbar_set_visibility();
3644 break;
3645 case XT_STATUSBAR_HIDE:
3646 if (show_statusbar == 1) {
3647 show_statusbar = 0;
3648 statusbar_set_visibility();
3650 break;
3652 return (rv);
3656 urlaction(struct tab *t, struct karg *args)
3658 int rv = XT_CB_HANDLED;
3660 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3662 if (t == NULL)
3663 return (XT_CB_PASSTHROUGH);
3665 switch (args->i) {
3666 case XT_URL_SHOW:
3667 if (show_url == 0) {
3668 url_set(t, 1);
3669 url_set_visibility();
3671 break;
3672 case XT_URL_HIDE:
3673 if (show_url == 1) {
3674 url_set(t, 0);
3675 url_set_visibility();
3677 break;
3679 return (rv);
3683 tabaction(struct tab *t, struct karg *args)
3685 int rv = XT_CB_HANDLED;
3686 char *url = NULL;
3687 struct undo *u;
3689 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3691 if (t == NULL)
3692 return (XT_CB_PASSTHROUGH);
3694 switch (args->i) {
3695 case XT_TAB_NEW:
3696 if ((url = getparams(args->s, "tabnew")))
3697 create_new_tab(url, NULL, 1);
3698 else
3699 create_new_tab(NULL, NULL, 1);
3700 break;
3701 case XT_TAB_DELETE:
3702 delete_tab(t);
3703 break;
3704 case XT_TAB_DELQUIT:
3705 if (gtk_notebook_get_n_pages(notebook) > 1)
3706 delete_tab(t);
3707 else
3708 quit(t, args);
3709 break;
3710 case XT_TAB_OPEN:
3711 if ((url = getparams(args->s, "open")) ||
3712 ((url = getparams(args->s, "op"))) ||
3713 ((url = getparams(args->s, "o"))))
3715 else {
3716 rv = XT_CB_PASSTHROUGH;
3717 goto done;
3719 load_uri(t, url);
3720 break;
3721 case XT_TAB_SHOW:
3722 if (show_tabs == 0) {
3723 show_tabs = 1;
3724 notebook_tab_set_visibility(notebook);
3726 break;
3727 case XT_TAB_HIDE:
3728 if (show_tabs == 1) {
3729 show_tabs = 0;
3730 notebook_tab_set_visibility(notebook);
3732 break;
3733 case XT_TAB_UNDO_CLOSE:
3734 if (undo_count == 0) {
3735 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3736 goto done;
3737 } else {
3738 undo_count--;
3739 u = TAILQ_FIRST(&undos);
3740 create_new_tab(u->uri, u, 1);
3742 TAILQ_REMOVE(&undos, u, entry);
3743 g_free(u->uri);
3744 /* u->history is freed in create_new_tab() */
3745 g_free(u);
3747 break;
3748 default:
3749 rv = XT_CB_PASSTHROUGH;
3750 goto done;
3753 done:
3754 if (args->s) {
3755 g_free(args->s);
3756 args->s = NULL;
3759 return (rv);
3763 resizetab(struct tab *t, struct karg *args)
3765 if (t == NULL || args == NULL) {
3766 show_oops_s("resizetab invalid parameters");
3767 return (XT_CB_PASSTHROUGH);
3770 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3771 t->tab_id, args->i);
3773 adjustfont_webkit(t, args->i);
3775 return (XT_CB_HANDLED);
3779 movetab(struct tab *t, struct karg *args)
3781 struct tab *tt;
3782 int x;
3784 if (t == NULL || args == NULL) {
3785 show_oops_s("movetab invalid parameters");
3786 return (XT_CB_PASSTHROUGH);
3789 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3790 t->tab_id, args->i);
3792 if (args->i == XT_TAB_INVALID)
3793 return (XT_CB_PASSTHROUGH);
3795 if (args->i < XT_TAB_INVALID) {
3796 /* next or previous tab */
3797 if (TAILQ_EMPTY(&tabs))
3798 return (XT_CB_PASSTHROUGH);
3800 switch (args->i) {
3801 case XT_TAB_NEXT:
3802 /* if at the last page, loop around to the first */
3803 if (gtk_notebook_get_current_page(notebook) ==
3804 gtk_notebook_get_n_pages(notebook) - 1)
3805 gtk_notebook_set_current_page(notebook, 0);
3806 else
3807 gtk_notebook_next_page(notebook);
3808 break;
3809 case XT_TAB_PREV:
3810 /* if at the first page, loop around to the last */
3811 if (gtk_notebook_current_page(notebook) == 0)
3812 gtk_notebook_set_current_page(notebook,
3813 gtk_notebook_get_n_pages(notebook) - 1);
3814 else
3815 gtk_notebook_prev_page(notebook);
3816 break;
3817 case XT_TAB_FIRST:
3818 gtk_notebook_set_current_page(notebook, 0);
3819 break;
3820 case XT_TAB_LAST:
3821 gtk_notebook_set_current_page(notebook, -1);
3822 break;
3823 default:
3824 return (XT_CB_PASSTHROUGH);
3827 return (XT_CB_HANDLED);
3830 /* jump to tab */
3831 x = args->i - 1;
3832 if (t->tab_id == x) {
3833 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3834 return (XT_CB_HANDLED);
3837 TAILQ_FOREACH(tt, &tabs, entry) {
3838 if (tt->tab_id == x) {
3839 gtk_notebook_set_current_page(notebook, x);
3840 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3841 if (tt->focus_wv)
3842 focus_webview(tt);
3846 return (XT_CB_HANDLED);
3850 command(struct tab *t, struct karg *args)
3852 char *s = NULL, *ss = NULL;
3853 GdkColor color;
3854 const gchar *uri;
3856 if (t == NULL || args == NULL) {
3857 show_oops_s("command invalid parameters");
3858 return (XT_CB_PASSTHROUGH);
3861 switch (args->i) {
3862 case '/':
3863 s = "/";
3864 break;
3865 case '?':
3866 s = "?";
3867 break;
3868 case ':':
3869 s = ":";
3870 break;
3871 case XT_CMD_OPEN:
3872 s = ":open ";
3873 break;
3874 case XT_CMD_TABNEW:
3875 s = ":tabnew ";
3876 break;
3877 case XT_CMD_OPEN_CURRENT:
3878 s = ":open ";
3879 /* FALL THROUGH */
3880 case XT_CMD_TABNEW_CURRENT:
3881 if (!s) /* FALL THROUGH? */
3882 s = ":tabnew ";
3883 if ((uri = get_uri(t->wv)) != NULL) {
3884 ss = g_strdup_printf("%s%s", s, uri);
3885 s = ss;
3887 break;
3888 default:
3889 show_oops(t, "command: invalid opcode %d", args->i);
3890 return (XT_CB_PASSTHROUGH);
3893 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3895 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3896 gdk_color_parse(XT_COLOR_WHITE, &color);
3897 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3898 show_cmd(t);
3899 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3900 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3902 if (ss)
3903 g_free(ss);
3905 return (XT_CB_HANDLED);
3909 * Return a new string with a download row (in html)
3910 * appended. Old string is freed.
3912 char *
3913 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3916 WebKitDownloadStatus stat;
3917 char *status_html = NULL, *cmd_html = NULL, *new_html;
3918 gdouble progress;
3919 char cur_sz[FMT_SCALED_STRSIZE];
3920 char tot_sz[FMT_SCALED_STRSIZE];
3921 char *xtp_prefix;
3923 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3925 /* All actions wil take this form:
3926 * xxxt://class/seskey
3928 xtp_prefix = g_strdup_printf("%s%d/%s/",
3929 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3931 stat = webkit_download_get_status(dl->download);
3933 switch (stat) {
3934 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3935 status_html = g_strdup_printf("Finished");
3936 cmd_html = g_strdup_printf(
3937 "<a href='%s%d/%d'>Remove</a>",
3938 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3939 break;
3940 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3941 /* gather size info */
3942 progress = 100 * webkit_download_get_progress(dl->download);
3944 fmt_scaled(
3945 webkit_download_get_current_size(dl->download), cur_sz);
3946 fmt_scaled(
3947 webkit_download_get_total_size(dl->download), tot_sz);
3949 status_html = g_strdup_printf(
3950 "<div style='width: 100%%' align='center'>"
3951 "<div class='progress-outer'>"
3952 "<div class='progress-inner' style='width: %.2f%%'>"
3953 "</div></div></div>"
3954 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3955 progress, cur_sz, tot_sz, progress);
3957 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3958 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3960 break;
3961 /* LLL */
3962 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3963 status_html = g_strdup_printf("Cancelled");
3964 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3965 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3966 break;
3967 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3968 status_html = g_strdup_printf("Error!");
3969 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3970 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3971 break;
3972 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3973 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3974 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3975 status_html = g_strdup_printf("Starting");
3976 break;
3977 default:
3978 show_oops(t, "%s: unknown download status", __func__);
3981 new_html = g_strdup_printf(
3982 "%s\n<tr><td>%s</td><td>%s</td>"
3983 "<td style='text-align:center'>%s</td></tr>\n",
3984 html, basename(webkit_download_get_destination_uri(dl->download)),
3985 status_html, cmd_html);
3986 g_free(html);
3988 if (status_html)
3989 g_free(status_html);
3991 if (cmd_html)
3992 g_free(cmd_html);
3994 g_free(xtp_prefix);
3996 return new_html;
4000 * update all download tabs apart from one. Pass NULL if
4001 * you want to update all.
4003 void
4004 update_download_tabs(struct tab *apart_from)
4006 struct tab *t;
4007 if (!updating_dl_tabs) {
4008 updating_dl_tabs = 1; /* stop infinite recursion */
4009 TAILQ_FOREACH(t, &tabs, entry)
4010 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4011 && (t != apart_from))
4012 xtp_page_dl(t, NULL);
4013 updating_dl_tabs = 0;
4018 * update all cookie tabs apart from one. Pass NULL if
4019 * you want to update all.
4021 void
4022 update_cookie_tabs(struct tab *apart_from)
4024 struct tab *t;
4025 if (!updating_cl_tabs) {
4026 updating_cl_tabs = 1; /* stop infinite recursion */
4027 TAILQ_FOREACH(t, &tabs, entry)
4028 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4029 && (t != apart_from))
4030 xtp_page_cl(t, NULL);
4031 updating_cl_tabs = 0;
4036 * update all history tabs apart from one. Pass NULL if
4037 * you want to update all.
4039 void
4040 update_history_tabs(struct tab *apart_from)
4042 struct tab *t;
4044 if (!updating_hl_tabs) {
4045 updating_hl_tabs = 1; /* stop infinite recursion */
4046 TAILQ_FOREACH(t, &tabs, entry)
4047 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4048 && (t != apart_from))
4049 xtp_page_hl(t, NULL);
4050 updating_hl_tabs = 0;
4054 /* cookie management XTP page */
4056 xtp_page_cl(struct tab *t, struct karg *args)
4058 char *header, *body, *footer, *page, *tmp;
4059 int i = 1; /* all ids start 1 */
4060 GSList *sc, *pc, *pc_start;
4061 SoupCookie *c;
4062 char *type, *table_headers;
4063 char *last_domain = strdup("");
4065 DNPRINTF(XT_D_CMD, "%s", __func__);
4067 if (t == NULL) {
4068 show_oops_s("%s invalid parameters", __func__);
4069 return (1);
4071 /* mark this tab as cookie jar */
4072 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4074 /* Generate a new session key */
4075 if (!updating_cl_tabs)
4076 generate_xtp_session_key(&cl_session_key);
4078 /* header */
4079 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4080 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4081 "</head><body><h1>Cookie Jar</h1>\n");
4083 /* table headers */
4084 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4085 "<th>Type</th>"
4086 "<th>Name</th>"
4087 "<th>Value</th>"
4088 "<th>Path</th>"
4089 "<th>Expires</th>"
4090 "<th>Secure</th>"
4091 "<th>HTTP<br />only</th>"
4092 "<th>Rm</th></tr>\n");
4094 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4095 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4096 pc_start = pc;
4098 body = NULL;
4099 for (; sc; sc = sc->next) {
4100 c = sc->data;
4102 if (strcmp(last_domain, c->domain) != 0) {
4103 /* new domain */
4104 free(last_domain);
4105 last_domain = strdup(c->domain);
4107 if (body != NULL) {
4108 tmp = body;
4109 body = g_strdup_printf("%s</table></div>"
4110 "<h2>%s</h2>%s\n",
4111 body, c->domain, table_headers);
4112 g_free(tmp);
4113 } else {
4114 /* first domain */
4115 body = g_strdup_printf("<h2>%s</h2>%s\n",
4116 c->domain, table_headers);
4120 type = "Session";
4121 for (pc = pc_start; pc; pc = pc->next)
4122 if (soup_cookie_equal(pc->data, c)) {
4123 type = "Session + Persistent";
4124 break;
4127 tmp = body;
4128 body = g_strdup_printf(
4129 "%s\n<tr>"
4130 "<td style='width: text-align: center'>%s</td>"
4131 "<td style='width: 1px'>%s</td>"
4132 "<td style='width=70%%;overflow: visible'>"
4133 " <textarea rows='4'>%s</textarea>"
4134 "</td>"
4135 "<td>%s</td>"
4136 "<td>%s</td>"
4137 "<td style='width: 1px; text-align: center'>%d</td>"
4138 "<td style='width: 1px; text-align: center'>%d</td>"
4139 "<td style='width: 1px; text-align: center'>"
4140 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4141 body,
4142 type,
4143 c->name,
4144 c->value,
4145 c->path,
4146 c->expires ?
4147 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4148 c->secure,
4149 c->http_only,
4151 XT_XTP_STR,
4152 XT_XTP_CL,
4153 cl_session_key,
4154 XT_XTP_CL_REMOVE,
4158 g_free(tmp);
4159 i++;
4162 soup_cookies_free(sc);
4163 soup_cookies_free(pc);
4165 /* small message if there are none */
4166 if (i == 1) {
4167 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4168 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4171 /* footer */
4172 footer = g_strdup_printf("</table></div></body></html>");
4174 page = g_strdup_printf("%s%s%s", header, body, footer);
4176 g_free(header);
4177 g_free(body);
4178 g_free(footer);
4179 g_free(table_headers);
4180 g_free(last_domain);
4182 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4183 update_cookie_tabs(t);
4185 g_free(page);
4187 return (0);
4191 xtp_page_hl(struct tab *t, struct karg *args)
4193 char *header, *body, *footer, *page, *tmp;
4194 struct history *h;
4195 int i = 1; /* all ids start 1 */
4197 DNPRINTF(XT_D_CMD, "%s", __func__);
4199 if (t == NULL) {
4200 show_oops_s("%s invalid parameters", __func__);
4201 return (1);
4204 /* mark this tab as history manager */
4205 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4207 /* Generate a new session key */
4208 if (!updating_hl_tabs)
4209 generate_xtp_session_key(&hl_session_key);
4211 /* header */
4212 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4213 "<title>History</title>\n"
4214 "%s"
4215 "</head>"
4216 "<h1>History</h1>\n",
4217 XT_PAGE_STYLE);
4219 /* body */
4220 body = g_strdup_printf("<div align='center'><table><tr>"
4221 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4223 RB_FOREACH_REVERSE(h, history_list, &hl) {
4224 tmp = body;
4225 body = g_strdup_printf(
4226 "%s\n<tr>"
4227 "<td><a href='%s'>%s</a></td>"
4228 "<td>%s</td>"
4229 "<td style='text-align: center'>"
4230 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4231 body, h->uri, h->uri, h->title,
4232 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4233 XT_XTP_HL_REMOVE, i);
4235 g_free(tmp);
4236 i++;
4239 /* small message if there are none */
4240 if (i == 1) {
4241 tmp = body;
4242 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4243 "colspan='3'>No History</td></tr>\n", body);
4244 g_free(tmp);
4247 /* footer */
4248 footer = g_strdup_printf("</table></div></body></html>");
4250 page = g_strdup_printf("%s%s%s", header, body, footer);
4253 * update all history manager tabs as the xtp session
4254 * key has now changed. No need to update the current tab.
4255 * Already did that above.
4257 update_history_tabs(t);
4259 g_free(header);
4260 g_free(body);
4261 g_free(footer);
4263 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4264 g_free(page);
4266 return (0);
4270 * Generate a web page detailing the status of any downloads
4273 xtp_page_dl(struct tab *t, struct karg *args)
4275 struct download *dl;
4276 char *header, *body, *footer, *page, *tmp;
4277 char *ref;
4278 int n_dl = 1;
4280 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4282 if (t == NULL) {
4283 show_oops_s("%s invalid parameters", __func__);
4284 return (1);
4286 /* mark as a download manager tab */
4287 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4290 * Generate a new session key for next page instance.
4291 * This only happens for the top level call to xtp_page_dl()
4292 * in which case updating_dl_tabs is 0.
4294 if (!updating_dl_tabs)
4295 generate_xtp_session_key(&dl_session_key);
4297 /* header - with refresh so as to update */
4298 if (refresh_interval >= 1)
4299 ref = g_strdup_printf(
4300 "<meta http-equiv='refresh' content='%u"
4301 ";url=%s%d/%s/%d' />\n",
4302 refresh_interval,
4303 XT_XTP_STR,
4304 XT_XTP_DL,
4305 dl_session_key,
4306 XT_XTP_DL_LIST);
4307 else
4308 ref = g_strdup("");
4311 header = g_strdup_printf(
4312 "%s\n<head>"
4313 "<title>Downloads</title>\n%s%s</head>\n",
4314 XT_DOCTYPE XT_HTML_TAG,
4315 ref,
4316 XT_PAGE_STYLE);
4318 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4319 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4320 "</p><table><tr><th style='width: 60%%'>"
4321 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4322 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4324 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4325 body = xtp_page_dl_row(t, body, dl);
4326 n_dl++;
4329 /* message if no downloads in list */
4330 if (n_dl == 1) {
4331 tmp = body;
4332 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4333 " style='text-align: center'>"
4334 "No downloads</td></tr>\n", body);
4335 g_free(tmp);
4338 /* footer */
4339 footer = g_strdup_printf("</table></div></body></html>");
4341 page = g_strdup_printf("%s%s%s", header, body, footer);
4345 * update all download manager tabs as the xtp session
4346 * key has now changed. No need to update the current tab.
4347 * Already did that above.
4349 update_download_tabs(t);
4351 g_free(ref);
4352 g_free(header);
4353 g_free(body);
4354 g_free(footer);
4356 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4357 g_free(page);
4359 return (0);
4363 search(struct tab *t, struct karg *args)
4365 gboolean d;
4367 if (t == NULL || args == NULL) {
4368 show_oops_s("search invalid parameters");
4369 return (1);
4371 if (t->search_text == NULL) {
4372 if (global_search == NULL)
4373 return (XT_CB_PASSTHROUGH);
4374 else {
4375 t->search_text = g_strdup(global_search);
4376 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4377 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4381 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4382 t->tab_id, args->i, t->search_forward, t->search_text);
4384 switch (args->i) {
4385 case XT_SEARCH_NEXT:
4386 d = t->search_forward;
4387 break;
4388 case XT_SEARCH_PREV:
4389 d = !t->search_forward;
4390 break;
4391 default:
4392 return (XT_CB_PASSTHROUGH);
4395 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4397 return (XT_CB_HANDLED);
4400 struct settings_args {
4401 char **body;
4402 int i;
4405 void
4406 print_setting(struct settings *s, char *val, void *cb_args)
4408 char *tmp, *color;
4409 struct settings_args *sa = cb_args;
4411 if (sa == NULL)
4412 return;
4414 if (s->flags & XT_SF_RUNTIME)
4415 color = "#22cc22";
4416 else
4417 color = "#cccccc";
4419 tmp = *sa->body;
4420 *sa->body = g_strdup_printf(
4421 "%s\n<tr>"
4422 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4423 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4424 *sa->body,
4425 color,
4426 s->name,
4427 color,
4430 g_free(tmp);
4431 sa->i++;
4435 set(struct tab *t, struct karg *args)
4437 char *header, *body, *footer, *page, *tmp, *pars;
4438 int i = 1;
4439 struct settings_args sa;
4441 if ((pars = getparams(args->s, "set")) == NULL) {
4442 bzero(&sa, sizeof sa);
4443 sa.body = &body;
4445 /* header */
4446 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4447 "\n<head><title>Settings</title>\n"
4448 "</head><body><h1>Settings</h1>\n");
4450 /* body */
4451 body = g_strdup_printf("<div align='center'><table><tr>"
4452 "<th align='left'>Setting</th>"
4453 "<th align='left'>Value</th></tr>\n");
4455 settings_walk(print_setting, &sa);
4456 i = sa.i;
4458 /* small message if there are none */
4459 if (i == 1) {
4460 tmp = body;
4461 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4462 "colspan='2'>No settings</td></tr>\n", body);
4463 g_free(tmp);
4466 /* footer */
4467 footer = g_strdup_printf("</table></div></body></html>");
4469 page = g_strdup_printf("%s%s%s", header, body, footer);
4471 g_free(header);
4472 g_free(body);
4473 g_free(footer);
4475 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4476 } else
4477 show_oops(t, "Invalid command: %s", pars);
4479 return (XT_CB_PASSTHROUGH);
4483 session_save(struct tab *t, char *filename, char **ret)
4485 struct karg a;
4486 char *f = filename;
4487 int rv = 1;
4489 f += strlen("save");
4490 while (*f == ' ' && *f != '\0')
4491 f++;
4492 if (strlen(f) == 0)
4493 goto done;
4495 *ret = f;
4496 if (f[0] == '.' || f[0] == '/')
4497 goto done;
4499 a.s = f;
4500 if (save_tabs(t, &a))
4501 goto done;
4502 strlcpy(named_session, f, sizeof named_session);
4504 rv = 0;
4505 done:
4506 return (rv);
4510 session_open(struct tab *t, char *filename, char **ret)
4512 struct karg a;
4513 char *f = filename;
4514 int rv = 1;
4516 f += strlen("open");
4517 while (*f == ' ' && *f != '\0')
4518 f++;
4519 if (strlen(f) == 0)
4520 goto done;
4522 *ret = f;
4523 if (f[0] == '.' || f[0] == '/')
4524 goto done;
4526 a.s = f;
4527 a.i = XT_SES_CLOSETABS;
4528 if (open_tabs(t, &a))
4529 goto done;
4531 strlcpy(named_session, f, sizeof named_session);
4533 rv = 0;
4534 done:
4535 return (rv);
4539 session_delete(struct tab *t, char *filename, char **ret)
4541 char file[PATH_MAX];
4542 char *f = filename;
4543 int rv = 1;
4545 f += strlen("delete");
4546 while (*f == ' ' && *f != '\0')
4547 f++;
4548 if (strlen(f) == 0)
4549 goto done;
4551 *ret = f;
4552 if (f[0] == '.' || f[0] == '/')
4553 goto done;
4555 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4556 if (unlink(file))
4557 goto done;
4559 if (!strcmp(f, named_session))
4560 strlcpy(named_session, XT_SAVED_TABS_FILE,
4561 sizeof named_session);
4563 rv = 0;
4564 done:
4565 return (rv);
4569 session_cmd(struct tab *t, struct karg *args)
4571 char *action = NULL;
4572 char *filename = NULL;
4574 if (t == NULL)
4575 return (1);
4577 if ((action = getparams(args->s, "session")))
4579 else
4580 action = "show";
4582 if (!strcmp(action, "show"))
4583 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4584 XT_SAVED_TABS_FILE : named_session);
4585 else if (g_str_has_prefix(action, "save ")) {
4586 if (session_save(t, action, &filename)) {
4587 show_oops(t, "Can't save session: %s",
4588 filename ? filename : "INVALID");
4589 goto done;
4591 } else if (g_str_has_prefix(action, "open ")) {
4592 if (session_open(t, action, &filename)) {
4593 show_oops(t, "Can't open session: %s",
4594 filename ? filename : "INVALID");
4595 goto done;
4597 } else if (g_str_has_prefix(action, "delete ")) {
4598 if (session_delete(t, action, &filename)) {
4599 show_oops(t, "Can't delete session: %s",
4600 filename ? filename : "INVALID");
4601 goto done;
4603 } else
4604 show_oops(t, "Invalid command: %s", action);
4605 done:
4606 return (XT_CB_PASSTHROUGH);
4610 * Make a hardcopy of the page
4613 print_page(struct tab *t, struct karg *args)
4615 WebKitWebFrame *frame;
4616 GtkPageSetup *ps;
4617 GtkPrintOperation *op;
4618 GtkPrintOperationAction action;
4619 GtkPrintOperationResult print_res;
4620 GError *g_err = NULL;
4621 int marg_l, marg_r, marg_t, marg_b;
4623 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4625 ps = gtk_page_setup_new();
4626 op = gtk_print_operation_new();
4627 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4628 frame = webkit_web_view_get_main_frame(t->wv);
4630 /* the default margins are too small, so we will bump them */
4631 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4632 XT_PRINT_EXTRA_MARGIN;
4633 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4634 XT_PRINT_EXTRA_MARGIN;
4635 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4636 XT_PRINT_EXTRA_MARGIN;
4637 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4638 XT_PRINT_EXTRA_MARGIN;
4640 /* set margins */
4641 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4642 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4643 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4644 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4646 gtk_print_operation_set_default_page_setup(op, ps);
4648 /* this appears to free 'op' and 'ps' */
4649 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4651 /* check it worked */
4652 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4653 show_oops_s("can't print: %s", g_err->message);
4654 g_error_free (g_err);
4655 return (1);
4658 return (0);
4662 go_home(struct tab *t, struct karg *args)
4664 load_uri(t, home);
4665 return (0);
4669 restart(struct tab *t, struct karg *args)
4671 struct karg a;
4673 a.s = XT_RESTART_TABS_FILE;
4674 save_tabs(t, &a);
4675 execvp(start_argv[0], start_argv);
4676 /* NOTREACHED */
4678 return (0);
4681 #define CTRL GDK_CONTROL_MASK
4682 #define MOD1 GDK_MOD1_MASK
4683 #define SHFT GDK_SHIFT_MASK
4685 /* inherent to GTK not all keys will be caught at all times */
4686 /* XXX sort key bindings */
4687 struct key_binding {
4688 char *name;
4689 guint mask;
4690 guint use_in_entry;
4691 guint key;
4692 struct karg arg;
4693 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4694 } keys[] = {
4695 { "cookiejar", MOD1, 0, GDK_j, {0} },
4696 { "downloadmgr", MOD1, 0, GDK_d, {0} },
4697 { "history", MOD1, 0, GDK_h, {0} },
4698 { "print", CTRL, 0, GDK_p, {0} },
4699 { "search", 0, 0, GDK_slash, {0} },
4700 { "searchb", 0, 0, GDK_question, {0} },
4701 { "command", 0, 0, GDK_colon, {0} },
4702 { "quit", CTRL, 0, GDK_q, {0} },
4703 { "restart", MOD1, 0, GDK_q, {0} },
4704 { "togglejs", CTRL, 0, GDK_j, {0} }, /* XXX broken */
4705 { "togglecookie", MOD1, 0, GDK_c, {0} }, /* XXX broken */
4706 { "togglesrc", CTRL, 0, GDK_s, {0} },
4707 { "yankuri", 0, 0, GDK_y, {0} },
4708 { "pasteuricur", 0, 0, GDK_p, {0} },
4709 { "pasteurinew", 0, 0, GDK_P, {0} },
4711 /* search */
4712 { "searchnext", 0, 0, GDK_n, {0} },
4713 { "searchprevious", 0, 0, GDK_N, {0} },
4715 /* focus */
4716 { "focusaddress", 0, 0, GDK_F6, {0} },
4717 { "focussearch", 0, 0, GDK_F7, {0} },
4719 /* hinting */
4720 { "hinting", 0, 0, GDK_f, {0} },
4722 /* custom stylesheet */
4723 { "userstyle", 0, 0, GDK_i, {0} },
4725 /* navigation */
4726 { "goback", 0, 0, GDK_BackSpace, {0} },
4727 { "goback", MOD1, 0, GDK_Left, {0} },
4728 { "goforward", SHFT, 0, GDK_BackSpace, {0} },
4729 { "goforward", MOD1, 0, GDK_Right, {0} },
4730 { "reload", 0, 0, GDK_F5, {0} },
4731 { "reload", CTRL, 0, GDK_r, {0} },
4732 { "reloadforce", CTRL, 0, GDK_R, {0} },
4733 { "reload", CTRL, 0, GDK_l, {0} },
4734 { "favorites", MOD1, 1, GDK_f, {0} },
4736 /* vertical movement */
4737 { "scrolldown", 0, 0, GDK_j, {0} },
4738 { "scrolldown", 0, 0, GDK_Down, {0} },
4739 { "scrollup", 0, 0, GDK_Up, {0} },
4740 { "scrollup", 0, 0, GDK_k, {0} },
4741 { "scrollbottom", 0, 0, GDK_G, {0} },
4742 { "scrollbottom", 0, 0, GDK_End, {0} },
4743 { "scrolltop", 0, 0, GDK_Home, {0} },
4744 { "scrolltop", 0, 0, GDK_g, {0} },
4745 { "scrollpagedown", 0, 0, GDK_space, {0} },
4746 { "scrollpagedown", CTRL, 0, GDK_f, {0} },
4747 { "scrollhalfdown", CTRL, 0, GDK_d, {0} },
4748 { "scrollpagedown", 0, 0, GDK_Page_Down, {0} },
4749 { "scrollpageup", 0, 0, GDK_Page_Up, {0} },
4750 { "scrollpageup", CTRL, 0, GDK_b, {0} },
4751 { "scrollhalfup", CTRL, 0, GDK_u, {0} },
4752 /* horizontal movement */
4753 { "scrollright", 0, 0, GDK_l, {0} },
4754 { "scrollright", 0, 0, GDK_Right, {0} },
4755 { "scrollleft", 0, 0, GDK_Left, {0} },
4756 { "scrollleft", 0, 0, GDK_h, {0} },
4757 { "scrollfarright", 0, 0, GDK_dollar, {0} },
4758 { "scrollfarleft", 0, 0, GDK_0, {0} },
4760 /* tabs */
4761 { "tabnew", CTRL, 0, GDK_t, {0} },
4762 { "tabclose", CTRL, 1, GDK_w, {0} },
4763 { "tabundoclose", 0, 0, GDK_U, {0} },
4764 { "tabgoto1", CTRL, 0, GDK_1, {0} },
4765 { "tabgoto2", CTRL, 0, GDK_2, {0} },
4766 { "tabgoto3", CTRL, 0, GDK_3, {0} },
4767 { "tabgoto4", CTRL, 0, GDK_4, {0} },
4768 { "tabgoto5", CTRL, 0, GDK_5, {0} },
4769 { "tabgoto6", CTRL, 0, GDK_6, {0} },
4770 { "tabgoto7", CTRL, 0, GDK_7, {0} },
4771 { "tabgoto8", CTRL, 0, GDK_8, {0} },
4772 { "tabgoto9", CTRL, 0, GDK_9, {0} },
4773 { "tabgoto10", CTRL, 0, GDK_0, {0} },
4774 { "tabfirst", CTRL, 0, GDK_less, {0} },
4775 { "tablast", CTRL, 0, GDK_greater, {0} },
4776 { "tabprevious", CTRL, 0, GDK_Left, {0} },
4777 { "tabnext", CTRL, 0, GDK_Right, {0} },
4778 { "focusout", CTRL, 0, GDK_minus, {0} },
4779 { "focusin", CTRL, 0, GDK_plus, {0} },
4780 { "focusin", CTRL, 0, GDK_equal, {0} },
4782 /* command aliases (handy when -S flag is used) */
4783 { "promptopen", 0, 0, GDK_F9, {0} },
4784 { "promptopencurrent", 0, 0, GDK_F10, {0} },
4785 { "prompttabnew", 0, 0, GDK_F11, {0} },
4786 { "prompttabnewcurrent",0, 0, GDK_F12, {0} },
4788 TAILQ_HEAD(keybinding_list, key_binding);
4790 void
4791 walk_kb(struct settings *s,
4792 void (*cb)(struct settings *, char *, void *), void *cb_args)
4794 struct key_binding *k;
4795 char str[1024];
4797 if (s == NULL || cb == NULL) {
4798 show_oops_s("walk_kb invalid parameters");
4799 return;
4802 TAILQ_FOREACH(k, &kbl, entry) {
4803 if (k->name == NULL)
4804 continue;
4805 str[0] = '\0';
4807 /* sanity */
4808 if (gdk_keyval_name(k->key) == NULL)
4809 continue;
4811 strlcat(str, k->name, sizeof str);
4812 strlcat(str, ",", sizeof str);
4814 if (k->mask & GDK_SHIFT_MASK)
4815 strlcat(str, "S-", sizeof str);
4816 if (k->mask & GDK_CONTROL_MASK)
4817 strlcat(str, "C-", sizeof str);
4818 if (k->mask & GDK_MOD1_MASK)
4819 strlcat(str, "M1-", sizeof str);
4820 if (k->mask & GDK_MOD2_MASK)
4821 strlcat(str, "M2-", sizeof str);
4822 if (k->mask & GDK_MOD3_MASK)
4823 strlcat(str, "M3-", sizeof str);
4824 if (k->mask & GDK_MOD4_MASK)
4825 strlcat(str, "M4-", sizeof str);
4826 if (k->mask & GDK_MOD5_MASK)
4827 strlcat(str, "M5-", sizeof str);
4829 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4830 cb(s, str, cb_args);
4833 void
4834 init_keybindings(void)
4836 int i;
4837 struct key_binding *k;
4839 for (i = 0; i < LENGTH(keys); i++) {
4840 k = g_malloc0(sizeof *k);
4841 k->name = keys[i].name;
4842 k->mask = keys[i].mask;
4843 k->use_in_entry = keys[i].use_in_entry;
4844 k->key = keys[i].key;
4845 bcopy(&keys[i].arg, &k->arg, sizeof k->arg);
4846 TAILQ_INSERT_HEAD(&kbl, k, entry);
4848 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4849 k->name ? k->name : "unnamed key");
4853 void
4854 keybinding_clearall(void)
4856 struct key_binding *k, *next;
4858 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4859 next = TAILQ_NEXT(k, entry);
4860 if (k->name == NULL)
4861 continue;
4863 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4864 k->name ? k->name : "unnamed key");
4865 TAILQ_REMOVE(&kbl, k, entry);
4866 g_free(k);
4871 keybinding_add(char *kb, char *value, struct key_binding *orig)
4873 struct key_binding *k;
4874 guint keyval, mask = 0;
4875 int i;
4877 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->name);
4879 if (orig == NULL)
4880 return (1);
4881 if (strcmp(kb, orig->name))
4882 return (1);
4884 /* find modifier keys */
4885 if (strstr(value, "S-"))
4886 mask |= GDK_SHIFT_MASK;
4887 if (strstr(value, "C-"))
4888 mask |= GDK_CONTROL_MASK;
4889 if (strstr(value, "M1-"))
4890 mask |= GDK_MOD1_MASK;
4891 if (strstr(value, "M2-"))
4892 mask |= GDK_MOD2_MASK;
4893 if (strstr(value, "M3-"))
4894 mask |= GDK_MOD3_MASK;
4895 if (strstr(value, "M4-"))
4896 mask |= GDK_MOD4_MASK;
4897 if (strstr(value, "M5-"))
4898 mask |= GDK_MOD5_MASK;
4900 /* find keyname */
4901 for (i = strlen(value) - 1; i > 0; i--)
4902 if (value[i] == '-')
4903 value = &value[i + 1];
4905 /* validate keyname */
4906 keyval = gdk_keyval_from_name(value);
4907 if (keyval == GDK_VoidSymbol) {
4908 warnx("invalid keybinding name %s", value);
4909 return (1);
4911 /* must run this test too, gtk+ doesn't handle 10 for example */
4912 if (gdk_keyval_name(keyval) == NULL) {
4913 warnx("invalid keybinding name %s", value);
4914 return (1);
4917 /* make sure it isn't a dupe */
4918 TAILQ_FOREACH(k, &kbl, entry)
4919 if (k->key == keyval && k->mask == mask) {
4920 warnx("duplicate keybinding for %s", value);
4921 return (1);
4924 /* add keyname */
4925 k = g_malloc0(sizeof *k);
4926 k->name = orig->name;
4927 k->mask = mask;
4928 k->use_in_entry = orig->use_in_entry;
4929 k->key = keyval;
4930 bcopy(&orig->arg, &k->arg, sizeof k->arg);
4932 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4933 k->name,
4934 k->mask,
4935 k->use_in_entry,
4936 k->key);
4937 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4938 k->name, gdk_keyval_name(keyval));
4940 TAILQ_INSERT_HEAD(&kbl, k, entry);
4942 return (0);
4946 add_kb(struct settings *s, char *entry)
4948 int i;
4949 char *kb, *value;
4951 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4953 /* clearall is special */
4954 if (!strcmp(entry, "clearall")) {
4955 keybinding_clearall();
4956 return (0);
4959 kb = strstr(entry, ",");
4960 if (kb == NULL)
4961 return (1);
4962 *kb = '\0';
4963 value = kb + 1;
4965 /* make sure it is a valid keybinding */
4966 for (i = 0; i < LENGTH(keys); i++)
4967 if (keys[i].name && !strcmp(entry, keys[i].name)) {
4968 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4969 keys[i].name,
4970 keys[i].mask,
4971 keys[i].use_in_entry,
4972 keys[i].key);
4974 return (keybinding_add(entry, value, &keys[i]));
4977 return (1);
4980 struct cmd {
4981 char *cmd;
4982 int params;
4983 int (*func)(struct tab *, struct karg *);
4984 struct karg arg;
4985 bool userarg; /* allow free text arg */
4986 } cmds[] = {
4987 { "command", 0, command, {.i = ':'}, FALSE },
4988 { "search", 0, command, {.i = '/'}, FALSE },
4989 { "searchb", 0, command, {.i = '?'}, FALSE },
4990 { "togglesrc", 0, toggle_src, {0}, FALSE },
4992 /* yanking and pasting */
4993 { "yankuri", 0, yank_uri, {0}, FALSE },
4994 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4995 { "pasteuricur", 0, paste_uri, {.i = XT_PASTE_CURRENT_TAB}, FALSE },
4996 { "pasteurinew", 0, paste_uri, {.i = XT_PASTE_NEW_TAB}, FALSE },
4998 /* search */
4999 { "searchnext", 0, search, {.i = XT_SEARCH_NEXT}, FALSE },
5000 { "searchprevious", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
5001 { "searchprev", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
5003 /* focus */
5004 { "focusaddress", 0, focus, {.i = XT_FOCUS_URI}, FALSE },
5005 { "focussearch", 0, focus, {.i = XT_FOCUS_SEARCH}, FALSE },
5007 /* hinting */
5008 { "hinting", 0, hint, {.i = 0}, FALSE },
5010 /* custom stylesheet */
5011 { "userstyle", 0, userstyle, {.i = 0 }, FALSE },
5013 /* navigation */
5014 { "goback", 0, navaction, {.i = XT_NAV_BACK}, FALSE },
5015 { "goforward", 0, navaction, {.i = XT_NAV_FORWARD}, FALSE },
5016 { "reload", 0, navaction, {.i = XT_NAV_RELOAD}, FALSE },
5017 { "reloadforce", 0, navaction, {.i = XT_NAV_RELOAD_CACHE}, FALSE },
5019 /* vertical movement */
5020 { "scrolldown", 0, move, {.i = XT_MOVE_DOWN}, FALSE },
5021 { "scrollup", 0, move, {.i = XT_MOVE_UP}, FALSE },
5022 { "scrollbottom", 0, move, {.i = XT_MOVE_BOTTOM}, FALSE },
5023 { "scrolltop", 0, move, {.i = XT_MOVE_TOP}, FALSE },
5024 { "1", 0, move, {.i = XT_MOVE_TOP}, FALSE },
5025 { "scrollhalfdown", 0, move, {.i = XT_MOVE_HALFDOWN},FALSE },
5026 { "scrollhalfup", 0, move, {.i = XT_MOVE_HALFUP}, FALSE },
5027 { "scrollpagedown", 0, move, {.i = XT_MOVE_PAGEDOWN},FALSE },
5028 { "scrollpageup", 0, move, {.i = XT_MOVE_PAGEUP}, FALSE },
5029 /* horizontal movement */
5030 { "scrollright", 0, move, {.i = XT_MOVE_RIGHT}, FALSE },
5031 { "scrollleft", 0, move, {.i = XT_MOVE_LEFT}, FALSE },
5032 { "scrollfarright", 0, move, {.i = XT_MOVE_FARRIGHT},FALSE },
5033 { "scrollfarleft", 0, move, {.i = XT_MOVE_FARLEFT}, FALSE },
5036 { "favorites", 0, xtp_page_fl, {0}, FALSE },
5037 { "fav", 0, xtp_page_fl, {0}, FALSE },
5038 { "favadd", 0, add_favorite, {0}, FALSE },
5040 { "quit", 0, quit, {0}, FALSE },
5041 { "q!", 0, quit, {0}, FALSE },
5042 { "qa", 0, quit, {0}, FALSE },
5043 { "qa!", 0, quit, {0}, FALSE },
5044 { "w", 0, save_tabs, {0}, FALSE },
5045 { "wq", 0, save_tabs_and_quit, {0}, FALSE },
5046 { "wq!", 0, save_tabs_and_quit, {0}, FALSE },
5047 { "help", 0, help, {0}, FALSE },
5048 { "about", 0, about, {0}, FALSE },
5049 { "stats", 0, stats, {0}, FALSE },
5050 { "version", 0, about, {0}, FALSE },
5051 { "cookiejar", 0, xtp_page_cl, {0}, FALSE },
5053 /* js command */
5054 { "js", 0, js_cmd, {0}, FALSE },
5055 { "save", 1, js_cmd, {0}, FALSE },
5056 { "domain", 2, js_cmd, {0}, FALSE },
5057 { "fqdn", 2, js_cmd, {0}, FALSE },
5058 { "toggle", 1, js_cmd, {0}, FALSE },
5059 { "domain", 2, js_cmd, {0}, FALSE },
5060 { "fqdn", 2, js_cmd, {0}, FALSE },
5061 { "show", 1, js_cmd, {0}, FALSE },
5062 { "all", 2, js_cmd, {0}, FALSE },
5063 { "persistent", 2, js_cmd, {0}, FALSE },
5064 { "session", 2, js_cmd, {0}, FALSE },
5066 /* cookie command */
5067 { "cookie", 0, cookie_cmd, {0}, FALSE },
5068 { "show", 1, cookie_cmd, {0}, FALSE },
5069 { "all", 2, cookie_cmd, {0}, FALSE },
5070 { "persistent", 2, cookie_cmd, {0}, FALSE },
5071 { "session", 2, cookie_cmd, {0}, FALSE },
5072 { "save", 1, cookie_cmd, {0}, FALSE },
5073 { "fqdn", 2, cookie_cmd, {0}, FALSE },
5074 { "domain", 2, cookie_cmd, {0}, FALSE },
5075 { "toggle", 1, cookie_cmd, {0}, FALSE },
5076 { "domain", 2, cookie_cmd, {0}, FALSE },
5077 { "fqdn", 2, cookie_cmd, {0}, FALSE },
5079 /* cert command */
5080 { "cert", 0, cert_cmd, {0}, FALSE },
5081 { "show", 1, cert_cmd, {0}, FALSE },
5082 { "save", 1, cert_cmd, {0}, FALSE },
5084 { "ca", 0, ca_cmd, {0}, FALSE },
5085 { "downloadmgr", 0, xtp_page_dl, {0}, FALSE },
5086 { "dl", 0, xtp_page_dl, {0}, FALSE },
5087 { "h", 0, xtp_page_hl, {0}, FALSE },
5088 { "hist", 0, xtp_page_hl, {0}, FALSE },
5089 { "history", 0, xtp_page_hl, {0}, FALSE },
5090 { "home", 0, go_home, {0}, FALSE },
5091 { "restart", 0, restart, {0}, FALSE },
5092 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5093 { "urlh", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5094 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5095 { "urls", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5096 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5097 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5098 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5099 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5101 { "print", 0, print_page, {0}, FALSE },
5103 /* tabs */
5104 { "o", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5105 { "op", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5106 { "open", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5107 { "tabnew", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5108 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5109 { "tabe", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5110 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5111 { "tabundoclose", 0, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
5112 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5113 { "tabshow", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5114 { "tabs", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5115 { "tabhide", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5116 { "tabh", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5117 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5118 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5119 /* XXX add count to these commands */
5120 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5121 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5122 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5123 { "tabr", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5124 { "tablast", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5125 { "tabl", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5126 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5127 { "tabprev", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5128 { "tabp", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5129 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT}, FALSE },
5130 { "tabn", 0, movetab, {.i = XT_TAB_NEXT}, FALSE },
5131 { "tabgoto1", 0, movetab, {.i = 1}, FALSE },
5132 { "tabgoto2", 0, movetab, {.i = 2}, FALSE },
5133 { "tabgoto3", 0, movetab, {.i = 3}, FALSE },
5134 { "tabgoto4", 0, movetab, {.i = 4}, FALSE },
5135 { "tabgoto5", 0, movetab, {.i = 5}, FALSE },
5136 { "tabgoto6", 0, movetab, {.i = 6}, FALSE },
5137 { "tabgoto7", 0, movetab, {.i = 7}, FALSE },
5138 { "tabgoto8", 0, movetab, {.i = 8}, FALSE },
5139 { "tabgoto9", 0, movetab, {.i = 9}, FALSE },
5140 { "tabgoto10", 0, movetab, {.i = 10}, FALSE },
5141 { "focusout", 0, resizetab, {.i = -1}, FALSE },
5142 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5143 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5145 /* command aliases (handy when -S flag is used) */
5146 { "promptopen", 0, command, {.i = XT_CMD_OPEN}, FALSE },
5147 { "promptopencurrent", 0, command, {.i = XT_CMD_OPEN_CURRENT}, FALSE },
5148 { "prompttabnew", 0, command, {.i = XT_CMD_TABNEW}, FALSE },
5149 { "prompttabnewcurrent",0, command, {.i = XT_CMD_TABNEW_CURRENT}, FALSE },
5151 /* settings */
5152 { "set", 0, set, {0}, FALSE },
5153 { "fullscreen", 0, fullscreen, {0}, FALSE },
5154 { "f", 0, fullscreen, {0}, FALSE },
5156 /* sessions */
5157 { "session", 0, session_cmd, {0}, FALSE },
5158 { "show", 1, session_cmd, {0}, FALSE },
5159 { "delete", 1, session_cmd, {0}, TRUE },
5160 { "open", 1, session_cmd, {0}, TRUE },
5161 { "save", 1, session_cmd, {0}, TRUE },
5164 struct {
5165 int index;
5166 int len;
5167 gchar *list[256];
5168 } cmd_status = {-1, 0};
5170 gboolean
5171 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5173 struct karg a;
5175 hide_oops(t);
5177 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5178 /* go backward */
5179 a.i = XT_NAV_BACK;
5180 navaction(t, &a);
5182 return (TRUE);
5183 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5184 /* go forward */
5185 a.i = XT_NAV_FORWARD;
5186 navaction(t, &a);
5188 return (TRUE);
5191 return (FALSE);
5194 gboolean
5195 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5197 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5199 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5200 delete_tab(t);
5202 return (FALSE);
5206 * cancel, remove, etc. downloads
5208 void
5209 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5211 struct download find, *d;
5213 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5215 /* some commands require a valid download id */
5216 if (cmd != XT_XTP_DL_LIST) {
5217 /* lookup download in question */
5218 find.id = id;
5219 d = RB_FIND(download_list, &downloads, &find);
5221 if (d == NULL) {
5222 show_oops(t, "%s: no such download", __func__);
5223 return;
5227 /* decide what to do */
5228 switch (cmd) {
5229 case XT_XTP_DL_CANCEL:
5230 webkit_download_cancel(d->download);
5231 break;
5232 case XT_XTP_DL_REMOVE:
5233 webkit_download_cancel(d->download); /* just incase */
5234 g_object_unref(d->download);
5235 RB_REMOVE(download_list, &downloads, d);
5236 break;
5237 case XT_XTP_DL_LIST:
5238 /* Nothing */
5239 break;
5240 default:
5241 show_oops(t, "%s: unknown command", __func__);
5242 break;
5244 xtp_page_dl(t, NULL);
5248 * Actions on history, only does one thing for now, but
5249 * we provide the function for future actions
5251 void
5252 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5254 struct history *h, *next;
5255 int i = 1;
5257 switch (cmd) {
5258 case XT_XTP_HL_REMOVE:
5259 /* walk backwards, as listed in reverse */
5260 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5261 next = RB_PREV(history_list, &hl, h);
5262 if (id == i) {
5263 RB_REMOVE(history_list, &hl, h);
5264 g_free((gpointer) h->title);
5265 g_free((gpointer) h->uri);
5266 g_free(h);
5267 break;
5269 i++;
5271 break;
5272 case XT_XTP_HL_LIST:
5273 /* Nothing - just xtp_page_hl() below */
5274 break;
5275 default:
5276 show_oops(t, "%s: unknown command", __func__);
5277 break;
5280 xtp_page_hl(t, NULL);
5283 /* remove a favorite */
5284 void
5285 remove_favorite(struct tab *t, int index)
5287 char file[PATH_MAX], *title, *uri;
5288 char *new_favs, *tmp;
5289 FILE *f;
5290 int i;
5291 size_t len, lineno;
5293 /* open favorites */
5294 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5296 if ((f = fopen(file, "r")) == NULL) {
5297 show_oops(t, "%s: can't open favorites: %s",
5298 __func__, strerror(errno));
5299 return;
5302 /* build a string which will become the new favroites file */
5303 new_favs = g_strdup_printf("%s", "");
5305 for (i = 1;;) {
5306 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5307 if (feof(f) || ferror(f))
5308 break;
5309 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5310 if (len == 0) {
5311 free(title);
5312 title = NULL;
5313 continue;
5316 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5317 if (feof(f) || ferror(f)) {
5318 show_oops(t, "%s: can't parse favorites %s",
5319 __func__, strerror(errno));
5320 goto clean;
5324 /* as long as this isn't the one we are deleting add to file */
5325 if (i != index) {
5326 tmp = new_favs;
5327 new_favs = g_strdup_printf("%s%s\n%s\n",
5328 new_favs, title, uri);
5329 g_free(tmp);
5332 free(uri);
5333 uri = NULL;
5334 free(title);
5335 title = NULL;
5336 i++;
5338 fclose(f);
5340 /* write back new favorites file */
5341 if ((f = fopen(file, "w")) == NULL) {
5342 show_oops(t, "%s: can't open favorites: %s",
5343 __func__, strerror(errno));
5344 goto clean;
5347 fwrite(new_favs, strlen(new_favs), 1, f);
5348 fclose(f);
5350 clean:
5351 if (uri)
5352 free(uri);
5353 if (title)
5354 free(title);
5356 g_free(new_favs);
5359 void
5360 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5362 switch (cmd) {
5363 case XT_XTP_FL_LIST:
5364 /* nothing, just the below call to xtp_page_fl() */
5365 break;
5366 case XT_XTP_FL_REMOVE:
5367 remove_favorite(t, arg);
5368 break;
5369 default:
5370 show_oops(t, "%s: invalid favorites command", __func__);
5371 break;
5374 xtp_page_fl(t, NULL);
5377 void
5378 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5380 switch (cmd) {
5381 case XT_XTP_CL_LIST:
5382 /* nothing, just xtp_page_cl() */
5383 break;
5384 case XT_XTP_CL_REMOVE:
5385 remove_cookie(arg);
5386 break;
5387 default:
5388 show_oops(t, "%s: unknown cookie xtp command", __func__);
5389 break;
5392 xtp_page_cl(t, NULL);
5395 /* link an XTP class to it's session key and handler function */
5396 struct xtp_despatch {
5397 uint8_t xtp_class;
5398 char **session_key;
5399 void (*handle_func)(struct tab *, uint8_t, int);
5402 struct xtp_despatch xtp_despatches[] = {
5403 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5404 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5405 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5406 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5407 { NULL, NULL, NULL }
5411 * is the url xtp protocol? (xxxt://)
5412 * if so, parse and despatch correct bahvior
5415 parse_xtp_url(struct tab *t, const char *url)
5417 char *dup = NULL, *p, *last;
5418 uint8_t n_tokens = 0;
5419 char *tokens[4] = {NULL, NULL, NULL, ""};
5420 struct xtp_despatch *dsp, *dsp_match = NULL;
5421 uint8_t req_class;
5424 * tokens array meaning:
5425 * tokens[0] = class
5426 * tokens[1] = session key
5427 * tokens[2] = action
5428 * tokens[3] = optional argument
5431 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5433 /*xtp tab meaning is normal unless proven special */
5434 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5436 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5437 return 0;
5439 dup = g_strdup(url + strlen(XT_XTP_STR));
5441 /* split out the url */
5442 for ((p = strtok_r(dup, "/", &last)); p;
5443 (p = strtok_r(NULL, "/", &last))) {
5444 if (n_tokens < 4)
5445 tokens[n_tokens++] = p;
5448 /* should be atleast three fields 'class/seskey/command/arg' */
5449 if (n_tokens < 3)
5450 goto clean;
5452 dsp = xtp_despatches;
5453 req_class = atoi(tokens[0]);
5454 while (dsp->xtp_class != NULL) {
5455 if (dsp->xtp_class == req_class) {
5456 dsp_match = dsp;
5457 break;
5459 dsp++;
5462 /* did we find one atall? */
5463 if (dsp_match == NULL) {
5464 show_oops(t, "%s: no matching xtp despatch found", __func__);
5465 goto clean;
5468 /* check session key and call despatch function */
5469 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5470 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5473 clean:
5474 if (dup)
5475 g_free(dup);
5477 return 1;
5482 void
5483 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5485 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5487 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5489 if (t == NULL) {
5490 show_oops_s("activate_uri_entry_cb invalid parameters");
5491 return;
5494 if (uri == NULL) {
5495 show_oops(t, "activate_uri_entry_cb no uri");
5496 return;
5499 uri += strspn(uri, "\t ");
5501 /* if xxxt:// treat specially */
5502 if (!parse_xtp_url(t, uri)) {
5503 load_uri(t, (gchar *)uri);
5504 focus_webview(t);
5508 void
5509 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5511 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5512 char *newuri = NULL;
5513 gchar *enc_search;
5515 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5517 if (t == NULL) {
5518 show_oops_s("activate_search_entry_cb invalid parameters");
5519 return;
5522 if (search_string == NULL) {
5523 show_oops(t, "no search_string");
5524 return;
5527 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5528 newuri = g_strdup_printf(search_string, enc_search);
5529 g_free(enc_search);
5531 webkit_web_view_load_uri(t->wv, newuri);
5532 focus_webview(t);
5534 if (newuri)
5535 g_free(newuri);
5538 void
5539 check_and_set_js(const gchar *uri, struct tab *t)
5541 struct domain *d = NULL;
5542 int es = 0;
5544 if (uri == NULL || t == NULL)
5545 return;
5547 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5548 es = 0;
5549 else
5550 es = 1;
5552 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5553 es ? "enable" : "disable", uri);
5555 g_object_set(G_OBJECT(t->settings),
5556 "enable-scripts", es, (char *)NULL);
5557 g_object_set(G_OBJECT(t->settings),
5558 "javascript-can-open-windows-automatically", es, (char *)NULL);
5559 webkit_web_view_set_settings(t->wv, t->settings);
5561 button_set_stockid(t->js_toggle,
5562 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5565 void
5566 show_ca_status(struct tab *t, const char *uri)
5568 WebKitWebFrame *frame;
5569 WebKitWebDataSource *source;
5570 WebKitNetworkRequest *request;
5571 SoupMessage *message;
5572 GdkColor color;
5573 gchar *col_str = XT_COLOR_WHITE;
5574 int r;
5576 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5577 ssl_strict_certs, ssl_ca_file, uri);
5579 if (uri == NULL)
5580 goto done;
5581 if (ssl_ca_file == NULL) {
5582 if (g_str_has_prefix(uri, "http://"))
5583 goto done;
5584 if (g_str_has_prefix(uri, "https://")) {
5585 col_str = XT_COLOR_RED;
5586 goto done;
5588 return;
5590 if (g_str_has_prefix(uri, "http://") ||
5591 !g_str_has_prefix(uri, "https://"))
5592 goto done;
5594 frame = webkit_web_view_get_main_frame(t->wv);
5595 source = webkit_web_frame_get_data_source(frame);
5596 request = webkit_web_data_source_get_request(source);
5597 message = webkit_network_request_get_message(request);
5599 if (message && (soup_message_get_flags(message) &
5600 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5601 col_str = XT_COLOR_GREEN;
5602 goto done;
5603 } else {
5604 r = load_compare_cert(t, NULL);
5605 if (r == 0)
5606 col_str = XT_COLOR_BLUE;
5607 else if (r == 1)
5608 col_str = XT_COLOR_YELLOW;
5609 else
5610 col_str = XT_COLOR_RED;
5611 goto done;
5613 done:
5614 if (col_str) {
5615 gdk_color_parse(col_str, &color);
5616 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5618 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5619 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5620 &color);
5621 gdk_color_parse(XT_COLOR_BLACK, &color);
5622 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5623 &color);
5624 } else {
5625 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5626 &color);
5627 gdk_color_parse(XT_COLOR_BLACK, &color);
5628 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5629 &color);
5634 void
5635 free_favicon(struct tab *t)
5637 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5638 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5640 if (t->icon_request)
5641 g_object_unref(t->icon_request);
5642 if (t->icon_pixbuf)
5643 g_object_unref(t->icon_pixbuf);
5644 if (t->icon_dest_uri)
5645 g_free(t->icon_dest_uri);
5647 t->icon_pixbuf = NULL;
5648 t->icon_request = NULL;
5649 t->icon_dest_uri = NULL;
5652 void
5653 xt_icon_from_name(struct tab *t, gchar *name)
5655 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5656 GTK_ENTRY_ICON_PRIMARY, "text-html");
5657 if (show_url == 0)
5658 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5659 GTK_ENTRY_ICON_PRIMARY, "text-html");
5660 else
5661 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5662 GTK_ENTRY_ICON_PRIMARY, NULL);
5665 void
5666 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5668 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5669 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5670 if (show_url == 0)
5671 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5672 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5673 else
5674 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5675 GTK_ENTRY_ICON_PRIMARY, NULL);
5678 gboolean
5679 is_valid_icon(char *file)
5681 gboolean valid = 0;
5682 const char *mime_type;
5683 GFileInfo *fi;
5684 GFile *gf;
5686 gf = g_file_new_for_path(file);
5687 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5688 NULL, NULL);
5689 mime_type = g_file_info_get_content_type(fi);
5690 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5691 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5692 g_strcmp0(mime_type, "image/png") == 0 ||
5693 g_strcmp0(mime_type, "image/gif") == 0 ||
5694 g_strcmp0(mime_type, "application/octet-stream") == 0;
5695 g_object_unref(fi);
5696 g_object_unref(gf);
5698 return (valid);
5701 void
5702 set_favicon_from_file(struct tab *t, char *file)
5704 gint width, height;
5705 GdkPixbuf *pixbuf, *scaled;
5706 struct stat sb;
5708 if (t == NULL || file == NULL)
5709 return;
5710 if (t->icon_pixbuf) {
5711 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5712 return;
5715 if (g_str_has_prefix(file, "file://"))
5716 file += strlen("file://");
5717 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5719 if (!stat(file, &sb)) {
5720 if (sb.st_size == 0 || !is_valid_icon(file)) {
5721 /* corrupt icon so trash it */
5722 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5723 __func__, file);
5724 unlink(file);
5725 /* no need to set icon to default here */
5726 return;
5730 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5731 if (pixbuf == NULL) {
5732 xt_icon_from_name(t, "text-html");
5733 return;
5736 g_object_get(pixbuf, "width", &width, "height", &height,
5737 (char *)NULL);
5738 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5739 __func__, t->tab_id, width, height);
5741 if (width > 16 || height > 16) {
5742 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5743 GDK_INTERP_BILINEAR);
5744 g_object_unref(pixbuf);
5745 } else
5746 scaled = pixbuf;
5748 if (scaled == NULL) {
5749 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5750 GDK_INTERP_BILINEAR);
5751 return;
5754 t->icon_pixbuf = scaled;
5755 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5758 void
5759 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5760 WebKitWebView *wv)
5762 WebKitDownloadStatus status = webkit_download_get_status(download);
5763 struct tab *tt = NULL, *t = NULL;
5766 * find the webview instead of passing in the tab as it could have been
5767 * deleted from underneath us.
5769 TAILQ_FOREACH(tt, &tabs, entry) {
5770 if (tt->wv == wv) {
5771 t = tt;
5772 break;
5775 if (t == NULL)
5776 return;
5778 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5779 __func__, t->tab_id, status);
5781 switch (status) {
5782 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5783 /* -1 */
5784 t->icon_download = NULL;
5785 free_favicon(t);
5786 break;
5787 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5788 /* 0 */
5789 break;
5790 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5791 /* 1 */
5792 break;
5793 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5794 /* 2 */
5795 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5796 __func__, t->tab_id);
5797 t->icon_download = NULL;
5798 free_favicon(t);
5799 break;
5800 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5801 /* 3 */
5803 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5804 __func__, t->icon_dest_uri);
5805 set_favicon_from_file(t, t->icon_dest_uri);
5806 /* these will be freed post callback */
5807 t->icon_request = NULL;
5808 t->icon_download = NULL;
5809 break;
5810 default:
5811 break;
5815 void
5816 abort_favicon_download(struct tab *t)
5818 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5820 if (t->icon_download) {
5821 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5822 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5823 webkit_download_cancel(t->icon_download);
5824 t->icon_download = NULL;
5825 } else
5826 free_favicon(t);
5828 xt_icon_from_name(t, "text-html");
5831 void
5832 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5834 gchar *name_hash, file[PATH_MAX];
5835 struct stat sb;
5837 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5839 if (uri == NULL || t == NULL)
5840 return;
5842 if (t->icon_request) {
5843 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5844 return;
5847 /* check to see if we got the icon in cache */
5848 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5849 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5850 g_free(name_hash);
5852 if (!stat(file, &sb)) {
5853 if (sb.st_size > 0) {
5854 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5855 __func__, file);
5856 set_favicon_from_file(t, file);
5857 return;
5860 /* corrupt icon so trash it */
5861 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5862 __func__, file);
5863 unlink(file);
5866 /* create download for icon */
5867 t->icon_request = webkit_network_request_new(uri);
5868 if (t->icon_request == NULL) {
5869 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5870 __func__, uri);
5871 return;
5874 t->icon_download = webkit_download_new(t->icon_request);
5875 if (t->icon_download == NULL) {
5876 fprintf(stderr, "%s: icon_download", __func__);
5877 return;
5880 /* we have to free icon_dest_uri later */
5881 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5882 webkit_download_set_destination_uri(t->icon_download,
5883 t->icon_dest_uri);
5885 if (webkit_download_get_status(t->icon_download) ==
5886 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5887 fprintf(stderr, "%s: download failed to start", __func__);
5888 g_object_unref(t->icon_request);
5889 g_free(t->icon_dest_uri);
5890 t->icon_request = NULL;
5891 t->icon_dest_uri = NULL;
5892 return;
5895 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5896 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5898 webkit_download_start(t->icon_download);
5901 void
5902 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5904 const gchar *set = NULL, *uri = NULL, *title = NULL;
5905 struct history *h, find;
5906 const gchar *s_loading;
5907 struct karg a;
5909 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5910 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5912 if (t == NULL) {
5913 show_oops_s("notify_load_status_cb invalid paramters");
5914 return;
5917 switch (webkit_web_view_get_load_status(wview)) {
5918 case WEBKIT_LOAD_PROVISIONAL:
5919 /* 0 */
5920 abort_favicon_download(t);
5921 #if GTK_CHECK_VERSION(2, 20, 0)
5922 gtk_widget_show(t->spinner);
5923 gtk_spinner_start(GTK_SPINNER(t->spinner));
5924 #endif
5925 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5927 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5929 t->focus_wv = 1;
5931 break;
5933 case WEBKIT_LOAD_COMMITTED:
5934 /* 1 */
5935 if ((uri = get_uri(wview)) != NULL) {
5936 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5937 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5939 if (t->status) {
5940 g_free(t->status);
5941 t->status = NULL;
5943 set_status(t, (char *)uri, XT_STATUS_LOADING);
5946 /* check if js white listing is enabled */
5947 if (enable_js_whitelist) {
5948 uri = get_uri(wview);
5949 check_and_set_js(uri, t);
5952 if (t->styled)
5953 apply_style(t);
5955 show_ca_status(t, uri);
5957 /* we know enough to autosave the session */
5958 if (session_autosave) {
5959 a.s = NULL;
5960 save_tabs(t, &a);
5962 break;
5964 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5965 /* 3 */
5966 break;
5968 case WEBKIT_LOAD_FINISHED:
5969 /* 2 */
5970 uri = get_uri(wview);
5971 if (uri == NULL)
5972 return;
5974 if (!strncmp(uri, "http://", strlen("http://")) ||
5975 !strncmp(uri, "https://", strlen("https://")) ||
5976 !strncmp(uri, "file://", strlen("file://"))) {
5977 find.uri = uri;
5978 h = RB_FIND(history_list, &hl, &find);
5979 if (!h) {
5980 title = webkit_web_view_get_title(wview);
5981 set = title ? title: uri;
5982 h = g_malloc(sizeof *h);
5983 h->uri = g_strdup(uri);
5984 h->title = g_strdup(set);
5985 RB_INSERT(history_list, &hl, h);
5986 completion_add_uri(h->uri);
5987 update_history_tabs(NULL);
5991 set_status(t, (char *)uri, XT_STATUS_URI);
5992 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5993 case WEBKIT_LOAD_FAILED:
5994 /* 4 */
5995 #endif
5996 #if GTK_CHECK_VERSION(2, 20, 0)
5997 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5998 gtk_widget_hide(t->spinner);
5999 #endif
6000 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
6001 if (s_loading && !strcmp(s_loading, "Loading"))
6002 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6003 default:
6004 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6005 break;
6008 if (t->item)
6009 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6010 else
6011 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6012 webkit_web_view_can_go_back(wview));
6014 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6015 webkit_web_view_can_go_forward(wview));
6017 /* take focus if we are visible */
6018 focus_webview(t);
6021 void
6022 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6024 const gchar *set = NULL, *title = NULL;
6026 title = webkit_web_view_get_title(wview);
6027 set = title ? title: get_uri(wview);
6028 gtk_label_set_text(GTK_LABEL(t->label), set);
6029 gtk_window_set_title(GTK_WINDOW(main_window), set);
6032 void
6033 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6035 run_script(t, JS_HINTING);
6038 void
6039 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6041 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6042 progress == 100 ? 0 : (double)progress / 100);
6043 if (show_url == 0) {
6044 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
6045 progress == 100 ? 0 : (double)progress / 100);
6050 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6051 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6052 WebKitWebPolicyDecision *pd, struct tab *t)
6054 char *uri;
6055 WebKitWebNavigationReason reason;
6056 struct domain *d = NULL;
6058 if (t == NULL) {
6059 show_oops_s("webview_npd_cb invalid parameters");
6060 return (FALSE);
6063 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6064 t->ctrl_click,
6065 webkit_network_request_get_uri(request));
6067 uri = (char *)webkit_network_request_get_uri(request);
6069 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
6070 t->ctrl_click = 0;
6071 create_new_tab(uri, NULL, ctrl_click_focus);
6072 webkit_web_policy_decision_ignore(pd);
6073 return (TRUE); /* we made the decission */
6077 * This is a little hairy but it comes down to this:
6078 * when we run in whitelist mode we have to assist the browser in
6079 * opening the URL that it would have opened in a new tab.
6081 reason = webkit_web_navigation_action_get_reason(na);
6082 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6083 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6084 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6085 load_uri(t, uri);
6087 webkit_web_policy_decision_use(pd);
6088 return (TRUE); /* we made the decission */
6091 return (FALSE);
6094 WebKitWebView *
6095 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6097 struct tab *tt;
6098 struct domain *d = NULL;
6099 const gchar *uri;
6100 WebKitWebView *webview = NULL;
6102 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6103 webkit_web_view_get_uri(wv));
6105 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6106 uri = webkit_web_view_get_uri(wv);
6107 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6108 return (NULL);
6110 tt = create_new_tab(NULL, NULL, 1);
6111 webview = tt->wv;
6112 } else if (enable_scripts == 1) {
6113 tt = create_new_tab(NULL, NULL, 1);
6114 webview = tt->wv;
6117 return (webview);
6120 gboolean
6121 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6123 const gchar *uri;
6124 struct domain *d = NULL;
6126 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6128 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6129 uri = webkit_web_view_get_uri(wv);
6130 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6131 return (FALSE);
6133 delete_tab(t);
6134 } else if (enable_scripts == 1)
6135 delete_tab(t);
6137 return (TRUE);
6141 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6143 /* we can not eat the event without throwing gtk off so defer it */
6145 /* catch middle click */
6146 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6147 t->ctrl_click = 1;
6148 goto done;
6151 /* catch ctrl click */
6152 if (e->type == GDK_BUTTON_RELEASE &&
6153 CLEAN(e->state) == GDK_CONTROL_MASK)
6154 t->ctrl_click = 1;
6155 else
6156 t->ctrl_click = 0;
6157 done:
6158 return (XT_CB_PASSTHROUGH);
6162 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6164 struct mime_type *m;
6166 m = find_mime_type(mime_type);
6167 if (m == NULL)
6168 return (1);
6169 if (m->mt_download)
6170 return (1);
6172 switch (fork()) {
6173 case -1:
6174 show_oops(t, "can't fork mime handler");
6175 /* NOTREACHED */
6176 case 0:
6177 break;
6178 default:
6179 return (0);
6182 /* child */
6183 execlp(m->mt_action, m->mt_action,
6184 webkit_network_request_get_uri(request), (void *)NULL);
6186 _exit(0);
6188 /* NOTREACHED */
6189 return (0);
6192 const gchar *
6193 get_mime_type(char *file)
6195 const char *mime_type;
6196 GFileInfo *fi;
6197 GFile *gf;
6199 if (g_str_has_prefix(file, "file://"))
6200 file += strlen("file://");
6202 gf = g_file_new_for_path(file);
6203 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6204 NULL, NULL);
6205 mime_type = g_file_info_get_content_type(fi);
6206 g_object_unref(fi);
6207 g_object_unref(gf);
6209 return (mime_type);
6213 run_download_mimehandler(char *mime_type, char *file)
6215 struct mime_type *m;
6217 m = find_mime_type(mime_type);
6218 if (m == NULL)
6219 return (1);
6221 switch (fork()) {
6222 case -1:
6223 show_oops_s("can't fork download mime handler");
6224 /* NOTREACHED */
6225 case 0:
6226 break;
6227 default:
6228 return (0);
6231 /* child */
6232 if (g_str_has_prefix(file, "file://"))
6233 file += strlen("file://");
6234 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6236 _exit(0);
6238 /* NOTREACHED */
6239 return (0);
6242 void
6243 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6244 WebKitWebView *wv)
6246 WebKitDownloadStatus status;
6247 const gchar *file = NULL, *mime = NULL;
6249 if (download == NULL)
6250 return;
6251 status = webkit_download_get_status(download);
6252 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6253 return;
6255 file = webkit_download_get_destination_uri(download);
6256 if (file == NULL)
6257 return;
6258 mime = get_mime_type((char *)file);
6259 if (mime == NULL)
6260 return;
6262 run_download_mimehandler((char *)mime, (char *)file);
6266 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6267 WebKitNetworkRequest *request, char *mime_type,
6268 WebKitWebPolicyDecision *decision, struct tab *t)
6270 if (t == NULL) {
6271 show_oops_s("webview_mimetype_cb invalid parameters");
6272 return (FALSE);
6275 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6276 t->tab_id, mime_type);
6278 if (run_mimehandler(t, mime_type, request) == 0) {
6279 webkit_web_policy_decision_ignore(decision);
6280 focus_webview(t);
6281 return (TRUE);
6284 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6285 webkit_web_policy_decision_download(decision);
6286 return (TRUE);
6289 return (FALSE);
6293 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6294 struct tab *t)
6296 const gchar *filename;
6297 char *uri = NULL;
6298 struct download *download_entry;
6299 int ret = TRUE;
6301 if (wk_download == NULL || t == NULL) {
6302 show_oops_s("%s invalid parameters", __func__);
6303 return (FALSE);
6306 filename = webkit_download_get_suggested_filename(wk_download);
6307 if (filename == NULL)
6308 return (FALSE); /* abort download */
6310 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6312 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6313 "local %s\n", __func__, t->tab_id, filename, uri);
6315 webkit_download_set_destination_uri(wk_download, uri);
6317 if (webkit_download_get_status(wk_download) ==
6318 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6319 show_oops(t, "%s: download failed to start", __func__);
6320 ret = FALSE;
6321 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6322 } else {
6323 /* connect "download first" mime handler */
6324 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6325 G_CALLBACK(download_status_changed_cb), NULL);
6327 download_entry = g_malloc(sizeof(struct download));
6328 download_entry->download = wk_download;
6329 download_entry->tab = t;
6330 download_entry->id = next_download_id++;
6331 RB_INSERT(download_list, &downloads, download_entry);
6332 /* get from history */
6333 g_object_ref(wk_download);
6334 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6335 show_oops(t, "Download of '%s' started...",
6336 basename(webkit_download_get_destination_uri(wk_download)));
6339 if (uri)
6340 g_free(uri);
6342 /* sync other download manager tabs */
6343 update_download_tabs(NULL);
6346 * NOTE: never redirect/render the current tab before this
6347 * function returns. This will cause the download to never start.
6349 return (ret); /* start download */
6352 void
6353 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6355 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6357 if (t == NULL) {
6358 show_oops_s("webview_hover_cb");
6359 return;
6362 if (uri)
6363 set_status(t, uri, XT_STATUS_LINK);
6364 else {
6365 if (t->status)
6366 set_status(t, t->status, XT_STATUS_NOTHING);
6371 _handle_keypress(struct key_binding *k, GdkEventKey *e)
6373 int i;
6375 for (i = 0; i < LENGTH(cmds); i++) {
6376 if (!strcmp(k->name, cmds[i].cmd)) {
6377 if (cmds[i].params)
6378 if (k->arg.s && strlen(k->arg.s) > 0)
6379 cmds[i].arg.s = g_strdup_printf("%s %s",
6380 k->name, k->arg.s);
6381 break;
6385 return (i);
6388 gboolean
6389 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6391 int i = -1;
6392 struct key_binding *k;
6394 TAILQ_FOREACH(k, &kbl, entry)
6395 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6396 if (k->mask == 0) {
6397 if ((e->state & (CTRL | MOD1)) == 0)
6398 i = _handle_keypress(k, e);
6399 } else if ((e->state & k->mask) == k->mask) {
6400 i = _handle_keypress(k, e);
6404 if (0 <= i && i < LENGTH(cmds)) {
6405 cmds[i].func(t, &cmds[i].arg);
6406 if (cmds[i].arg.s)
6407 g_free(cmds[i].arg.s);
6408 return (XT_CB_HANDLED);
6409 } else
6410 return (XT_CB_PASSTHROUGH);
6414 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6416 char s[2], buf[128];
6417 const char *errstr = NULL;
6418 long long link;
6420 /* don't use w directly; use t->whatever instead */
6422 if (t == NULL) {
6423 show_oops_s("wv_keypress_after_cb");
6424 return (XT_CB_PASSTHROUGH);
6427 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6428 e->keyval, e->state, t);
6430 if (t->hints_on) {
6431 /* ESC */
6432 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6433 disable_hints(t);
6434 return (XT_CB_HANDLED);
6437 /* RETURN */
6438 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6439 link = strtonum(t->hint_num, 1, 1000, &errstr);
6440 if (errstr) {
6441 /* we have a string */
6442 } else {
6443 /* we have a number */
6444 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6445 t->hint_num);
6446 run_script(t, buf);
6448 disable_hints(t);
6451 /* BACKSPACE */
6452 /* XXX unfuck this */
6453 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6454 if (t->hint_mode == XT_HINT_NUMERICAL) {
6455 /* last input was numerical */
6456 int l;
6457 l = strlen(t->hint_num);
6458 if (l > 0) {
6459 l--;
6460 if (l == 0) {
6461 disable_hints(t);
6462 enable_hints(t);
6463 } else {
6464 t->hint_num[l] = '\0';
6465 goto num;
6468 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6469 /* last input was alphanumerical */
6470 int l;
6471 l = strlen(t->hint_buf);
6472 if (l > 0) {
6473 l--;
6474 if (l == 0) {
6475 disable_hints(t);
6476 enable_hints(t);
6477 } else {
6478 t->hint_buf[l] = '\0';
6479 goto anum;
6482 } else {
6483 /* bogus */
6484 disable_hints(t);
6488 /* numerical input */
6489 if (CLEAN(e->state) == 0 &&
6490 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6491 snprintf(s, sizeof s, "%c", e->keyval);
6492 strlcat(t->hint_num, s, sizeof t->hint_num);
6493 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6494 t->hint_num);
6495 num:
6496 link = strtonum(t->hint_num, 1, 1000, &errstr);
6497 if (errstr) {
6498 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6499 disable_hints(t);
6500 } else {
6501 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6502 t->hint_num);
6503 t->hint_mode = XT_HINT_NUMERICAL;
6504 run_script(t, buf);
6507 /* empty the counter buffer */
6508 bzero(t->hint_buf, sizeof t->hint_buf);
6509 return (XT_CB_HANDLED);
6512 /* alphanumerical input */
6513 if (
6514 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6515 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6516 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6517 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6518 snprintf(s, sizeof s, "%c", e->keyval);
6519 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6520 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6521 t->hint_buf);
6522 anum:
6523 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6524 run_script(t, buf);
6526 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6527 t->hint_buf);
6528 t->hint_mode = XT_HINT_ALPHANUM;
6529 run_script(t, buf);
6531 /* empty the counter buffer */
6532 bzero(t->hint_num, sizeof t->hint_num);
6533 return (XT_CB_HANDLED);
6536 return (XT_CB_HANDLED);
6539 return (handle_keypress(t, e, 0));
6543 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6545 hide_oops(t);
6547 return (XT_CB_PASSTHROUGH);
6551 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6553 const gchar *c = gtk_entry_get_text(w);
6554 GdkColor color;
6555 int forward = TRUE;
6557 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6558 e->keyval, e->state, t);
6560 if (t == NULL) {
6561 show_oops_s("cmd_keyrelease_cb invalid parameters");
6562 return (XT_CB_PASSTHROUGH);
6565 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6566 e->keyval, e->state, t);
6568 if (c[0] == ':')
6569 goto done;
6570 if (strlen(c) == 1) {
6571 webkit_web_view_unmark_text_matches(t->wv);
6572 goto done;
6575 if (c[0] == '/')
6576 forward = TRUE;
6577 else if (c[0] == '?')
6578 forward = FALSE;
6579 else
6580 goto done;
6582 /* search */
6583 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6584 FALSE) {
6585 /* not found, mark red */
6586 gdk_color_parse(XT_COLOR_RED, &color);
6587 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6588 /* unmark and remove selection */
6589 webkit_web_view_unmark_text_matches(t->wv);
6590 /* my kingdom for a way to unselect text in webview */
6591 } else {
6592 /* found, highlight all */
6593 webkit_web_view_unmark_text_matches(t->wv);
6594 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6595 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6596 gdk_color_parse(XT_COLOR_WHITE, &color);
6597 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6599 done:
6600 return (XT_CB_PASSTHROUGH);
6603 gboolean
6604 match_uri(const gchar *uri, const gchar *key) {
6605 gchar *voffset;
6606 size_t len;
6607 gboolean match = FALSE;
6609 len = strlen(key);
6611 if (!strncmp(key, uri, len))
6612 match = TRUE;
6613 else {
6614 voffset = strstr(uri, "/") + 2;
6615 if (!strncmp(key, voffset, len))
6616 match = TRUE;
6617 else if (g_str_has_prefix(voffset, "www.")) {
6618 voffset = voffset + strlen("www.");
6619 if (!strncmp(key, voffset, len))
6620 match = TRUE;
6624 return (match);
6627 void
6628 cmd_getlist(int id, char *key)
6630 int i, dep, c = 0;
6631 struct history *h;
6633 if (id >= 0 && (cmds[id].arg.i == XT_TAB_OPEN || cmds[id].arg.i == XT_TAB_NEW)) {
6634 RB_FOREACH_REVERSE(h, history_list, &hl)
6635 if (match_uri(h->uri, key)) {
6636 cmd_status.list[c] = (char *)h->uri;
6637 if (++c > 255)
6638 break;
6641 cmd_status.len = c;
6642 return;
6645 dep = (id == -1) ? 0 : cmds[id].params + 1;
6647 for (i = id + 1; i < LENGTH(cmds); i++) {
6648 if(cmds[i].params < dep)
6649 break;
6650 if (cmds[i].params == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6651 cmd_status.list[c++] = cmds[i].cmd;
6655 cmd_status.len = c;
6658 char *
6659 cmd_getnext(int dir)
6661 cmd_status.index += dir;
6663 if (cmd_status.index<0)
6664 cmd_status.index = cmd_status.len-1;
6665 else if (cmd_status.index >= cmd_status.len)
6666 cmd_status.index = 0;
6668 return cmd_status.list[cmd_status.index];
6672 cmd_tokenize(char *s, char *tokens[])
6674 int i = 0;
6675 char *tok, *last;
6676 size_t len = strlen(s);
6677 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6679 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6680 tokens[i] = tok;
6682 if (blank && i < 3)
6683 tokens[i++] = "";
6685 return (i);
6688 void
6689 cmd_complete(struct tab *t, char *str, int dir)
6691 GtkEntry *w = GTK_ENTRY(t->cmd);
6692 int i, j, levels, c = 0, dep = 0, parent = -1;
6693 char *tok, *match, *s = strdup(str);
6694 char *tokens[3];
6695 char res[XT_MAX_URL_LENGTH + 32] = ":";
6697 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", str);
6699 levels = cmd_tokenize(s, tokens);
6701 for (i = 0; i < levels - 1; i++) {
6702 tok=tokens[i];
6703 for (j = c; j < LENGTH(cmds); j++)
6704 if (cmds[j].params == dep && !strcmp(tok, cmds[j].cmd)) {
6705 strlcat(res, tok, sizeof res);
6706 strlcat(res, " ", sizeof res);
6707 dep++;
6708 c = j;
6709 break;
6712 parent = c;
6715 if (cmd_status.index == -1 )
6716 cmd_getlist(parent, tokens[i]);
6718 if (cmd_status.len > 0) {
6719 match = cmd_getnext(dir);
6720 strlcat(res, match, sizeof res);
6721 gtk_entry_set_text(w, res);
6722 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6726 void
6727 cmd_execute(struct tab *t, char *str)
6729 struct cmd *cmd = NULL;
6730 char *tok, *last, *s = g_strdup(str);
6731 int i, c = 0, dep = 0;
6733 for (tok = strtok_r(s, " ", &last); tok;
6734 tok = strtok_r(NULL, " ", &last)) {
6735 for (i = c; i < LENGTH(cmds); i++) {
6736 if(cmds[i].params < dep) {
6737 show_oops(t, "Invalid command: %s", str);
6738 return;
6740 if (cmds[i].params == dep && !strcmp(tok, cmds[i].cmd)) {
6741 cmd = &cmds[i];
6742 if (cmd->userarg) {
6743 goto execute_cmd;
6745 c = i + 1;
6746 dep++;
6747 break;
6750 if (i == LENGTH(cmds)) {
6751 show_oops(t, "Invalid command: %s", str);
6752 return;
6756 execute_cmd:
6757 cmd->arg.s = g_strdup(str);
6758 cmd->func(t, &cmd->arg);
6759 if (cmd->arg.s)
6760 g_free(cmd->arg.s);
6764 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6766 if (t == NULL) {
6767 show_oops_s("entry_key_cb invalid parameters");
6768 return (XT_CB_PASSTHROUGH);
6771 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6772 e->keyval, e->state, t);
6774 hide_oops(t);
6776 if (e->keyval == GDK_Escape) {
6777 /* don't use focus_webview(t) because we want to type :cmds */
6778 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6781 return (handle_keypress(t, e, 1));
6785 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6787 int rv = XT_CB_HANDLED;
6788 const gchar *c = gtk_entry_get_text(w);
6790 if (t == NULL) {
6791 show_oops_s("cmd_keypress_cb parameters");
6792 return (XT_CB_PASSTHROUGH);
6795 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6796 e->keyval, e->state, t);
6798 /* sanity */
6799 if (c == NULL)
6800 e->keyval = GDK_Escape;
6801 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6802 e->keyval = GDK_Escape;
6804 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6805 cmd_status.index = -1;
6807 switch (e->keyval) {
6808 case GDK_Tab:
6809 if (c[0] == ':')
6810 cmd_complete(t, (char *)&c[1], 1);
6811 goto done;
6812 case GDK_ISO_Left_Tab:
6813 if (c[0] == ':')
6814 cmd_complete(t, (char *)&c[1], -1);
6816 goto done;
6817 case GDK_BackSpace:
6818 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6819 break;
6820 /* FALLTHROUGH */
6821 case GDK_Escape:
6822 hide_cmd(t);
6823 focus_webview(t);
6825 /* cancel search */
6826 if (c[0] == '/' || c[0] == '?')
6827 webkit_web_view_unmark_text_matches(t->wv);
6828 goto done;
6831 rv = XT_CB_PASSTHROUGH;
6832 done:
6833 return (rv);
6837 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6839 if (t == NULL) {
6840 show_oops_s("cmd_focusout_cb invalid parameters");
6841 return (XT_CB_PASSTHROUGH);
6843 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6845 hide_cmd(t);
6846 hide_oops(t);
6848 if (show_url == 0 || t->focus_wv)
6849 focus_webview(t);
6850 else
6851 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6853 return (XT_CB_PASSTHROUGH);
6856 void
6857 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6859 char *s;
6860 const gchar *c = gtk_entry_get_text(entry);
6862 if (t == NULL) {
6863 show_oops_s("cmd_activate_cb invalid parameters");
6864 return;
6867 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6869 /* sanity */
6870 if (c == NULL)
6871 goto done;
6872 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6873 goto done;
6874 if (strlen(c) < 2)
6875 goto done;
6876 s = (char *)&c[1];
6878 if (c[0] == '/' || c[0] == '?') {
6879 if (t->search_text) {
6880 g_free(t->search_text);
6881 t->search_text = NULL;
6884 t->search_text = g_strdup(s);
6885 if (global_search)
6886 g_free(global_search);
6887 global_search = g_strdup(s);
6888 t->search_forward = c[0] == '/';
6890 goto done;
6893 cmd_execute(t, s);
6895 done:
6896 hide_cmd(t);
6899 void
6900 backward_cb(GtkWidget *w, struct tab *t)
6902 struct karg a;
6904 if (t == NULL) {
6905 show_oops_s("backward_cb invalid parameters");
6906 return;
6909 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6911 a.i = XT_NAV_BACK;
6912 navaction(t, &a);
6915 void
6916 forward_cb(GtkWidget *w, struct tab *t)
6918 struct karg a;
6920 if (t == NULL) {
6921 show_oops_s("forward_cb invalid parameters");
6922 return;
6925 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6927 a.i = XT_NAV_FORWARD;
6928 navaction(t, &a);
6931 void
6932 home_cb(GtkWidget *w, struct tab *t)
6934 if (t == NULL) {
6935 show_oops_s("home_cb invalid parameters");
6936 return;
6939 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6941 load_uri(t, home);
6944 void
6945 stop_cb(GtkWidget *w, struct tab *t)
6947 WebKitWebFrame *frame;
6949 if (t == NULL) {
6950 show_oops_s("stop_cb invalid parameters");
6951 return;
6954 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6956 frame = webkit_web_view_get_main_frame(t->wv);
6957 if (frame == NULL) {
6958 show_oops(t, "stop_cb: no frame");
6959 return;
6962 webkit_web_frame_stop_loading(frame);
6963 abort_favicon_download(t);
6966 void
6967 setup_webkit(struct tab *t)
6969 if (is_g_object_setting(G_OBJECT(t->settings),
6970 "enable-dns-prefetching"))
6971 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6972 FALSE, (char *)NULL);
6973 g_object_set(G_OBJECT(t->settings),
6974 "user-agent", t->user_agent, (char *)NULL);
6975 g_object_set(G_OBJECT(t->settings),
6976 "enable-scripts", enable_scripts, (char *)NULL);
6977 g_object_set(G_OBJECT(t->settings),
6978 "enable-plugins", enable_plugins, (char *)NULL);
6979 g_object_set(G_OBJECT(t->settings),
6980 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6981 g_object_set(G_OBJECT(t->wv),
6982 "full-content-zoom", TRUE, (char *)NULL);
6983 adjustfont_webkit(t, XT_FONT_SET);
6985 webkit_web_view_set_settings(t->wv, t->settings);
6988 GtkWidget *
6989 create_browser(struct tab *t)
6991 GtkWidget *w;
6992 gchar *strval;
6994 if (t == NULL) {
6995 show_oops_s("create_browser invalid parameters");
6996 return (NULL);
6999 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7000 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7001 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7002 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7004 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7005 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7006 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7008 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7009 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7011 /* set defaults */
7012 t->settings = webkit_web_settings_new();
7014 if (user_agent == NULL) {
7015 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7016 (char *)NULL);
7017 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7018 g_free(strval);
7019 } else
7020 t->user_agent = g_strdup(user_agent);
7022 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7024 setup_webkit(t);
7026 return (w);
7029 GtkWidget *
7030 create_window(void)
7032 GtkWidget *w;
7034 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7035 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7036 gtk_widget_set_name(w, "xxxterm");
7037 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7038 g_signal_connect(G_OBJECT(w), "delete_event",
7039 G_CALLBACK (gtk_main_quit), NULL);
7041 return (w);
7044 GtkWidget *
7045 create_kiosk_toolbar(struct tab *t)
7047 GtkWidget *toolbar = NULL, *b;
7049 b = gtk_hbox_new(FALSE, 0);
7050 toolbar = b;
7051 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7053 /* backward button */
7054 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
7055 gtk_widget_set_sensitive(t->backward, FALSE);
7056 g_signal_connect(G_OBJECT(t->backward), "clicked",
7057 G_CALLBACK(backward_cb), t);
7058 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7060 /* forward button */
7061 t->forward = create_button("GoForward", GTK_STOCK_GO_FORWARD, 0);
7062 gtk_widget_set_sensitive(t->forward, FALSE);
7063 g_signal_connect(G_OBJECT(t->forward), "clicked",
7064 G_CALLBACK(forward_cb), t);
7065 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7067 /* home button */
7068 t->gohome = create_button("GoForward", GTK_STOCK_HOME, 0);
7069 gtk_widget_set_sensitive(t->gohome, true);
7070 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7071 G_CALLBACK(home_cb), t);
7072 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7074 /* create widgets but don't use them */
7075 t->uri_entry = gtk_entry_new();
7076 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7077 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7078 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7080 return (toolbar);
7083 GtkWidget *
7084 create_toolbar(struct tab *t)
7086 GtkWidget *toolbar = NULL, *b, *eb1;
7088 b = gtk_hbox_new(FALSE, 0);
7089 toolbar = b;
7090 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7092 if (fancy_bar) {
7093 /* backward button */
7094 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
7095 gtk_widget_set_sensitive(t->backward, FALSE);
7096 g_signal_connect(G_OBJECT(t->backward), "clicked",
7097 G_CALLBACK(backward_cb), t);
7098 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7100 /* forward button */
7101 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
7102 gtk_widget_set_sensitive(t->forward, FALSE);
7103 g_signal_connect(G_OBJECT(t->forward), "clicked",
7104 G_CALLBACK(forward_cb), t);
7105 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7106 FALSE, 0);
7108 /* stop button */
7109 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7110 gtk_widget_set_sensitive(t->stop, FALSE);
7111 g_signal_connect(G_OBJECT(t->stop), "clicked",
7112 G_CALLBACK(stop_cb), t);
7113 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7114 FALSE, 0);
7116 /* JS button */
7117 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7118 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7119 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7120 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7121 G_CALLBACK(js_toggle_cb), t);
7122 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7125 t->uri_entry = gtk_entry_new();
7126 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7127 G_CALLBACK(activate_uri_entry_cb), t);
7128 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7129 G_CALLBACK(entry_key_cb), t);
7130 completion_add(t);
7131 eb1 = gtk_hbox_new(FALSE, 0);
7132 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7133 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7134 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7136 /* search entry */
7137 if (fancy_bar && search_string) {
7138 GtkWidget *eb2;
7139 t->search_entry = gtk_entry_new();
7140 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7141 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7142 G_CALLBACK(activate_search_entry_cb), t);
7143 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7144 G_CALLBACK(entry_key_cb), t);
7145 gtk_widget_set_size_request(t->search_entry, -1, -1);
7146 eb2 = gtk_hbox_new(FALSE, 0);
7147 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7148 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7150 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7152 return (toolbar);
7155 void
7156 recalc_tabs(void)
7158 struct tab *t;
7160 TAILQ_FOREACH(t, &tabs, entry)
7161 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7165 undo_close_tab_save(struct tab *t)
7167 int m, n;
7168 const gchar *uri;
7169 struct undo *u1, *u2;
7170 GList *items;
7171 WebKitWebHistoryItem *item;
7173 if ((uri = get_uri(t->wv)) == NULL)
7174 return (1);
7176 u1 = g_malloc0(sizeof(struct undo));
7177 u1->uri = g_strdup(uri);
7179 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7181 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7182 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7183 u1->back = n;
7185 /* forward history */
7186 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7188 while (items) {
7189 item = items->data;
7190 u1->history = g_list_prepend(u1->history,
7191 webkit_web_history_item_copy(item));
7192 items = g_list_next(items);
7195 /* current item */
7196 if (m) {
7197 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7198 u1->history = g_list_prepend(u1->history,
7199 webkit_web_history_item_copy(item));
7202 /* back history */
7203 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7205 while (items) {
7206 item = items->data;
7207 u1->history = g_list_prepend(u1->history,
7208 webkit_web_history_item_copy(item));
7209 items = g_list_next(items);
7212 TAILQ_INSERT_HEAD(&undos, u1, entry);
7214 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7215 u2 = TAILQ_LAST(&undos, undo_tailq);
7216 TAILQ_REMOVE(&undos, u2, entry);
7217 g_free(u2->uri);
7218 g_list_free(u2->history);
7219 g_free(u2);
7220 } else
7221 undo_count++;
7223 return (0);
7226 void
7227 delete_tab(struct tab *t)
7229 struct karg a;
7231 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7233 if (t == NULL)
7234 return;
7236 TAILQ_REMOVE(&tabs, t, entry);
7238 /* halt all webkit activity */
7239 abort_favicon_download(t);
7240 webkit_web_view_stop_loading(t->wv);
7241 undo_close_tab_save(t);
7243 if (browser_mode == XT_BM_KIOSK) {
7244 gtk_widget_destroy(t->uri_entry);
7245 gtk_widget_destroy(t->stop);
7246 gtk_widget_destroy(t->js_toggle);
7250 gtk_widget_destroy(t->vbox);
7251 g_free(t->user_agent);
7252 g_free(t->stylesheet);
7253 g_free(t);
7255 recalc_tabs();
7256 if (TAILQ_EMPTY(&tabs)) {
7257 if (browser_mode == XT_BM_KIOSK)
7258 create_new_tab(home, NULL, 1);
7259 else
7260 create_new_tab(NULL, NULL, 1);
7263 /* recreate session */
7264 if (session_autosave) {
7265 a.s = NULL;
7266 save_tabs(t, &a);
7270 void
7271 adjustfont_webkit(struct tab *t, int adjust)
7273 gfloat zoom;
7275 if (t == NULL) {
7276 show_oops_s("adjustfont_webkit invalid parameters");
7277 return;
7280 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7281 if (adjust == XT_FONT_SET) {
7282 t->font_size = default_font_size;
7283 zoom = default_zoom_level;
7284 t->font_size += adjust;
7285 g_object_set(G_OBJECT(t->settings), "default-font-size",
7286 t->font_size, (char *)NULL);
7287 g_object_get(G_OBJECT(t->settings), "default-font-size",
7288 &t->font_size, (char *)NULL);
7289 } else {
7290 t->font_size += adjust;
7291 zoom += adjust/25.0;
7292 if (zoom < 0.0) {
7293 zoom = 0.04;
7296 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7297 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7300 void
7301 append_tab(struct tab *t)
7303 if (t == NULL)
7304 return;
7306 TAILQ_INSERT_TAIL(&tabs, t, entry);
7307 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7310 struct tab *
7311 create_new_tab(char *title, struct undo *u, int focus)
7313 struct tab *t, *tt;
7314 int load = 1, id, notfound;
7315 GtkWidget *b, *bb;
7316 WebKitWebHistoryItem *item;
7317 GList *items;
7318 GdkColor color;
7320 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7322 if (tabless && !TAILQ_EMPTY(&tabs)) {
7323 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7324 return (NULL);
7327 t = g_malloc0(sizeof *t);
7329 if (title == NULL) {
7330 title = "(untitled)";
7331 load = 0;
7334 t->vbox = gtk_vbox_new(FALSE, 0);
7336 /* label + button for tab */
7337 b = gtk_hbox_new(FALSE, 0);
7338 t->tab_content = b;
7340 #if GTK_CHECK_VERSION(2, 20, 0)
7341 t->spinner = gtk_spinner_new ();
7342 #endif
7343 t->label = gtk_label_new(title);
7344 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7345 gtk_widget_set_size_request(t->label, 100, 0);
7346 gtk_widget_set_size_request(b, 130, 0);
7348 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7349 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7350 #if GTK_CHECK_VERSION(2, 20, 0)
7351 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7352 #endif
7354 /* toolbar */
7355 if (browser_mode == XT_BM_KIOSK)
7356 t->toolbar = create_kiosk_toolbar(t);
7357 else
7358 t->toolbar = create_toolbar(t);
7360 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7362 /* browser */
7363 t->browser_win = create_browser(t);
7364 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7366 /* oops message for user feedback */
7367 t->oops = gtk_entry_new();
7368 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7369 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7370 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7371 gdk_color_parse(XT_COLOR_RED, &color);
7372 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7373 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7375 /* command entry */
7376 t->cmd = gtk_entry_new();
7377 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7378 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7379 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7381 /* status bar */
7382 t->statusbar = gtk_entry_new();
7383 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7384 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7385 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7386 gdk_color_parse(XT_COLOR_BLACK, &color);
7387 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7388 gdk_color_parse(XT_COLOR_WHITE, &color);
7389 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7390 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7392 /* xtp meaning is normal by default */
7393 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7395 /* set empty favicon */
7396 xt_icon_from_name(t, "text-html");
7398 /* and show it all */
7399 gtk_widget_show_all(b);
7400 gtk_widget_show_all(t->vbox);
7402 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7403 append_tab(t);
7404 else {
7405 notfound = 1;
7406 id = gtk_notebook_get_current_page(notebook);
7407 TAILQ_FOREACH(tt, &tabs, entry) {
7408 if (tt->tab_id == id) {
7409 notfound = 0;
7410 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7411 gtk_notebook_insert_page(notebook, t->vbox, b,
7412 id + 1);
7413 recalc_tabs();
7414 break;
7417 if (notfound)
7418 append_tab(t);
7421 #if GTK_CHECK_VERSION(2, 20, 0)
7422 /* turn spinner off if we are a new tab without uri */
7423 if (!load) {
7424 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7425 gtk_widget_hide(t->spinner);
7427 #endif
7428 /* make notebook tabs reorderable */
7429 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7431 g_object_connect(G_OBJECT(t->cmd),
7432 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7433 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7434 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7435 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7436 (char *)NULL);
7438 /* reuse wv_button_cb to hide oops */
7439 g_object_connect(G_OBJECT(t->oops),
7440 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7441 (char *)NULL);
7443 g_object_connect(G_OBJECT(t->wv),
7444 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7445 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7446 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7447 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7448 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7449 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7450 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7451 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7452 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7453 "signal::event", G_CALLBACK(webview_event_cb), t,
7454 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7455 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7456 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7457 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7458 #endif
7459 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7460 (char *)NULL);
7461 g_signal_connect(t->wv,
7462 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7463 g_signal_connect(t->wv,
7464 "notify::title", G_CALLBACK(notify_title_cb), t);
7466 /* hijack the unused keys as if we were the browser */
7467 g_object_connect(G_OBJECT(t->toolbar),
7468 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7469 (char *)NULL);
7471 g_signal_connect(G_OBJECT(bb), "button_press_event",
7472 G_CALLBACK(tab_close_cb), t);
7474 /* hide stuff */
7475 hide_cmd(t);
7476 hide_oops(t);
7477 url_set_visibility();
7478 statusbar_set_visibility();
7480 if (focus) {
7481 gtk_notebook_set_current_page(notebook, t->tab_id);
7482 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7483 t->tab_id);
7486 if (load) {
7487 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7488 load_uri(t, title);
7489 } else {
7490 if (show_url == 1)
7491 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7492 else
7493 focus_webview(t);
7496 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7497 /* restore the tab's history */
7498 if (u && u->history) {
7499 items = u->history;
7500 while (items) {
7501 item = items->data;
7502 webkit_web_back_forward_list_add_item(t->bfl, item);
7503 items = g_list_next(items);
7506 item = g_list_nth_data(u->history, u->back);
7507 if (item)
7508 webkit_web_view_go_to_back_forward_item(t->wv, item);
7510 g_list_free(items);
7511 g_list_free(u->history);
7512 } else
7513 webkit_web_back_forward_list_clear(t->bfl);
7515 return (t);
7518 void
7519 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7520 gpointer *udata)
7522 struct tab *t;
7523 const gchar *uri;
7525 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7527 TAILQ_FOREACH(t, &tabs, entry) {
7528 if (t->tab_id == pn) {
7529 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7530 "%d\n", pn);
7532 uri = webkit_web_view_get_title(t->wv);
7533 if (uri == NULL)
7534 uri = XT_NAME;
7535 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7537 hide_cmd(t);
7538 hide_oops(t);
7540 if (t->focus_wv)
7541 focus_webview(t);
7546 void
7547 menuitem_response(struct tab *t)
7549 gtk_notebook_set_current_page(notebook, t->tab_id);
7552 gboolean
7553 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7555 GtkWidget *menu, *menu_items;
7556 GdkEventButton *bevent;
7557 const gchar *uri;
7558 struct tab *ti;
7560 if (event->type == GDK_BUTTON_PRESS) {
7561 bevent = (GdkEventButton *) event;
7562 menu = gtk_menu_new();
7564 TAILQ_FOREACH(ti, &tabs, entry) {
7565 if ((uri = get_uri(ti->wv)) == NULL)
7566 /* XXX make sure there is something to print */
7567 /* XXX add gui pages in here to look purdy */
7568 uri = "(untitled)";
7569 menu_items = gtk_menu_item_new_with_label(uri);
7570 gtk_menu_append(GTK_MENU (menu), menu_items);
7571 gtk_widget_show(menu_items);
7573 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7574 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7575 (gpointer)ti);
7578 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7579 bevent->button, bevent->time);
7581 /* unref object so it'll free itself when popped down */
7582 g_object_ref_sink(menu);
7583 g_object_unref(menu);
7585 return (TRUE /* eat event */);
7588 return (FALSE /* propagate */);
7592 icon_size_map(int icon_size)
7594 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7595 icon_size > GTK_ICON_SIZE_DIALOG)
7596 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7598 return (icon_size);
7601 GtkWidget *
7602 create_button(char *name, char *stockid, int size)
7604 GtkWidget *button, *image;
7605 gchar *rcstring;
7606 int gtk_icon_size;
7607 rcstring = g_strdup_printf(
7608 "style \"%s-style\"\n"
7609 "{\n"
7610 " GtkWidget::focus-padding = 0\n"
7611 " GtkWidget::focus-line-width = 0\n"
7612 " xthickness = 0\n"
7613 " ythickness = 0\n"
7614 "}\n"
7615 "widget \"*.%s\" style \"%s-style\"",name,name,name);
7616 gtk_rc_parse_string(rcstring);
7617 g_free(rcstring);
7618 button = gtk_button_new();
7619 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7620 gtk_icon_size = icon_size_map(size?size:icon_size);
7622 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7623 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7624 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7625 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7626 gtk_widget_set_name(button, name);
7627 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7628 gtk_widget_set_tooltip_text(button, name);
7630 return button;
7633 void
7634 button_set_stockid(GtkWidget *button, char *stockid)
7636 GtkWidget *image;
7638 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7639 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7640 gtk_button_set_image(GTK_BUTTON(button), image);
7643 void
7644 create_canvas(void)
7646 GtkWidget *vbox;
7647 GList *l = NULL;
7648 GdkPixbuf *pb;
7649 char file[PATH_MAX];
7650 int i;
7652 vbox = gtk_vbox_new(FALSE, 0);
7653 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7654 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7655 gtk_notebook_set_tab_hborder(notebook, 0);
7656 gtk_notebook_set_tab_vborder(notebook, 0);
7657 gtk_notebook_set_scrollable(notebook, TRUE);
7658 notebook_tab_set_visibility(notebook);
7659 gtk_notebook_set_show_border(notebook, FALSE);
7660 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7662 abtn = gtk_button_new();
7663 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7664 gtk_widget_set_size_request(arrow, -1, -1);
7665 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7666 gtk_widget_set_size_request(abtn, -1, 20);
7667 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7669 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7670 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7671 gtk_widget_set_size_request(vbox, -1, -1);
7673 g_object_connect(G_OBJECT(notebook),
7674 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7675 (char *)NULL);
7676 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7677 G_CALLBACK(arrow_cb), NULL);
7679 main_window = create_window();
7680 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7681 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7683 /* icons */
7684 for (i = 0; i < LENGTH(icons); i++) {
7685 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7686 pb = gdk_pixbuf_new_from_file(file, NULL);
7687 l = g_list_append(l, pb);
7689 gtk_window_set_default_icon_list(l);
7691 gtk_widget_show_all(abtn);
7692 gtk_widget_show_all(main_window);
7695 void
7696 set_hook(void **hook, char *name)
7698 if (hook == NULL)
7699 errx(1, "set_hook");
7701 if (*hook == NULL) {
7702 *hook = dlsym(RTLD_NEXT, name);
7703 if (*hook == NULL)
7704 errx(1, "can't hook %s", name);
7708 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7709 gboolean
7710 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7712 g_return_val_if_fail(cookie1, FALSE);
7713 g_return_val_if_fail(cookie2, FALSE);
7715 return (!strcmp (cookie1->name, cookie2->name) &&
7716 !strcmp (cookie1->value, cookie2->value) &&
7717 !strcmp (cookie1->path, cookie2->path) &&
7718 !strcmp (cookie1->domain, cookie2->domain));
7721 void
7722 transfer_cookies(void)
7724 GSList *cf;
7725 SoupCookie *sc, *pc;
7727 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7729 for (;cf; cf = cf->next) {
7730 pc = cf->data;
7731 sc = soup_cookie_copy(pc);
7732 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7735 soup_cookies_free(cf);
7738 void
7739 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7741 GSList *cf;
7742 SoupCookie *ci;
7744 print_cookie("soup_cookie_jar_delete_cookie", c);
7746 if (cookies_enabled == 0)
7747 return;
7749 if (jar == NULL || c == NULL)
7750 return;
7752 /* find and remove from persistent jar */
7753 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7755 for (;cf; cf = cf->next) {
7756 ci = cf->data;
7757 if (soup_cookie_equal(ci, c)) {
7758 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7759 break;
7763 soup_cookies_free(cf);
7765 /* delete from session jar */
7766 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7769 void
7770 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7772 struct domain *d = NULL;
7773 SoupCookie *c;
7774 FILE *r_cookie_f;
7776 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7777 jar, p_cookiejar, s_cookiejar);
7779 if (cookies_enabled == 0)
7780 return;
7782 /* see if we are up and running */
7783 if (p_cookiejar == NULL) {
7784 _soup_cookie_jar_add_cookie(jar, cookie);
7785 return;
7787 /* disallow p_cookiejar adds, shouldn't happen */
7788 if (jar == p_cookiejar)
7789 return;
7791 /* sanity */
7792 if (jar == NULL || cookie == NULL)
7793 return;
7795 if (enable_cookie_whitelist &&
7796 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7797 blocked_cookies++;
7798 DNPRINTF(XT_D_COOKIE,
7799 "soup_cookie_jar_add_cookie: reject %s\n",
7800 cookie->domain);
7801 if (save_rejected_cookies) {
7802 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7803 show_oops_s("can't open reject cookie file");
7804 return;
7806 fseek(r_cookie_f, 0, SEEK_END);
7807 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7808 cookie->http_only ? "#HttpOnly_" : "",
7809 cookie->domain,
7810 *cookie->domain == '.' ? "TRUE" : "FALSE",
7811 cookie->path,
7812 cookie->secure ? "TRUE" : "FALSE",
7813 cookie->expires ?
7814 (gulong)soup_date_to_time_t(cookie->expires) :
7816 cookie->name,
7817 cookie->value);
7818 fflush(r_cookie_f);
7819 fclose(r_cookie_f);
7821 if (!allow_volatile_cookies)
7822 return;
7825 if (cookie->expires == NULL && session_timeout) {
7826 soup_cookie_set_expires(cookie,
7827 soup_date_new_from_now(session_timeout));
7828 print_cookie("modified add cookie", cookie);
7831 /* see if we are white listed for persistence */
7832 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7833 /* add to persistent jar */
7834 c = soup_cookie_copy(cookie);
7835 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7836 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7839 /* add to session jar */
7840 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7841 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7844 void
7845 setup_cookies(void)
7847 char file[PATH_MAX];
7849 set_hook((void *)&_soup_cookie_jar_add_cookie,
7850 "soup_cookie_jar_add_cookie");
7851 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7852 "soup_cookie_jar_delete_cookie");
7854 if (cookies_enabled == 0)
7855 return;
7858 * the following code is intricate due to overriding several libsoup
7859 * functions.
7860 * do not alter order of these operations.
7863 /* rejected cookies */
7864 if (save_rejected_cookies)
7865 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7867 /* persistent cookies */
7868 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7869 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7871 /* session cookies */
7872 s_cookiejar = soup_cookie_jar_new();
7873 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7874 cookie_policy, (void *)NULL);
7875 transfer_cookies();
7877 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7880 void
7881 setup_proxy(char *uri)
7883 if (proxy_uri) {
7884 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7885 soup_uri_free(proxy_uri);
7886 proxy_uri = NULL;
7888 if (http_proxy) {
7889 if (http_proxy != uri) {
7890 g_free(http_proxy);
7891 http_proxy = NULL;
7895 if (uri) {
7896 http_proxy = g_strdup(uri);
7897 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7898 proxy_uri = soup_uri_new(http_proxy);
7899 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7904 send_cmd_to_socket(char *cmd)
7906 int s, len, rv = 1;
7907 struct sockaddr_un sa;
7909 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7910 warnx("%s: socket", __func__);
7911 return (rv);
7914 sa.sun_family = AF_UNIX;
7915 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7916 work_dir, XT_SOCKET_FILE);
7917 len = SUN_LEN(&sa);
7919 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7920 warnx("%s: connect", __func__);
7921 goto done;
7924 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7925 warnx("%s: send", __func__);
7926 goto done;
7929 rv = 0;
7930 done:
7931 close(s);
7932 return (rv);
7935 void
7936 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7938 int s, n;
7939 char str[XT_MAX_URL_LENGTH];
7940 socklen_t t = sizeof(struct sockaddr_un);
7941 struct sockaddr_un sa;
7942 struct passwd *p;
7943 uid_t uid;
7944 gid_t gid;
7945 struct tab *tt;
7947 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7948 warn("accept");
7949 return;
7952 if (getpeereid(s, &uid, &gid) == -1) {
7953 warn("getpeereid");
7954 return;
7956 if (uid != getuid() || gid != getgid()) {
7957 warnx("unauthorized user");
7958 return;
7961 p = getpwuid(uid);
7962 if (p == NULL) {
7963 warnx("not a valid user");
7964 return;
7967 n = recv(s, str, sizeof(str), 0);
7968 if (n <= 0)
7969 return;
7971 tt = TAILQ_LAST(&tabs, tab_list);
7972 cmd_execute(tt, str);
7976 is_running(void)
7978 int s, len, rv = 1;
7979 struct sockaddr_un sa;
7981 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7982 warn("is_running: socket");
7983 return (-1);
7986 sa.sun_family = AF_UNIX;
7987 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7988 work_dir, XT_SOCKET_FILE);
7989 len = SUN_LEN(&sa);
7991 /* connect to see if there is a listener */
7992 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7993 rv = 0; /* not running */
7994 else
7995 rv = 1; /* already running */
7997 close(s);
7999 return (rv);
8003 build_socket(void)
8005 int s, len;
8006 struct sockaddr_un sa;
8008 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8009 warn("build_socket: socket");
8010 return (-1);
8013 sa.sun_family = AF_UNIX;
8014 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8015 work_dir, XT_SOCKET_FILE);
8016 len = SUN_LEN(&sa);
8018 /* connect to see if there is a listener */
8019 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8020 /* no listener so we will */
8021 unlink(sa.sun_path);
8023 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8024 warn("build_socket: bind");
8025 goto done;
8028 if (listen(s, 1) == -1) {
8029 warn("build_socket: listen");
8030 goto done;
8033 return (s);
8036 done:
8037 close(s);
8038 return (-1);
8041 static gboolean
8042 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8043 GtkTreeIter *iter, struct tab *t)
8045 gchar *value;
8047 gtk_tree_model_get(model, iter, 0, &value, -1);
8048 load_uri(t, value);
8050 return (FALSE);
8053 void
8054 completion_add_uri(const gchar *uri)
8056 GtkTreeIter iter;
8058 /* add uri to list_store */
8059 gtk_list_store_append(completion_model, &iter);
8060 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8063 gboolean
8064 completion_match(GtkEntryCompletion *completion, const gchar *key,
8065 GtkTreeIter *iter, gpointer user_data)
8067 gchar *value;
8068 gboolean match = FALSE;
8070 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8071 -1);
8073 if (value == NULL)
8074 return FALSE;
8076 match = match_uri(value, key);
8078 g_free(value);
8079 return (match);
8082 void
8083 completion_add(struct tab *t)
8085 /* enable completion for tab */
8086 t->completion = gtk_entry_completion_new();
8087 gtk_entry_completion_set_text_column(t->completion, 0);
8088 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8089 gtk_entry_completion_set_model(t->completion,
8090 GTK_TREE_MODEL(completion_model));
8091 gtk_entry_completion_set_match_func(t->completion, completion_match,
8092 NULL, NULL);
8093 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8094 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8095 G_CALLBACK(completion_select_cb), t);
8098 void
8099 usage(void)
8101 fprintf(stderr,
8102 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8103 exit(0);
8107 main(int argc, char *argv[])
8109 struct stat sb;
8110 int c, s, optn = 0, opte = 0, focus = 1;
8111 char conf[PATH_MAX] = { '\0' };
8112 char file[PATH_MAX];
8113 char *env_proxy = NULL;
8114 FILE *f = NULL;
8115 struct karg a;
8116 struct sigaction sact;
8118 start_argv = argv;
8120 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8122 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8123 switch (c) {
8124 case 'S':
8125 show_url = 0;
8126 break;
8127 case 'T':
8128 show_tabs = 0;
8129 break;
8130 case 'V':
8131 errx(0 , "Version: %s", version);
8132 break;
8133 case 'f':
8134 strlcpy(conf, optarg, sizeof(conf));
8135 break;
8136 case 's':
8137 strlcpy(named_session, optarg, sizeof(named_session));
8138 break;
8139 case 't':
8140 tabless = 1;
8141 break;
8142 case 'n':
8143 optn = 1;
8144 break;
8145 case 'e':
8146 opte = 1;
8147 break;
8148 default:
8149 usage();
8150 /* NOTREACHED */
8153 argc -= optind;
8154 argv += optind;
8156 RB_INIT(&hl);
8157 RB_INIT(&js_wl);
8158 RB_INIT(&downloads);
8160 TAILQ_INIT(&tabs);
8161 TAILQ_INIT(&mtl);
8162 TAILQ_INIT(&aliases);
8163 TAILQ_INIT(&undos);
8164 TAILQ_INIT(&kbl);
8166 init_keybindings();
8168 gnutls_global_init();
8170 /* generate session keys for xtp pages */
8171 generate_xtp_session_key(&dl_session_key);
8172 generate_xtp_session_key(&hl_session_key);
8173 generate_xtp_session_key(&cl_session_key);
8174 generate_xtp_session_key(&fl_session_key);
8176 /* prepare gtk */
8177 gtk_init(&argc, &argv);
8178 if (!g_thread_supported())
8179 g_thread_init(NULL);
8181 /* signals */
8182 bzero(&sact, sizeof(sact));
8183 sigemptyset(&sact.sa_mask);
8184 sact.sa_handler = sigchild;
8185 sact.sa_flags = SA_NOCLDSTOP;
8186 sigaction(SIGCHLD, &sact, NULL);
8188 /* set download dir */
8189 pwd = getpwuid(getuid());
8190 if (pwd == NULL)
8191 errx(1, "invalid user %d", getuid());
8192 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8194 /* set default string settings */
8195 home = g_strdup("http://www.peereboom.us");
8196 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8197 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8198 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8200 /* read config file */
8201 if (strlen(conf) == 0)
8202 snprintf(conf, sizeof conf, "%s/.%s",
8203 pwd->pw_dir, XT_CONF_FILE);
8204 config_parse(conf, 0);
8206 /* working directory */
8207 if (strlen(work_dir) == 0)
8208 snprintf(work_dir, sizeof work_dir, "%s/%s",
8209 pwd->pw_dir, XT_DIR);
8210 if (stat(work_dir, &sb)) {
8211 if (mkdir(work_dir, S_IRWXU) == -1)
8212 err(1, "mkdir work_dir");
8213 if (stat(work_dir, &sb))
8214 err(1, "stat work_dir");
8216 if (S_ISDIR(sb.st_mode) == 0)
8217 errx(1, "%s not a dir", work_dir);
8218 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8219 warnx("fixing invalid permissions on %s", work_dir);
8220 if (chmod(work_dir, S_IRWXU) == -1)
8221 err(1, "chmod");
8224 /* icon cache dir */
8225 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8226 if (stat(cache_dir, &sb)) {
8227 if (mkdir(cache_dir, S_IRWXU) == -1)
8228 err(1, "mkdir cache_dir");
8229 if (stat(cache_dir, &sb))
8230 err(1, "stat cache_dir");
8232 if (S_ISDIR(sb.st_mode) == 0)
8233 errx(1, "%s not a dir", cache_dir);
8234 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8235 warnx("fixing invalid permissions on %s", cache_dir);
8236 if (chmod(cache_dir, S_IRWXU) == -1)
8237 err(1, "chmod");
8240 /* certs dir */
8241 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8242 if (stat(certs_dir, &sb)) {
8243 if (mkdir(certs_dir, S_IRWXU) == -1)
8244 err(1, "mkdir certs_dir");
8245 if (stat(certs_dir, &sb))
8246 err(1, "stat certs_dir");
8248 if (S_ISDIR(sb.st_mode) == 0)
8249 errx(1, "%s not a dir", certs_dir);
8250 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8251 warnx("fixing invalid permissions on %s", certs_dir);
8252 if (chmod(certs_dir, S_IRWXU) == -1)
8253 err(1, "chmod");
8256 /* sessions dir */
8257 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8258 work_dir, XT_SESSIONS_DIR);
8259 if (stat(sessions_dir, &sb)) {
8260 if (mkdir(sessions_dir, S_IRWXU) == -1)
8261 err(1, "mkdir sessions_dir");
8262 if (stat(sessions_dir, &sb))
8263 err(1, "stat sessions_dir");
8265 if (S_ISDIR(sb.st_mode) == 0)
8266 errx(1, "%s not a dir", sessions_dir);
8267 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8268 warnx("fixing invalid permissions on %s", sessions_dir);
8269 if (chmod(sessions_dir, S_IRWXU) == -1)
8270 err(1, "chmod");
8272 /* runtime settings that can override config file */
8273 if (runtime_settings[0] != '\0')
8274 config_parse(runtime_settings, 1);
8276 /* download dir */
8277 if (!strcmp(download_dir, pwd->pw_dir))
8278 strlcat(download_dir, "/downloads", sizeof download_dir);
8279 if (stat(download_dir, &sb)) {
8280 if (mkdir(download_dir, S_IRWXU) == -1)
8281 err(1, "mkdir download_dir");
8282 if (stat(download_dir, &sb))
8283 err(1, "stat download_dir");
8285 if (S_ISDIR(sb.st_mode) == 0)
8286 errx(1, "%s not a dir", download_dir);
8287 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8288 warnx("fixing invalid permissions on %s", download_dir);
8289 if (chmod(download_dir, S_IRWXU) == -1)
8290 err(1, "chmod");
8293 /* favorites file */
8294 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8295 if (stat(file, &sb)) {
8296 warnx("favorites file doesn't exist, creating it");
8297 if ((f = fopen(file, "w")) == NULL)
8298 err(1, "favorites");
8299 fclose(f);
8302 /* cookies */
8303 session = webkit_get_default_session();
8304 setup_cookies();
8306 /* certs */
8307 if (ssl_ca_file) {
8308 if (stat(ssl_ca_file, &sb)) {
8309 warn("no CA file: %s", ssl_ca_file);
8310 g_free(ssl_ca_file);
8311 ssl_ca_file = NULL;
8312 } else
8313 g_object_set(session,
8314 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8315 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8316 (void *)NULL);
8319 /* proxy */
8320 env_proxy = getenv("http_proxy");
8321 if (env_proxy)
8322 setup_proxy(env_proxy);
8323 else
8324 setup_proxy(http_proxy);
8326 if (opte) {
8327 send_cmd_to_socket(argv[0]);
8328 exit(0);
8331 /* see if there is already an xxxterm running */
8332 if (single_instance && is_running()) {
8333 optn = 1;
8334 warnx("already running");
8337 char *cmd = NULL;
8338 if (optn) {
8339 while (argc) {
8340 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8341 send_cmd_to_socket(cmd);
8342 if (cmd)
8343 g_free(cmd);
8345 argc--;
8346 argv++;
8348 exit(0);
8351 /* uri completion */
8352 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8354 /* go graphical */
8355 create_canvas();
8357 if (save_global_history)
8358 restore_global_history();
8360 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8361 restore_saved_tabs();
8362 else {
8363 a.s = named_session;
8364 a.i = XT_SES_DONOTHING;
8365 open_tabs(NULL, &a);
8368 while (argc) {
8369 create_new_tab(argv[0], NULL, focus);
8370 focus = 0;
8372 argc--;
8373 argv++;
8376 if (TAILQ_EMPTY(&tabs))
8377 create_new_tab(home, NULL, 1);
8379 if (enable_socket)
8380 if ((s = build_socket()) != -1)
8381 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8383 gtk_main();
8385 gnutls_global_deinit();
8387 return (0);