force tabless in kiosk mode
[xxxterm.git] / xxxterm.c
blobd1134f3e13d0a06830e36fa8d65a329cfa241e21
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)
419 #define XT_WL_PERSISTENT (1<<5)
420 #define XT_WL_SESSION (1<<6)
422 #define XT_SHOW (1<<7)
423 #define XT_DELETE (1<<8)
424 #define XT_SAVE (1<<9)
425 #define XT_OPEN (1<<10)
427 #define XT_CMD_OPEN (0)
428 #define XT_CMD_OPEN_CURRENT (1)
429 #define XT_CMD_TABNEW (2)
430 #define XT_CMD_TABNEW_CURRENT (3)
432 #define XT_STATUS_NOTHING (0)
433 #define XT_STATUS_LINK (1)
434 #define XT_STATUS_URI (2)
435 #define XT_STATUS_LOADING (3)
437 #define XT_SES_DONOTHING (0)
438 #define XT_SES_CLOSETABS (1)
440 #define XT_BM_NORMAL (0)
441 #define XT_BM_WHITELIST (1)
442 #define XT_BM_KIOSK (2)
444 /* mime types */
445 struct mime_type {
446 char *mt_type;
447 char *mt_action;
448 int mt_default;
449 int mt_download;
450 TAILQ_ENTRY(mime_type) entry;
452 TAILQ_HEAD(mime_type_list, mime_type);
454 /* uri aliases */
455 struct alias {
456 char *a_name;
457 char *a_uri;
458 TAILQ_ENTRY(alias) entry;
460 TAILQ_HEAD(alias_list, alias);
462 /* settings that require restart */
463 int tabless = 0; /* allow only 1 tab */
464 int enable_socket = 0;
465 int single_instance = 0; /* only allow one xxxterm to run */
466 int fancy_bar = 1; /* fancy toolbar */
467 int browser_mode = XT_BM_NORMAL;
469 /* runtime settings */
470 int show_tabs = 1; /* show tabs on notebook */
471 int show_url = 1; /* show url toolbar on notebook */
472 int show_statusbar = 0; /* vimperator style status bar */
473 int ctrl_click_focus = 0; /* ctrl click gets focus */
474 int cookies_enabled = 1; /* enable cookies */
475 int read_only_cookies = 0; /* enable to not write cookies */
476 int enable_scripts = 1;
477 int enable_plugins = 0;
478 int default_font_size = 12;
479 gfloat default_zoom_level = 1.0;
480 int window_height = 768;
481 int window_width = 1024;
482 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
483 unsigned refresh_interval = 10; /* download refresh interval */
484 int enable_cookie_whitelist = 0;
485 int enable_js_whitelist = 0;
486 time_t session_timeout = 3600; /* cookie session timeout */
487 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
488 char *ssl_ca_file = NULL;
489 char *resource_dir = NULL;
490 gboolean ssl_strict_certs = FALSE;
491 int append_next = 1; /* append tab after current tab */
492 char *home = NULL;
493 char *search_string = NULL;
494 char *http_proxy = NULL;
495 char download_dir[PATH_MAX];
496 char runtime_settings[PATH_MAX]; /* override of settings */
497 int allow_volatile_cookies = 0;
498 int save_global_history = 0; /* save global history to disk */
499 char *user_agent = NULL;
500 int save_rejected_cookies = 0;
501 time_t session_autosave = 0;
502 int guess_search = 0;
503 int dns_prefetch = FALSE;
504 gint max_connections = 25;
505 gint max_host_connections = 5;
507 struct settings;
508 struct key_binding;
509 int set_download_dir(struct settings *, char *);
510 int set_work_dir(struct settings *, char *);
511 int set_runtime_dir(struct settings *, char *);
512 int set_browser_mode(struct settings *, char *);
513 int set_cookie_policy(struct settings *, char *);
514 int add_alias(struct settings *, char *);
515 int add_mime_type(struct settings *, char *);
516 int add_cookie_wl(struct settings *, char *);
517 int add_js_wl(struct settings *, char *);
518 int add_kb(struct settings *, char *);
519 void button_set_stockid(GtkWidget *, char *);
520 GtkWidget * create_button(char *, char *, int);
522 char *get_browser_mode(struct settings *);
523 char *get_cookie_policy(struct settings *);
525 char *get_download_dir(struct settings *);
526 char *get_work_dir(struct settings *);
527 char *get_runtime_dir(struct settings *);
529 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
530 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
531 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
532 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
533 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
535 struct special {
536 int (*set)(struct settings *, char *);
537 char *(*get)(struct settings *);
538 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
541 struct special s_browser_mode = {
542 set_browser_mode,
543 get_browser_mode,
544 NULL
547 struct special s_cookie = {
548 set_cookie_policy,
549 get_cookie_policy,
550 NULL
553 struct special s_alias = {
554 add_alias,
555 NULL,
556 walk_alias
559 struct special s_mime = {
560 add_mime_type,
561 NULL,
562 walk_mime_type
565 struct special s_js = {
566 add_js_wl,
567 NULL,
568 walk_js_wl
571 struct special s_kb = {
572 add_kb,
573 NULL,
574 walk_kb
577 struct special s_cookie_wl = {
578 add_cookie_wl,
579 NULL,
580 walk_cookie_wl
583 struct special s_download_dir = {
584 set_download_dir,
585 get_download_dir,
586 NULL
589 struct special s_work_dir = {
590 set_work_dir,
591 get_work_dir,
592 NULL
595 struct settings {
596 char *name;
597 int type;
598 #define XT_S_INVALID (0)
599 #define XT_S_INT (1)
600 #define XT_S_STR (2)
601 #define XT_S_FLOAT (3)
602 uint32_t flags;
603 #define XT_SF_RESTART (1<<0)
604 #define XT_SF_RUNTIME (1<<1)
605 int *ival;
606 char **sval;
607 struct special *s;
608 gfloat *fval;
609 } rs[] = {
610 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
611 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
612 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
613 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
614 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
615 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
616 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
617 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
618 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
619 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
620 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
621 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
622 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
623 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
624 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
625 { "home", XT_S_STR, 0, NULL, &home, NULL },
626 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
627 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
628 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
629 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
630 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
631 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
632 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
633 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
634 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
635 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
636 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
637 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
638 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
639 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
640 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
641 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
642 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
643 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
644 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
645 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
646 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
647 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
648 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
650 /* runtime settings */
651 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
652 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
653 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
654 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
655 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
658 int about(struct tab *, struct karg *);
659 int blank(struct tab *, struct karg *);
660 int cookie_show_wl(struct tab *, struct karg *);
661 int js_show_wl(struct tab *, struct karg *);
662 int help(struct tab *, struct karg *);
663 int set(struct tab *, struct karg *);
664 int stats(struct tab *, struct karg *);
665 int marco(struct tab *, struct karg *);
666 const char * marco_message(int *);
667 int xtp_page_cl(struct tab *, struct karg *);
668 int xtp_page_dl(struct tab *, struct karg *);
669 int xtp_page_fl(struct tab *, struct karg *);
670 int xtp_page_hl(struct tab *, struct karg *);
672 #define XT_URI_ABOUT ("about:")
673 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
674 #define XT_URI_ABOUT_ABOUT ("about")
675 #define XT_URI_ABOUT_BLANK ("blank")
676 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
677 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
678 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
679 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
680 #define XT_URI_ABOUT_FAVORITES ("favorites")
681 #define XT_URI_ABOUT_HELP ("help")
682 #define XT_URI_ABOUT_HISTORY ("history")
683 #define XT_URI_ABOUT_JSWL ("jswl")
684 #define XT_URI_ABOUT_SET ("set")
685 #define XT_URI_ABOUT_STATS ("stats")
686 #define XT_URI_ABOUT_MARCO ("marco")
688 struct about_type {
689 char *name;
690 int (*func)(struct tab *, struct karg *);
691 } about_list[] = {
692 { XT_URI_ABOUT_ABOUT, about },
693 { XT_URI_ABOUT_BLANK, blank },
694 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
695 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
696 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
697 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
698 { XT_URI_ABOUT_HELP, help },
699 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
700 { XT_URI_ABOUT_JSWL, js_show_wl },
701 { XT_URI_ABOUT_SET, set },
702 { XT_URI_ABOUT_STATS, stats },
703 { XT_URI_ABOUT_MARCO, marco },
706 /* globals */
707 extern char *__progname;
708 char **start_argv;
709 struct passwd *pwd;
710 GtkWidget *main_window;
711 GtkNotebook *notebook;
712 GtkWidget *arrow, *abtn;
713 struct tab_list tabs;
714 struct history_list hl;
715 struct download_list downloads;
716 struct domain_list c_wl;
717 struct domain_list js_wl;
718 struct undo_tailq undos;
719 struct keybinding_list kbl;
720 int undo_count;
721 int updating_dl_tabs = 0;
722 int updating_hl_tabs = 0;
723 int updating_cl_tabs = 0;
724 int updating_fl_tabs = 0;
725 char *global_search;
726 uint64_t blocked_cookies = 0;
727 char named_session[PATH_MAX];
728 void update_favicon(struct tab *);
729 int icon_size_map(int);
731 GtkListStore *completion_model;
732 void completion_add(struct tab *);
733 void completion_add_uri(const gchar *);
735 void
736 sigchild(int sig)
738 int saved_errno, status;
739 pid_t pid;
741 saved_errno = errno;
743 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
744 if (pid == -1) {
745 if (errno == EINTR)
746 continue;
747 if (errno != ECHILD) {
749 clog_warn("sigchild: waitpid:");
752 break;
755 if (WIFEXITED(status)) {
756 if (WEXITSTATUS(status) != 0) {
758 clog_warnx("sigchild: child exit status: %d",
759 WEXITSTATUS(status));
762 } else {
764 clog_warnx("sigchild: child is terminated abnormally");
769 errno = saved_errno;
773 is_g_object_setting(GObject *o, char *str)
775 guint n_props = 0, i;
776 GParamSpec **proplist;
778 if (! G_IS_OBJECT(o))
779 return (0);
781 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
782 &n_props);
784 for (i=0; i < n_props; i++) {
785 if (! strcmp(proplist[i]->name, str))
786 return (1);
788 return (0);
791 void
792 load_webkit_string(struct tab *t, const char *str, gchar *title)
794 gchar *uri;
795 char file[PATH_MAX];
796 GdkPixbuf *pb;
798 /* we set this to indicate we want to manually do navaction */
799 if (t->bfl)
800 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
801 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
803 if (title) {
804 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
805 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
806 g_free(uri);
808 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
809 pb = gdk_pixbuf_new_from_file(file, NULL);
810 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
811 GTK_ENTRY_ICON_PRIMARY, pb);
812 gdk_pixbuf_unref(pb);
816 void
817 set_status(struct tab *t, gchar *s, int status)
819 gchar *type = NULL;
821 if (s == NULL)
822 return;
824 switch (status) {
825 case XT_STATUS_LOADING:
826 type = g_strdup_printf("Loading: %s", s);
827 s = type;
828 break;
829 case XT_STATUS_LINK:
830 type = g_strdup_printf("Link: %s", s);
831 if (!t->status)
832 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
833 s = type;
834 break;
835 case XT_STATUS_URI:
836 type = g_strdup_printf("%s", s);
837 if (!t->status) {
838 t->status = g_strdup(type);
840 s = type;
841 if (!t->status)
842 t->status = g_strdup(s);
843 break;
844 case XT_STATUS_NOTHING:
845 /* FALL THROUGH */
846 default:
847 break;
849 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
850 if (type)
851 g_free(type);
854 void
855 hide_oops(struct tab *t)
857 gtk_widget_hide(t->oops);
860 void
861 hide_cmd(struct tab *t)
863 gtk_widget_hide(t->cmd);
866 void
867 show_cmd(struct tab *t)
869 gtk_widget_hide(t->oops);
870 gtk_widget_show(t->cmd);
873 void
874 show_oops(struct tab *t, const char *fmt, ...)
876 va_list ap;
877 char *msg;
879 if (fmt == NULL)
880 return;
882 va_start(ap, fmt);
883 if (vasprintf(&msg, fmt, ap) == -1)
884 errx(1, "show_oops failed");
885 va_end(ap);
887 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
888 gtk_widget_hide(t->cmd);
889 gtk_widget_show(t->oops);
892 /* XXX collapse with show_oops */
893 void
894 show_oops_s(const char *fmt, ...)
896 va_list ap;
897 char *msg;
898 struct tab *ti, *t = NULL;
900 if (fmt == NULL)
901 return;
903 TAILQ_FOREACH(ti, &tabs, entry)
904 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
905 t = ti;
906 break;
908 if (t == NULL)
909 return;
911 va_start(ap, fmt);
912 if (vasprintf(&msg, fmt, ap) == -1)
913 errx(1, "show_oops_s failed");
914 va_end(ap);
916 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
917 gtk_widget_hide(t->cmd);
918 gtk_widget_show(t->oops);
921 char *
922 get_as_string(struct settings *s)
924 char *r = NULL;
926 if (s == NULL)
927 return (NULL);
929 if (s->s) {
930 if (s->s->get)
931 r = s->s->get(s);
932 else
933 warnx("get_as_string skip %s\n", s->name);
934 } else if (s->type == XT_S_INT)
935 r = g_strdup_printf("%d", *s->ival);
936 else if (s->type == XT_S_STR)
937 r = g_strdup(*s->sval);
938 else if (s->type == XT_S_FLOAT)
939 r = g_strdup_printf("%f", *s->fval);
940 else
941 r = g_strdup_printf("INVALID TYPE");
943 return (r);
946 void
947 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
949 int i;
950 char *s;
952 for (i = 0; i < LENGTH(rs); i++) {
953 if (rs[i].s && rs[i].s->walk)
954 rs[i].s->walk(&rs[i], cb, cb_args);
955 else {
956 s = get_as_string(&rs[i]);
957 cb(&rs[i], s, cb_args);
958 g_free(s);
964 set_browser_mode(struct settings *s, char *val)
966 if (!strcmp(val, "whitelist")) {
967 browser_mode = XT_BM_WHITELIST;
968 allow_volatile_cookies = 0;
969 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
970 cookies_enabled = 1;
971 enable_cookie_whitelist = 1;
972 read_only_cookies = 0;
973 save_rejected_cookies = 0;
974 session_timeout = 3600;
975 enable_scripts = 0;
976 enable_js_whitelist = 1;
977 } else if (!strcmp(val, "normal")) {
978 browser_mode = XT_BM_NORMAL;
979 allow_volatile_cookies = 0;
980 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
981 cookies_enabled = 1;
982 enable_cookie_whitelist = 0;
983 read_only_cookies = 0;
984 save_rejected_cookies = 0;
985 session_timeout = 3600;
986 enable_scripts = 1;
987 enable_js_whitelist = 0;
988 } else if (!strcmp(val, "kiosk")) {
989 browser_mode = XT_BM_KIOSK;
990 allow_volatile_cookies = 0;
991 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
992 cookies_enabled = 1;
993 enable_cookie_whitelist = 0;
994 read_only_cookies = 0;
995 save_rejected_cookies = 0;
996 session_timeout = 3600;
997 enable_scripts = 1;
998 enable_js_whitelist = 0;
999 show_tabs = 0;
1000 tabless = 1;
1001 } else
1002 return (1);
1004 return (0);
1007 char *
1008 get_browser_mode(struct settings *s)
1010 char *r = NULL;
1012 if (browser_mode == XT_BM_WHITELIST)
1013 r = g_strdup("whitelist");
1014 else if (browser_mode == XT_BM_NORMAL)
1015 r = g_strdup("normal");
1016 else if (browser_mode == XT_BM_KIOSK)
1017 r = g_strdup("kiosk");
1018 else
1019 return (NULL);
1021 return (r);
1025 set_cookie_policy(struct settings *s, char *val)
1027 if (!strcmp(val, "no3rdparty"))
1028 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1029 else if (!strcmp(val, "accept"))
1030 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1031 else if (!strcmp(val, "reject"))
1032 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1033 else
1034 return (1);
1036 return (0);
1039 char *
1040 get_cookie_policy(struct settings *s)
1042 char *r = NULL;
1044 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1045 r = g_strdup("no3rdparty");
1046 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1047 r = g_strdup("accept");
1048 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1049 r = g_strdup("reject");
1050 else
1051 return (NULL);
1053 return (r);
1056 char *
1057 get_download_dir(struct settings *s)
1059 if (download_dir[0] == '\0')
1060 return (0);
1061 return (g_strdup(download_dir));
1065 set_download_dir(struct settings *s, char *val)
1067 if (val[0] == '~')
1068 snprintf(download_dir, sizeof download_dir, "%s/%s",
1069 pwd->pw_dir, &val[1]);
1070 else
1071 strlcpy(download_dir, val, sizeof download_dir);
1073 return (0);
1077 * Session IDs.
1078 * We use these to prevent people putting xxxt:// URLs on
1079 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1081 #define XT_XTP_SES_KEY_SZ 8
1082 #define XT_XTP_SES_KEY_HEX_FMT \
1083 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1084 char *dl_session_key; /* downloads */
1085 char *hl_session_key; /* history list */
1086 char *cl_session_key; /* cookie list */
1087 char *fl_session_key; /* favorites list */
1089 char work_dir[PATH_MAX];
1090 char certs_dir[PATH_MAX];
1091 char cache_dir[PATH_MAX];
1092 char sessions_dir[PATH_MAX];
1093 char cookie_file[PATH_MAX];
1094 SoupURI *proxy_uri = NULL;
1095 SoupSession *session;
1096 SoupCookieJar *s_cookiejar;
1097 SoupCookieJar *p_cookiejar;
1098 char rc_fname[PATH_MAX];
1100 struct mime_type_list mtl;
1101 struct alias_list aliases;
1103 /* protos */
1104 struct tab *create_new_tab(char *, struct undo *, int);
1105 void delete_tab(struct tab *);
1106 void adjustfont_webkit(struct tab *, int);
1107 int run_script(struct tab *, char *);
1108 int download_rb_cmp(struct download *, struct download *);
1109 gboolean cmd_execute(struct tab *t, char *str);
1112 history_rb_cmp(struct history *h1, struct history *h2)
1114 return (strcmp(h1->uri, h2->uri));
1116 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1119 domain_rb_cmp(struct domain *d1, struct domain *d2)
1121 return (strcmp(d1->d, d2->d));
1123 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1125 char *
1126 get_work_dir(struct settings *s)
1128 if (work_dir[0] == '\0')
1129 return (0);
1130 return (g_strdup(work_dir));
1134 set_work_dir(struct settings *s, char *val)
1136 if (val[0] == '~')
1137 snprintf(work_dir, sizeof work_dir, "%s/%s",
1138 pwd->pw_dir, &val[1]);
1139 else
1140 strlcpy(work_dir, val, sizeof work_dir);
1142 return (0);
1146 * generate a session key to secure xtp commands.
1147 * pass in a ptr to the key in question and it will
1148 * be modified in place.
1150 void
1151 generate_xtp_session_key(char **key)
1153 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1155 /* free old key */
1156 if (*key)
1157 g_free(*key);
1159 /* make a new one */
1160 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1161 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1162 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1163 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1165 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1169 * validate a xtp session key.
1170 * return 1 if OK
1173 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1175 if (strcmp(trusted, untrusted) != 0) {
1176 show_oops(t, "%s: xtp session key mismatch possible spoof",
1177 __func__);
1178 return (0);
1181 return (1);
1185 download_rb_cmp(struct download *e1, struct download *e2)
1187 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1189 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1191 struct valid_url_types {
1192 char *type;
1193 } vut[] = {
1194 { "http://" },
1195 { "https://" },
1196 { "ftp://" },
1197 { "file://" },
1198 { XT_XTP_STR },
1202 valid_url_type(char *url)
1204 int i;
1206 for (i = 0; i < LENGTH(vut); i++)
1207 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1208 return (0);
1210 return (1);
1213 void
1214 print_cookie(char *msg, SoupCookie *c)
1216 if (c == NULL)
1217 return;
1219 if (msg)
1220 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1221 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1222 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1223 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1224 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1225 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1226 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1227 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1228 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1229 DNPRINTF(XT_D_COOKIE, "====================================\n");
1232 void
1233 walk_alias(struct settings *s,
1234 void (*cb)(struct settings *, char *, void *), void *cb_args)
1236 struct alias *a;
1237 char *str;
1239 if (s == NULL || cb == NULL) {
1240 show_oops_s("walk_alias invalid parameters");
1241 return;
1244 TAILQ_FOREACH(a, &aliases, entry) {
1245 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1246 cb(s, str, cb_args);
1247 g_free(str);
1251 char *
1252 match_alias(char *url_in)
1254 struct alias *a;
1255 char *arg;
1256 char *url_out = NULL, *search, *enc_arg;
1258 search = g_strdup(url_in);
1259 arg = search;
1260 if (strsep(&arg, " \t") == NULL) {
1261 show_oops_s("match_alias: NULL URL");
1262 goto done;
1265 TAILQ_FOREACH(a, &aliases, entry) {
1266 if (!strcmp(search, a->a_name))
1267 break;
1270 if (a != NULL) {
1271 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1272 a->a_name);
1273 if (arg != NULL) {
1274 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1275 url_out = g_strdup_printf(a->a_uri, enc_arg);
1276 g_free(enc_arg);
1277 } else
1278 url_out = g_strdup(a->a_uri);
1280 done:
1281 g_free(search);
1282 return (url_out);
1285 char *
1286 guess_url_type(char *url_in)
1288 struct stat sb;
1289 char *url_out = NULL, *enc_search = NULL;
1291 url_out = match_alias(url_in);
1292 if (url_out != NULL)
1293 return (url_out);
1295 if (guess_search) {
1297 * If there is no dot nor slash in the string and it isn't a
1298 * path to a local file and doesn't resolves to an IP, assume
1299 * that the user wants to search for the string.
1302 if (strchr(url_in, '.') == NULL &&
1303 strchr(url_in, '/') == NULL &&
1304 stat(url_in, &sb) != 0 &&
1305 gethostbyname(url_in) == NULL) {
1307 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1308 url_out = g_strdup_printf(search_string, enc_search);
1309 g_free(enc_search);
1310 return (url_out);
1314 /* XXX not sure about this heuristic */
1315 if (stat(url_in, &sb) == 0)
1316 url_out = g_strdup_printf("file://%s", url_in);
1317 else
1318 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1320 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1322 return (url_out);
1325 void
1326 load_uri(struct tab *t, gchar *uri)
1328 struct karg args;
1329 gchar *newuri = NULL;
1330 int i;
1332 if (uri == NULL)
1333 return;
1335 /* Strip leading spaces. */
1336 while(*uri && isspace(*uri))
1337 uri++;
1339 if (strlen(uri) == 0) {
1340 blank(t, NULL);
1341 return;
1344 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1345 for (i = 0; i < LENGTH(about_list); i++)
1346 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1347 bzero(&args, sizeof args);
1348 about_list[i].func(t, &args);
1349 return;
1351 show_oops(t, "invalid about page");
1352 return;
1355 if (valid_url_type(uri)) {
1356 newuri = guess_url_type(uri);
1357 uri = newuri;
1360 set_status(t, (char *)uri, XT_STATUS_LOADING);
1361 webkit_web_view_load_uri(t->wv, uri);
1363 if (newuri)
1364 g_free(newuri);
1367 const gchar *
1368 get_uri(WebKitWebView *wv)
1370 WebKitWebFrame *frame;
1371 const gchar *uri;
1373 frame = webkit_web_view_get_main_frame(wv);
1374 uri = webkit_web_frame_get_uri(frame);
1376 if (uri && strlen(uri) > 0)
1377 return (uri);
1378 else
1379 return (NULL);
1383 add_alias(struct settings *s, char *line)
1385 char *l, *alias;
1386 struct alias *a = NULL;
1388 if (s == NULL || line == NULL) {
1389 show_oops_s("add_alias invalid parameters");
1390 return (1);
1393 l = line;
1394 a = g_malloc(sizeof(*a));
1396 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1397 show_oops_s("add_alias: incomplete alias definition");
1398 goto bad;
1400 if (strlen(alias) == 0 || strlen(l) == 0) {
1401 show_oops_s("add_alias: invalid alias definition");
1402 goto bad;
1405 a->a_name = g_strdup(alias);
1406 a->a_uri = g_strdup(l);
1408 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1410 TAILQ_INSERT_TAIL(&aliases, a, entry);
1412 return (0);
1413 bad:
1414 if (a)
1415 g_free(a);
1416 return (1);
1420 add_mime_type(struct settings *s, char *line)
1422 char *mime_type;
1423 char *l;
1424 struct mime_type *m = NULL;
1425 int downloadfirst = 0;
1427 /* XXX this could be smarter */
1429 if (line == NULL && strlen(line) == 0) {
1430 show_oops_s("add_mime_type invalid parameters");
1431 return (1);
1434 l = line;
1435 if (*l == '@') {
1436 downloadfirst = 1;
1437 l++;
1439 m = g_malloc(sizeof(*m));
1441 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1442 show_oops_s("add_mime_type: invalid mime_type");
1443 goto bad;
1445 if (mime_type[strlen(mime_type) - 1] == '*') {
1446 mime_type[strlen(mime_type) - 1] = '\0';
1447 m->mt_default = 1;
1448 } else
1449 m->mt_default = 0;
1451 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1452 show_oops_s("add_mime_type: invalid mime_type");
1453 goto bad;
1456 m->mt_type = g_strdup(mime_type);
1457 m->mt_action = g_strdup(l);
1458 m->mt_download = downloadfirst;
1460 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1461 m->mt_type, m->mt_action, m->mt_default);
1463 TAILQ_INSERT_TAIL(&mtl, m, entry);
1465 return (0);
1466 bad:
1467 if (m)
1468 g_free(m);
1469 return (1);
1472 struct mime_type *
1473 find_mime_type(char *mime_type)
1475 struct mime_type *m, *def = NULL, *rv = NULL;
1477 TAILQ_FOREACH(m, &mtl, entry) {
1478 if (m->mt_default &&
1479 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1480 def = m;
1482 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1483 rv = m;
1484 break;
1488 if (rv == NULL)
1489 rv = def;
1491 return (rv);
1494 void
1495 walk_mime_type(struct settings *s,
1496 void (*cb)(struct settings *, char *, void *), void *cb_args)
1498 struct mime_type *m;
1499 char *str;
1501 if (s == NULL || cb == NULL)
1502 show_oops_s("walk_mime_type invalid parameters");
1504 TAILQ_FOREACH(m, &mtl, entry) {
1505 str = g_strdup_printf("%s%s --> %s",
1506 m->mt_type,
1507 m->mt_default ? "*" : "",
1508 m->mt_action);
1509 cb(s, str, cb_args);
1510 g_free(str);
1514 void
1515 wl_add(char *str, struct domain_list *wl, int handy)
1517 struct domain *d;
1518 int add_dot = 0;
1520 if (str == NULL || wl == NULL)
1521 return;
1522 if (strlen(str) < 2)
1523 return;
1525 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1527 /* treat *.moo.com the same as .moo.com */
1528 if (str[0] == '*' && str[1] == '.')
1529 str = &str[1];
1530 else if (str[0] == '.')
1531 str = &str[0];
1532 else
1533 add_dot = 1;
1535 d = g_malloc(sizeof *d);
1536 if (add_dot)
1537 d->d = g_strdup_printf(".%s", str);
1538 else
1539 d->d = g_strdup(str);
1540 d->handy = handy;
1542 if (RB_INSERT(domain_list, wl, d))
1543 goto unwind;
1545 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1546 return;
1547 unwind:
1548 if (d) {
1549 if (d->d)
1550 g_free(d->d);
1551 g_free(d);
1556 add_cookie_wl(struct settings *s, char *entry)
1558 wl_add(entry, &c_wl, 1);
1559 return (0);
1562 void
1563 walk_cookie_wl(struct settings *s,
1564 void (*cb)(struct settings *, char *, void *), void *cb_args)
1566 struct domain *d;
1568 if (s == NULL || cb == NULL) {
1569 show_oops_s("walk_cookie_wl invalid parameters");
1570 return;
1573 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1574 cb(s, d->d, cb_args);
1577 void
1578 walk_js_wl(struct settings *s,
1579 void (*cb)(struct settings *, char *, void *), void *cb_args)
1581 struct domain *d;
1583 if (s == NULL || cb == NULL) {
1584 show_oops_s("walk_js_wl invalid parameters");
1585 return;
1588 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1589 cb(s, d->d, cb_args);
1593 add_js_wl(struct settings *s, char *entry)
1595 wl_add(entry, &js_wl, 1 /* persistent */);
1596 return (0);
1599 struct domain *
1600 wl_find(const gchar *search, struct domain_list *wl)
1602 int i;
1603 struct domain *d = NULL, dfind;
1604 gchar *s = NULL;
1606 if (search == NULL || wl == NULL)
1607 return (NULL);
1608 if (strlen(search) < 2)
1609 return (NULL);
1611 if (search[0] != '.')
1612 s = g_strdup_printf(".%s", search);
1613 else
1614 s = g_strdup(search);
1616 for (i = strlen(s) - 1; i >= 0; i--) {
1617 if (s[i] == '.') {
1618 dfind.d = &s[i];
1619 d = RB_FIND(domain_list, wl, &dfind);
1620 if (d)
1621 goto done;
1625 done:
1626 if (s)
1627 g_free(s);
1629 return (d);
1632 struct domain *
1633 wl_find_uri(const gchar *s, struct domain_list *wl)
1635 int i;
1636 char *ss;
1637 struct domain *r;
1639 if (s == NULL || wl == NULL)
1640 return (NULL);
1642 if (!strncmp(s, "http://", strlen("http://")))
1643 s = &s[strlen("http://")];
1644 else if (!strncmp(s, "https://", strlen("https://")))
1645 s = &s[strlen("https://")];
1647 if (strlen(s) < 2)
1648 return (NULL);
1650 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1651 /* chop string at first slash */
1652 if (s[i] == '/' || s[i] == '\0') {
1653 ss = g_strdup(s);
1654 ss[i] = '\0';
1655 r = wl_find(ss, wl);
1656 g_free(ss);
1657 return (r);
1660 return (NULL);
1663 char *
1664 get_toplevel_domain(char *domain)
1666 char *s;
1667 int found = 0;
1669 if (domain == NULL)
1670 return (NULL);
1671 if (strlen(domain) < 2)
1672 return (NULL);
1674 s = &domain[strlen(domain) - 1];
1675 while (s != domain) {
1676 if (*s == '.') {
1677 found++;
1678 if (found == 2)
1679 return (s);
1681 s--;
1684 if (found)
1685 return (domain);
1687 return (NULL);
1691 settings_add(char *var, char *val)
1693 int i, rv, *p;
1694 gfloat *f;
1695 char **s;
1697 /* get settings */
1698 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1699 if (strcmp(var, rs[i].name))
1700 continue;
1702 if (rs[i].s) {
1703 if (rs[i].s->set(&rs[i], val))
1704 errx(1, "invalid value for %s: %s", var, val);
1705 rv = 1;
1706 break;
1707 } else
1708 switch (rs[i].type) {
1709 case XT_S_INT:
1710 p = rs[i].ival;
1711 *p = atoi(val);
1712 rv = 1;
1713 break;
1714 case XT_S_STR:
1715 s = rs[i].sval;
1716 if (s == NULL)
1717 errx(1, "invalid sval for %s",
1718 rs[i].name);
1719 if (*s)
1720 g_free(*s);
1721 *s = g_strdup(val);
1722 rv = 1;
1723 break;
1724 case XT_S_FLOAT:
1725 f = rs[i].fval;
1726 *f = atof(val);
1727 rv = 1;
1728 break;
1729 case XT_S_INVALID:
1730 default:
1731 errx(1, "invalid type for %s", var);
1733 break;
1735 return (rv);
1738 #define WS "\n= \t"
1739 void
1740 config_parse(char *filename, int runtime)
1742 FILE *config, *f;
1743 char *line, *cp, *var, *val;
1744 size_t len, lineno = 0;
1745 int handled;
1746 char file[PATH_MAX];
1747 struct stat sb;
1749 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1751 if (filename == NULL)
1752 return;
1754 if (runtime && runtime_settings[0] != '\0') {
1755 snprintf(file, sizeof file, "%s/%s",
1756 work_dir, runtime_settings);
1757 if (stat(file, &sb)) {
1758 warnx("runtime file doesn't exist, creating it");
1759 if ((f = fopen(file, "w")) == NULL)
1760 err(1, "runtime");
1761 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1762 fclose(f);
1764 } else
1765 strlcpy(file, filename, sizeof file);
1767 if ((config = fopen(file, "r")) == NULL) {
1768 warn("config_parse: cannot open %s", filename);
1769 return;
1772 for (;;) {
1773 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1774 if (feof(config) || ferror(config))
1775 break;
1777 cp = line;
1778 cp += (long)strspn(cp, WS);
1779 if (cp[0] == '\0') {
1780 /* empty line */
1781 free(line);
1782 continue;
1785 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1786 errx(1, "invalid config file entry: %s", line);
1788 cp += (long)strspn(cp, WS);
1790 if ((val = strsep(&cp, "\0")) == NULL)
1791 break;
1793 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1794 handled = settings_add(var, val);
1795 if (handled == 0)
1796 errx(1, "invalid conf file entry: %s=%s", var, val);
1798 free(line);
1801 fclose(config);
1804 char *
1805 js_ref_to_string(JSContextRef context, JSValueRef ref)
1807 char *s = NULL;
1808 size_t l;
1809 JSStringRef jsref;
1811 jsref = JSValueToStringCopy(context, ref, NULL);
1812 if (jsref == NULL)
1813 return (NULL);
1815 l = JSStringGetMaximumUTF8CStringSize(jsref);
1816 s = g_malloc(l);
1817 if (s)
1818 JSStringGetUTF8CString(jsref, s, l);
1819 JSStringRelease(jsref);
1821 return (s);
1824 void
1825 disable_hints(struct tab *t)
1827 bzero(t->hint_buf, sizeof t->hint_buf);
1828 bzero(t->hint_num, sizeof t->hint_num);
1829 run_script(t, "vimprobable_clear()");
1830 t->hints_on = 0;
1831 t->hint_mode = XT_HINT_NONE;
1834 void
1835 enable_hints(struct tab *t)
1837 bzero(t->hint_buf, sizeof t->hint_buf);
1838 run_script(t, "vimprobable_show_hints()");
1839 t->hints_on = 1;
1840 t->hint_mode = XT_HINT_NONE;
1843 #define XT_JS_OPEN ("open;")
1844 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1845 #define XT_JS_FIRE ("fire;")
1846 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1847 #define XT_JS_FOUND ("found;")
1848 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1851 run_script(struct tab *t, char *s)
1853 JSGlobalContextRef ctx;
1854 WebKitWebFrame *frame;
1855 JSStringRef str;
1856 JSValueRef val, exception;
1857 char *es, buf[128];
1859 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1860 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1862 frame = webkit_web_view_get_main_frame(t->wv);
1863 ctx = webkit_web_frame_get_global_context(frame);
1865 str = JSStringCreateWithUTF8CString(s);
1866 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1867 NULL, 0, &exception);
1868 JSStringRelease(str);
1870 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1871 if (val == NULL) {
1872 es = js_ref_to_string(ctx, exception);
1873 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1874 g_free(es);
1875 return (1);
1876 } else {
1877 es = js_ref_to_string(ctx, val);
1878 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1880 /* handle return value right here */
1881 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1882 disable_hints(t);
1883 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1886 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1887 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1888 &es[XT_JS_FIRE_LEN]);
1889 run_script(t, buf);
1890 disable_hints(t);
1893 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1894 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1895 disable_hints(t);
1898 g_free(es);
1901 return (0);
1905 hint(struct tab *t, struct karg *args)
1908 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1910 if (t->hints_on == 0)
1911 enable_hints(t);
1912 else
1913 disable_hints(t);
1915 return (0);
1918 void
1919 apply_style(struct tab *t)
1921 g_object_set(G_OBJECT(t->settings),
1922 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1926 userstyle(struct tab *t, struct karg *args)
1928 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1930 if (t->styled) {
1931 t->styled = 0;
1932 g_object_set(G_OBJECT(t->settings),
1933 "user-stylesheet-uri", NULL, (char *)NULL);
1934 } else {
1935 t->styled = 1;
1936 apply_style(t);
1938 return (0);
1942 * Doesn't work fully, due to the following bug:
1943 * https://bugs.webkit.org/show_bug.cgi?id=51747
1946 restore_global_history(void)
1948 char file[PATH_MAX];
1949 FILE *f;
1950 struct history *h;
1951 gchar *uri;
1952 gchar *title;
1954 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1956 if ((f = fopen(file, "r")) == NULL) {
1957 warnx("%s: fopen", __func__);
1958 return (1);
1961 for (;;) {
1962 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1963 if (feof(f) || ferror(f))
1964 break;
1966 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1967 if (feof(f) || ferror(f)) {
1968 free(uri);
1969 warnx("%s: broken history file\n", __func__);
1970 return (1);
1973 if (uri && strlen(uri) && title && strlen(title)) {
1974 webkit_web_history_item_new_with_data(uri, title);
1975 h = g_malloc(sizeof(struct history));
1976 h->uri = g_strdup(uri);
1977 h->title = g_strdup(title);
1978 RB_INSERT(history_list, &hl, h);
1979 completion_add_uri(h->uri);
1980 } else {
1981 warnx("%s: failed to restore history\n", __func__);
1982 free(uri);
1983 free(title);
1984 return (1);
1987 free(uri);
1988 free(title);
1989 uri = NULL;
1990 title = NULL;
1993 return (0);
1997 save_global_history_to_disk(struct tab *t)
1999 char file[PATH_MAX];
2000 FILE *f;
2001 struct history *h;
2003 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2005 if ((f = fopen(file, "w")) == NULL) {
2006 show_oops(t, "%s: global history file: %s",
2007 __func__, strerror(errno));
2008 return (1);
2011 RB_FOREACH_REVERSE(h, history_list, &hl) {
2012 if (h->uri && h->title)
2013 fprintf(f, "%s\n%s\n", h->uri, h->title);
2016 fclose(f);
2018 return (0);
2022 quit(struct tab *t, struct karg *args)
2024 if (save_global_history)
2025 save_global_history_to_disk(t);
2027 gtk_main_quit();
2029 return (1);
2033 open_tabs(struct tab *t, struct karg *a)
2035 char file[PATH_MAX];
2036 FILE *f = NULL;
2037 char *uri = NULL;
2038 int rv = 1;
2039 struct tab *ti, *tt;
2041 if (a == NULL)
2042 goto done;
2044 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2045 if ((f = fopen(file, "r")) == NULL)
2046 goto done;
2048 ti = TAILQ_LAST(&tabs, tab_list);
2050 for (;;) {
2051 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2052 if (feof(f) || ferror(f))
2053 break;
2055 /* retrieve session name */
2056 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2057 strlcpy(named_session,
2058 &uri[strlen(XT_SAVE_SESSION_ID)],
2059 sizeof named_session);
2060 continue;
2063 if (uri && strlen(uri))
2064 create_new_tab(uri, NULL, 1);
2066 free(uri);
2067 uri = NULL;
2070 /* close open tabs */
2071 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2072 for (;;) {
2073 tt = TAILQ_FIRST(&tabs);
2074 if (tt != ti) {
2075 delete_tab(tt);
2076 continue;
2078 delete_tab(tt);
2079 break;
2083 rv = 0;
2084 done:
2085 if (f)
2086 fclose(f);
2088 return (rv);
2092 restore_saved_tabs(void)
2094 char file[PATH_MAX];
2095 int unlink_file = 0;
2096 struct stat sb;
2097 struct karg a;
2098 int rv = 0;
2100 snprintf(file, sizeof file, "%s/%s",
2101 sessions_dir, XT_RESTART_TABS_FILE);
2102 if (stat(file, &sb) == -1)
2103 a.s = XT_SAVED_TABS_FILE;
2104 else {
2105 unlink_file = 1;
2106 a.s = XT_RESTART_TABS_FILE;
2109 a.i = XT_SES_DONOTHING;
2110 rv = open_tabs(NULL, &a);
2112 if (unlink_file)
2113 unlink(file);
2115 return (rv);
2119 save_tabs(struct tab *t, struct karg *a)
2121 char file[PATH_MAX];
2122 FILE *f;
2123 struct tab *ti;
2124 const gchar *uri;
2125 int len = 0, i;
2126 const gchar **arr = NULL;
2128 if (a == NULL)
2129 return (1);
2130 if (a->s == NULL)
2131 snprintf(file, sizeof file, "%s/%s",
2132 sessions_dir, named_session);
2133 else
2134 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2136 if ((f = fopen(file, "w")) == NULL) {
2137 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2138 return (1);
2141 /* save session name */
2142 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2144 /* save tabs, in the order they are arranged in the notebook */
2145 TAILQ_FOREACH(ti, &tabs, entry)
2146 len++;
2148 arr = g_malloc0(len * sizeof(gchar *));
2150 TAILQ_FOREACH(ti, &tabs, entry) {
2151 if ((uri = get_uri(ti->wv)) != NULL)
2152 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2155 for (i = 0; i < len; i++)
2156 if (arr[i])
2157 fprintf(f, "%s\n", arr[i]);
2159 g_free(arr);
2160 fclose(f);
2162 return (0);
2166 save_tabs_and_quit(struct tab *t, struct karg *args)
2168 struct karg a;
2170 a.s = NULL;
2171 save_tabs(t, &a);
2172 quit(t, NULL);
2174 return (1);
2178 yank_uri(struct tab *t, struct karg *args)
2180 const gchar *uri;
2181 GtkClipboard *clipboard;
2183 if ((uri = get_uri(t->wv)) == NULL)
2184 return (1);
2186 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2187 gtk_clipboard_set_text(clipboard, uri, -1);
2189 return (0);
2192 struct paste_args {
2193 struct tab *t;
2194 int i;
2197 void
2198 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2200 struct paste_args *pap;
2202 if (data == NULL || text == NULL || !strlen(text))
2203 return;
2205 pap = (struct paste_args *)data;
2207 switch(pap->i) {
2208 case XT_PASTE_CURRENT_TAB:
2209 load_uri(pap->t, (gchar *)text);
2210 break;
2211 case XT_PASTE_NEW_TAB:
2212 create_new_tab((gchar *)text, NULL, 1);
2213 break;
2216 g_free(pap);
2220 paste_uri(struct tab *t, struct karg *args)
2222 GtkClipboard *clipboard;
2223 struct paste_args *pap;
2225 pap = g_malloc(sizeof(struct paste_args));
2227 pap->t = t;
2228 pap->i = args->i;
2230 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2231 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2233 return (0);
2236 char *
2237 find_domain(const gchar *s, int add_dot)
2239 int i;
2240 char *r = NULL, *ss = NULL;
2242 if (s == NULL)
2243 return (NULL);
2245 if (!strncmp(s, "http://", strlen("http://")))
2246 s = &s[strlen("http://")];
2247 else if (!strncmp(s, "https://", strlen("https://")))
2248 s = &s[strlen("https://")];
2250 if (strlen(s) < 2)
2251 return (NULL);
2253 ss = g_strdup(s);
2254 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2255 /* chop string at first slash */
2256 if (ss[i] == '/' || ss[i] == '\0') {
2257 ss[i] = '\0';
2258 if (add_dot)
2259 r = g_strdup_printf(".%s", ss);
2260 else
2261 r = g_strdup(ss);
2262 break;
2264 g_free(ss);
2266 return (r);
2270 toggle_cwl(struct tab *t, struct karg *args)
2272 struct domain *d;
2273 const gchar *uri;
2274 char *dom = NULL, *dom_toggle = NULL;
2275 int es;
2277 if (args == NULL)
2278 return (1);
2280 uri = get_uri(t->wv);
2281 dom = find_domain(uri, 1);
2282 d = wl_find(dom, &c_wl);
2284 if (d == NULL)
2285 es = 0;
2286 else
2287 es = 1;
2289 if (args->i & XT_WL_TOGGLE)
2290 es = !es;
2291 else if ((args->i & XT_WL_ENABLE) && es != 1)
2292 es = 1;
2293 else if ((args->i & XT_WL_DISABLE) && es != 0)
2294 es = 0;
2296 if (args->i & XT_WL_TOPLEVEL)
2297 dom_toggle = get_toplevel_domain(dom);
2298 else
2299 dom_toggle = dom;
2301 if (es)
2302 /* enable cookies for domain */
2303 wl_add(dom_toggle, &c_wl, 0);
2304 else
2305 /* disable cookies for domain */
2306 RB_REMOVE(domain_list, &c_wl, d);
2308 webkit_web_view_reload(t->wv);
2310 g_free(dom);
2311 return (0);
2315 toggle_js(struct tab *t, struct karg *args)
2317 int es;
2318 const gchar *uri;
2319 struct domain *d;
2320 char *dom = NULL, *dom_toggle = NULL;
2322 if (args == NULL)
2323 return (1);
2325 g_object_get(G_OBJECT(t->settings),
2326 "enable-scripts", &es, (char *)NULL);
2327 if (args->i & XT_WL_TOGGLE)
2328 es = !es;
2329 else if ((args->i & XT_WL_ENABLE) && es != 1)
2330 es = 1;
2331 else if ((args->i & XT_WL_DISABLE) && es != 0)
2332 es = 0;
2333 else
2334 return (1);
2336 uri = get_uri(t->wv);
2337 dom = find_domain(uri, 1);
2339 if (uri == NULL || dom == NULL) {
2340 show_oops(t, "Can't toggle domain in JavaScript white list");
2341 goto done;
2344 if (args->i & XT_WL_TOPLEVEL)
2345 dom_toggle = get_toplevel_domain(dom);
2346 else
2347 dom_toggle = dom;
2349 if (es) {
2350 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2351 wl_add(dom_toggle, &js_wl, 0 /* session */);
2352 } else {
2353 d = wl_find(dom_toggle, &js_wl);
2354 if (d)
2355 RB_REMOVE(domain_list, &js_wl, d);
2356 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2358 g_object_set(G_OBJECT(t->settings),
2359 "enable-scripts", es, (char *)NULL);
2360 g_object_set(G_OBJECT(t->settings),
2361 "javascript-can-open-windows-automatically", es, (char *)NULL);
2362 webkit_web_view_set_settings(t->wv, t->settings);
2363 webkit_web_view_reload(t->wv);
2364 done:
2365 if (dom)
2366 g_free(dom);
2367 return (0);
2370 void
2371 js_toggle_cb(GtkWidget *w, struct tab *t)
2373 struct karg a;
2375 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2376 toggle_js(t, &a);
2380 toggle_src(struct tab *t, struct karg *args)
2382 gboolean mode;
2384 if (t == NULL)
2385 return (0);
2387 mode = webkit_web_view_get_view_source_mode(t->wv);
2388 webkit_web_view_set_view_source_mode(t->wv, !mode);
2389 webkit_web_view_reload(t->wv);
2391 return (0);
2394 void
2395 focus_webview(struct tab *t)
2397 if (t == NULL)
2398 return;
2400 /* only grab focus if we are visible */
2401 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2402 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2406 focus(struct tab *t, struct karg *args)
2408 if (t == NULL || args == NULL)
2409 return (1);
2411 if (show_url == 0)
2412 return (0);
2414 if (args->i == XT_FOCUS_URI)
2415 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2416 else if (args->i == XT_FOCUS_SEARCH)
2417 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2419 return (0);
2423 stats(struct tab *t, struct karg *args)
2425 char *stats, *s, line[64 * 1024];
2426 uint64_t line_count = 0;
2427 FILE *r_cookie_f;
2429 if (t == NULL)
2430 show_oops_s("stats invalid parameters");
2432 line[0] = '\0';
2433 if (save_rejected_cookies) {
2434 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2435 for (;;) {
2436 s = fgets(line, sizeof line, r_cookie_f);
2437 if (s == NULL || feof(r_cookie_f) ||
2438 ferror(r_cookie_f))
2439 break;
2440 line_count++;
2442 fclose(r_cookie_f);
2443 snprintf(line, sizeof line,
2444 "<br>Cookies blocked(*) total: %llu", line_count);
2445 } else
2446 show_oops(t, "Can't open blocked cookies file: %s",
2447 strerror(errno));
2450 stats = g_strdup_printf(XT_DOCTYPE
2451 "<html>"
2452 "<head>"
2453 "<title>Statistics</title>"
2454 "</head>"
2455 "<h1>Statistics</h1>"
2456 "<body>"
2457 "Cookies blocked(*) this session: %llu"
2458 "%s"
2459 "<p><small><b>*</b> results vary based on settings"
2460 "</body>"
2461 "</html>",
2462 blocked_cookies,
2463 line);
2465 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2466 g_free(stats);
2468 return (0);
2472 marco(struct tab *t, struct karg *args)
2474 char *message, line[64 * 1024];
2475 int len;
2477 if (t == NULL)
2478 show_oops_s("marco invalid parameters");
2480 line[0] = '\0';
2481 snprintf(line, sizeof line, "<br>%s", marco_message(&len));
2483 message = g_strdup_printf(XT_DOCTYPE
2484 "<html>"
2485 "<head>"
2486 "<title>Marco Sez...</title>"
2487 "</head>"
2488 "<h1>Moo</h1>"
2489 "<body>"
2490 "%s"
2491 "</body>"
2492 "</html>",
2493 line);
2495 load_webkit_string(t, message, XT_URI_ABOUT_MARCO);
2496 g_free(message);
2498 return (0);
2502 blank(struct tab *t, struct karg *args)
2504 if (t == NULL)
2505 show_oops_s("about invalid parameters");
2507 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2509 return (0);
2512 about(struct tab *t, struct karg *args)
2514 char *about;
2516 if (t == NULL)
2517 show_oops_s("about invalid parameters");
2519 about = g_strdup_printf(XT_DOCTYPE
2520 "<html>"
2521 "<head>"
2522 "<title>About</title>"
2523 "</head>"
2524 "<h1>About</h1>"
2525 "<body>"
2526 "<b>Version: %s</b><p>"
2527 "Authors:"
2528 "<ul>"
2529 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2530 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2531 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2532 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2533 "</ul>"
2534 "Copyrights and licenses can be found on the XXXterm "
2535 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2536 "</body>"
2537 "</html>",
2538 version
2541 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2542 g_free(about);
2544 return (0);
2548 help(struct tab *t, struct karg *args)
2550 char *help;
2552 if (t == NULL)
2553 show_oops_s("help invalid parameters");
2555 help = XT_DOCTYPE
2556 "<html>"
2557 "<head>"
2558 "<title>XXXterm</title>"
2559 "<meta http-equiv=\"REFRESH\" content=\"0;"
2560 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2561 "</head>"
2562 "<body>"
2563 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2564 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2565 "cgi-bin/man-cgi?xxxterm</a>"
2566 "</body>"
2567 "</html>"
2570 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2572 return (0);
2576 * update all favorite tabs apart from one. Pass NULL if
2577 * you want to update all.
2579 void
2580 update_favorite_tabs(struct tab *apart_from)
2582 struct tab *t;
2583 if (!updating_fl_tabs) {
2584 updating_fl_tabs = 1; /* stop infinite recursion */
2585 TAILQ_FOREACH(t, &tabs, entry)
2586 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2587 && (t != apart_from))
2588 xtp_page_fl(t, NULL);
2589 updating_fl_tabs = 0;
2593 /* show a list of favorites (bookmarks) */
2595 xtp_page_fl(struct tab *t, struct karg *args)
2597 char file[PATH_MAX];
2598 FILE *f;
2599 char *uri = NULL, *title = NULL;
2600 size_t len, lineno = 0;
2601 int i, failed = 0;
2602 char *header, *body, *tmp, *html = NULL;
2604 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2606 if (t == NULL)
2607 warn("%s: bad param", __func__);
2609 /* mark tab as favorite list */
2610 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2612 /* new session key */
2613 if (!updating_fl_tabs)
2614 generate_xtp_session_key(&fl_session_key);
2616 /* open favorites */
2617 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2618 if ((f = fopen(file, "r")) == NULL) {
2619 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2620 return (1);
2623 /* header */
2624 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2625 "<title>Favorites</title>\n"
2626 "%s"
2627 "</head>"
2628 "<h1>Favorites</h1>\n",
2629 XT_PAGE_STYLE);
2631 /* body */
2632 body = g_strdup_printf("<div align='center'><table><tr>"
2633 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2634 "<th style='width: 15%%'>Remove</th></tr>\n");
2636 for (i = 1;;) {
2637 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2638 if (feof(f) || ferror(f))
2639 break;
2640 if (len == 0) {
2641 free(title);
2642 title = NULL;
2643 continue;
2646 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2647 if (feof(f) || ferror(f)) {
2648 show_oops(t, "favorites file corrupt");
2649 failed = 1;
2650 break;
2653 tmp = body;
2654 body = g_strdup_printf("%s<tr>"
2655 "<td>%d</td>"
2656 "<td><a href='%s'>%s</a></td>"
2657 "<td style='text-align: center'>"
2658 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2659 "</tr>\n",
2660 body, i, uri, title,
2661 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2663 g_free(tmp);
2665 free(uri);
2666 uri = NULL;
2667 free(title);
2668 title = NULL;
2669 i++;
2671 fclose(f);
2673 /* if none, say so */
2674 if (i == 1) {
2675 tmp = body;
2676 body = g_strdup_printf("%s<tr>"
2677 "<td colspan='3' style='text-align: center'>"
2678 "No favorites - To add one use the 'favadd' command."
2679 "</td></tr>", body);
2680 g_free(tmp);
2683 if (uri)
2684 free(uri);
2685 if (title)
2686 free(title);
2688 /* render */
2689 if (!failed) {
2690 html = g_strdup_printf("%s%s</table></div></html>",
2691 header, body);
2692 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2695 update_favorite_tabs(t);
2697 if (header)
2698 g_free(header);
2699 if (body)
2700 g_free(body);
2701 if (html)
2702 g_free(html);
2704 return (failed);
2707 void
2708 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2709 size_t cert_count, char *title)
2711 gnutls_datum_t cinfo;
2712 char *tmp, *header, *body, *footer;
2713 int i;
2715 header = g_strdup_printf("<html><head><title>%s</title></head><body>", title);
2716 footer = g_strdup("</body></html>");
2717 body = g_strdup("");
2719 for (i = 0; i < cert_count; i++) {
2720 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2721 &cinfo))
2722 return;
2724 tmp = body;
2725 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2726 body, i, cinfo.data);
2727 gnutls_free(cinfo.data);
2728 g_free(tmp);
2731 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2732 g_free(header);
2733 g_free(body);
2734 g_free(footer);
2735 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2736 g_free(tmp);
2740 ca_cmd(struct tab *t, struct karg *args)
2742 FILE *f = NULL;
2743 int rv = 1, certs = 0, certs_read;
2744 struct stat sb;
2745 gnutls_datum dt;
2746 gnutls_x509_crt_t *c = NULL;
2747 char *certs_buf = NULL, *s;
2749 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2750 show_oops(t, "Can't open CA file: %s", strerror(errno));
2751 return (1);
2754 if (fstat(fileno(f), &sb) == -1) {
2755 show_oops(t, "Can't stat CA file: %s", strerror(errno));
2756 goto done;
2759 certs_buf = g_malloc(sb.st_size + 1);
2760 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2761 show_oops(t, "Can't read CA file: %s", strerror(errno));
2762 goto done;
2764 certs_buf[sb.st_size] = '\0';
2766 s = certs_buf;
2767 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2768 certs++;
2769 s += strlen("BEGIN CERTIFICATE");
2772 bzero(&dt, sizeof dt);
2773 dt.data = certs_buf;
2774 dt.size = sb.st_size;
2775 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2776 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2777 GNUTLS_X509_FMT_PEM, 0);
2778 if (certs_read <= 0) {
2779 show_oops(t, "No cert(s) available");
2780 goto done;
2782 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2783 done:
2784 if (c)
2785 g_free(c);
2786 if (certs_buf)
2787 g_free(certs_buf);
2788 if (f)
2789 fclose(f);
2791 return (rv);
2795 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2797 SoupURI *su = NULL;
2798 struct addrinfo hints, *res = NULL, *ai;
2799 int s = -1, on;
2800 char port[8];
2802 if (uri && !g_str_has_prefix(uri, "https://"))
2803 goto done;
2805 su = soup_uri_new(uri);
2806 if (su == NULL)
2807 goto done;
2808 if (!SOUP_URI_VALID_FOR_HTTP(su))
2809 goto done;
2811 snprintf(port, sizeof port, "%d", su->port);
2812 bzero(&hints, sizeof(struct addrinfo));
2813 hints.ai_flags = AI_CANONNAME;
2814 hints.ai_family = AF_UNSPEC;
2815 hints.ai_socktype = SOCK_STREAM;
2817 if (getaddrinfo(su->host, port, &hints, &res))
2818 goto done;
2820 for (ai = res; ai; ai = ai->ai_next) {
2821 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2822 continue;
2824 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2825 if (s < 0)
2826 goto done;
2827 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2828 sizeof(on)) == -1)
2829 goto done;
2831 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2832 goto done;
2835 if (domain)
2836 strlcpy(domain, su->host, domain_sz);
2837 done:
2838 if (su)
2839 soup_uri_free(su);
2840 if (res)
2841 freeaddrinfo(res);
2843 return (s);
2847 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2849 if (gsession)
2850 gnutls_deinit(gsession);
2851 if (xcred)
2852 gnutls_certificate_free_credentials(xcred);
2854 return (0);
2858 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2859 gnutls_certificate_credentials_t *xc)
2861 gnutls_certificate_credentials_t xcred;
2862 gnutls_session_t gsession;
2863 int rv = 1;
2865 if (gs == NULL || xc == NULL)
2866 goto done;
2868 *gs = NULL;
2869 *xc = NULL;
2871 gnutls_certificate_allocate_credentials(&xcred);
2872 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2873 GNUTLS_X509_FMT_PEM);
2874 gnutls_init(&gsession, GNUTLS_CLIENT);
2875 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2876 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2877 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2878 if ((rv = gnutls_handshake(gsession)) < 0) {
2879 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2881 gnutls_error_is_fatal(rv),
2882 gnutls_strerror_name(rv));
2883 stop_tls(gsession, xcred);
2884 goto done;
2887 gnutls_credentials_type_t cred;
2888 cred = gnutls_auth_get_type(gsession);
2889 if (cred != GNUTLS_CRD_CERTIFICATE) {
2890 stop_tls(gsession, xcred);
2891 goto done;
2894 *gs = gsession;
2895 *xc = xcred;
2896 rv = 0;
2897 done:
2898 return (rv);
2902 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2903 size_t *cert_count)
2905 unsigned int len;
2906 const gnutls_datum_t *cl;
2907 gnutls_x509_crt_t *all_certs;
2908 int i, rv = 1;
2910 if (certs == NULL || cert_count == NULL)
2911 goto done;
2912 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2913 goto done;
2914 cl = gnutls_certificate_get_peers(gsession, &len);
2915 if (len == 0)
2916 goto done;
2918 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2919 for (i = 0; i < len; i++) {
2920 gnutls_x509_crt_init(&all_certs[i]);
2921 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2922 GNUTLS_X509_FMT_PEM < 0)) {
2923 g_free(all_certs);
2924 goto done;
2928 *certs = all_certs;
2929 *cert_count = len;
2930 rv = 0;
2931 done:
2932 return (rv);
2935 void
2936 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2938 int i;
2940 for (i = 0; i < cert_count; i++)
2941 gnutls_x509_crt_deinit(certs[i]);
2942 g_free(certs);
2945 void
2946 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2947 size_t cert_count, char *domain)
2949 size_t cert_buf_sz;
2950 char cert_buf[64 * 1024], file[PATH_MAX];
2951 int i;
2952 FILE *f;
2953 GdkColor color;
2955 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2956 return;
2958 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2959 if ((f = fopen(file, "w")) == NULL) {
2960 show_oops(t, "Can't create cert file %s %s",
2961 file, strerror(errno));
2962 return;
2965 for (i = 0; i < cert_count; i++) {
2966 cert_buf_sz = sizeof cert_buf;
2967 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2968 cert_buf, &cert_buf_sz)) {
2969 show_oops(t, "gnutls_x509_crt_export failed");
2970 goto done;
2972 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2973 show_oops(t, "Can't write certs: %s", strerror(errno));
2974 goto done;
2978 /* not the best spot but oh well */
2979 gdk_color_parse("lightblue", &color);
2980 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2981 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2982 gdk_color_parse(XT_COLOR_BLACK, &color);
2983 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2984 done:
2985 fclose(f);
2989 load_compare_cert(struct tab *t, struct karg *args)
2991 const gchar *uri;
2992 char domain[8182], file[PATH_MAX];
2993 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2994 int s = -1, rv = 1, i;
2995 size_t cert_count;
2996 FILE *f = NULL;
2997 size_t cert_buf_sz;
2998 gnutls_session_t gsession;
2999 gnutls_x509_crt_t *certs;
3000 gnutls_certificate_credentials_t xcred;
3002 if (t == NULL)
3003 return (1);
3005 if ((uri = get_uri(t->wv)) == NULL)
3006 return (1);
3008 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3009 return (1);
3011 /* go ssl/tls */
3012 if (start_tls(t, s, &gsession, &xcred)) {
3013 show_oops(t, "Start TLS failed");
3014 goto done;
3017 /* get certs */
3018 if (get_connection_certs(gsession, &certs, &cert_count)) {
3019 show_oops(t, "Can't get connection certificates");
3020 goto done;
3023 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3024 if ((f = fopen(file, "r")) == NULL)
3025 goto freeit;
3027 for (i = 0; i < cert_count; i++) {
3028 cert_buf_sz = sizeof cert_buf;
3029 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3030 cert_buf, &cert_buf_sz)) {
3031 goto freeit;
3033 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3034 rv = -1; /* critical */
3035 goto freeit;
3037 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3038 rv = -1; /* critical */
3039 goto freeit;
3043 rv = 0;
3044 freeit:
3045 if (f)
3046 fclose(f);
3047 free_connection_certs(certs, cert_count);
3048 done:
3049 /* we close the socket first for speed */
3050 if (s != -1)
3051 close(s);
3052 stop_tls(gsession, xcred);
3054 return (rv);
3058 cert_cmd(struct tab *t, struct karg *args)
3060 const gchar *uri;
3061 char domain[8182];
3062 int s = -1;
3063 size_t cert_count;
3064 gnutls_session_t gsession;
3065 gnutls_x509_crt_t *certs;
3066 gnutls_certificate_credentials_t xcred;
3068 if (t == NULL)
3069 return (1);
3071 if ((uri = get_uri(t->wv)) == NULL) {
3072 show_oops(t, "Invalid URI");
3073 return (1);
3076 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3077 show_oops(t, "Invalid certidicate URI: %s", uri);
3078 return (1);
3081 /* go ssl/tls */
3082 if (start_tls(t, s, &gsession, &xcred)) {
3083 show_oops(t, "Start TLS failed");
3084 goto done;
3087 /* get certs */
3088 if (get_connection_certs(gsession, &certs, &cert_count)) {
3089 show_oops(t, "get_connection_certs failed");
3090 goto done;
3093 if (args->i & XT_SHOW)
3094 show_certs(t, certs, cert_count, "Certificate Chain");
3095 else if (args->i & XT_SAVE)
3096 save_certs(t, certs, cert_count, domain);
3098 free_connection_certs(certs, cert_count);
3099 done:
3100 /* we close the socket first for speed */
3101 if (s != -1)
3102 close(s);
3103 stop_tls(gsession, xcred);
3105 return (0);
3109 remove_cookie(int index)
3111 int i, rv = 1;
3112 GSList *cf;
3113 SoupCookie *c;
3115 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3117 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3119 for (i = 1; cf; cf = cf->next, i++) {
3120 if (i != index)
3121 continue;
3122 c = cf->data;
3123 print_cookie("remove cookie", c);
3124 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3125 rv = 0;
3126 break;
3129 soup_cookies_free(cf);
3131 return (rv);
3135 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3137 struct domain *d;
3138 char *tmp, *header, *body, *footer;
3140 /* we set this to indicate we want to manually do navaction */
3141 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3143 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3144 title, title);
3145 footer = g_strdup("</body></html>");
3146 body = g_strdup("");
3148 /* p list */
3149 if (args->i & XT_WL_PERSISTENT) {
3150 tmp = body;
3151 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3152 g_free(tmp);
3153 RB_FOREACH(d, domain_list, wl) {
3154 if (d->handy == 0)
3155 continue;
3156 tmp = body;
3157 body = g_strdup_printf("%s%s<br>", body, d->d);
3158 g_free(tmp);
3162 /* s list */
3163 if (args->i & XT_WL_SESSION) {
3164 tmp = body;
3165 body = g_strdup_printf("%s<h2>Session</h2>", body);
3166 g_free(tmp);
3167 RB_FOREACH(d, domain_list, wl) {
3168 if (d->handy == 1)
3169 continue;
3170 tmp = body;
3171 body = g_strdup_printf("%s%s<br>", body, d->d);
3172 g_free(tmp);
3176 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3177 g_free(header);
3178 g_free(body);
3179 g_free(footer);
3180 if (wl == &js_wl)
3181 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3182 else
3183 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3184 g_free(tmp);
3185 return (0);
3189 wl_save(struct tab *t, struct karg *args, int js)
3191 char file[PATH_MAX];
3192 FILE *f;
3193 char *line = NULL, *lt = NULL;
3194 size_t linelen;
3195 const gchar *uri;
3196 char *dom = NULL, *dom_save = NULL;
3197 struct karg a;
3198 struct domain *d;
3199 GSList *cf;
3200 SoupCookie *ci, *c;
3202 if (t == NULL || args == NULL)
3203 return (1);
3205 if (runtime_settings[0] == '\0')
3206 return (1);
3208 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3209 if ((f = fopen(file, "r+")) == NULL)
3210 return (1);
3212 uri = get_uri(t->wv);
3213 dom = find_domain(uri, 1);
3214 if (uri == NULL || dom == NULL) {
3215 show_oops(t, "Can't add domain to %s white list",
3216 js ? "JavaScript" : "cookie");
3217 goto done;
3220 if (args->i & XT_WL_TOPLEVEL) {
3221 /* save domain */
3222 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3223 show_oops(t, "invalid domain: %s", dom);
3224 goto done;
3226 } else if (args->i & XT_WL_FQDN) {
3227 /* save fqdn */
3228 dom_save = dom;
3229 } else
3230 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 |= args->i;
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 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3294 wl_show(t, args, "JavaScript White List", &js_wl);
3296 return (0);
3300 cookie_show_wl(struct tab *t, struct karg *args)
3302 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3303 wl_show(t, args, "Cookie White List", &c_wl);
3305 return (0);
3309 cookie_cmd(struct tab *t, struct karg *args)
3311 if (args->i & XT_SHOW)
3312 wl_show(t, args, "Cookie White List", &c_wl);
3313 else if (args->i & XT_WL_TOGGLE)
3314 toggle_cwl(t, args);
3315 else if (args->i & XT_SAVE)
3316 wl_save(t, args, 0);
3317 else if (args->i & XT_DELETE)
3318 show_oops(t, "'cookie delete' currently unimplemented");
3320 return (0);
3324 js_cmd(struct tab *t, struct karg *args)
3326 if (args->i & XT_SHOW)
3327 wl_show(t, args, "JavaScript White List", &js_wl);
3328 else if (args->i & XT_SAVE)
3329 wl_save(t, args, 1);
3330 else if (args->i & XT_WL_TOGGLE)
3331 toggle_js(t, args);
3332 else if (args->i & XT_DELETE)
3333 show_oops(t, "'js delete' currently unimplemented");
3335 return (0);
3339 add_favorite(struct tab *t, struct karg *args)
3341 char file[PATH_MAX];
3342 FILE *f;
3343 char *line = NULL;
3344 size_t urilen, linelen;
3345 const gchar *uri, *title;
3347 if (t == NULL)
3348 return (1);
3350 /* don't allow adding of xtp pages to favorites */
3351 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3352 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3353 return (1);
3356 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3357 if ((f = fopen(file, "r+")) == NULL) {
3358 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3359 return (1);
3362 title = webkit_web_view_get_title(t->wv);
3363 uri = get_uri(t->wv);
3365 if (title == NULL)
3366 title = uri;
3368 if (title == NULL || uri == NULL) {
3369 show_oops(t, "can't add page to favorites");
3370 goto done;
3373 urilen = strlen(uri);
3375 for (;;) {
3376 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3377 if (feof(f) || ferror(f))
3378 break;
3380 if (linelen == urilen && !strcmp(line, uri))
3381 goto done;
3383 free(line);
3384 line = NULL;
3387 fprintf(f, "\n%s\n%s", title, uri);
3388 done:
3389 if (line)
3390 free(line);
3391 fclose(f);
3393 update_favorite_tabs(NULL);
3395 return (0);
3399 navaction(struct tab *t, struct karg *args)
3401 WebKitWebHistoryItem *item;
3403 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3404 t->tab_id, args->i);
3406 if (t->item) {
3407 if (args->i == XT_NAV_BACK)
3408 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3409 else
3410 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3411 if (item == NULL)
3412 return (XT_CB_PASSTHROUGH);
3413 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3414 t->item = NULL;
3415 return (XT_CB_PASSTHROUGH);
3418 switch (args->i) {
3419 case XT_NAV_BACK:
3420 webkit_web_view_go_back(t->wv);
3421 break;
3422 case XT_NAV_FORWARD:
3423 webkit_web_view_go_forward(t->wv);
3424 break;
3425 case XT_NAV_RELOAD:
3426 webkit_web_view_reload(t->wv);
3427 break;
3428 case XT_NAV_RELOAD_CACHE:
3429 webkit_web_view_reload_bypass_cache(t->wv);
3430 break;
3432 return (XT_CB_PASSTHROUGH);
3436 move(struct tab *t, struct karg *args)
3438 GtkAdjustment *adjust;
3439 double pi, si, pos, ps, upper, lower, max;
3441 switch (args->i) {
3442 case XT_MOVE_DOWN:
3443 case XT_MOVE_UP:
3444 case XT_MOVE_BOTTOM:
3445 case XT_MOVE_TOP:
3446 case XT_MOVE_PAGEDOWN:
3447 case XT_MOVE_PAGEUP:
3448 case XT_MOVE_HALFDOWN:
3449 case XT_MOVE_HALFUP:
3450 adjust = t->adjust_v;
3451 break;
3452 default:
3453 adjust = t->adjust_h;
3454 break;
3457 pos = gtk_adjustment_get_value(adjust);
3458 ps = gtk_adjustment_get_page_size(adjust);
3459 upper = gtk_adjustment_get_upper(adjust);
3460 lower = gtk_adjustment_get_lower(adjust);
3461 si = gtk_adjustment_get_step_increment(adjust);
3462 pi = gtk_adjustment_get_page_increment(adjust);
3463 max = upper - ps;
3465 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3466 "max %f si %f pi %f\n",
3467 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3468 pos, ps, upper, lower, max, si, pi);
3470 switch (args->i) {
3471 case XT_MOVE_DOWN:
3472 case XT_MOVE_RIGHT:
3473 pos += si;
3474 gtk_adjustment_set_value(adjust, MIN(pos, max));
3475 break;
3476 case XT_MOVE_UP:
3477 case XT_MOVE_LEFT:
3478 pos -= si;
3479 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3480 break;
3481 case XT_MOVE_BOTTOM:
3482 case XT_MOVE_FARRIGHT:
3483 gtk_adjustment_set_value(adjust, max);
3484 break;
3485 case XT_MOVE_TOP:
3486 case XT_MOVE_FARLEFT:
3487 gtk_adjustment_set_value(adjust, lower);
3488 break;
3489 case XT_MOVE_PAGEDOWN:
3490 pos += pi;
3491 gtk_adjustment_set_value(adjust, MIN(pos, max));
3492 break;
3493 case XT_MOVE_PAGEUP:
3494 pos -= pi;
3495 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3496 break;
3497 case XT_MOVE_HALFDOWN:
3498 pos += pi / 2;
3499 gtk_adjustment_set_value(adjust, MIN(pos, max));
3500 break;
3501 case XT_MOVE_HALFUP:
3502 pos -= pi / 2;
3503 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3504 break;
3505 default:
3506 return (XT_CB_PASSTHROUGH);
3509 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3511 return (XT_CB_HANDLED);
3514 void
3515 url_set_visibility(void)
3517 struct tab *t;
3519 TAILQ_FOREACH(t, &tabs, entry) {
3520 if (show_url == 0) {
3521 gtk_widget_hide(t->toolbar);
3522 focus_webview(t);
3523 } else
3524 gtk_widget_show(t->toolbar);
3528 void
3529 notebook_tab_set_visibility(GtkNotebook *notebook)
3531 if (show_tabs == 0)
3532 gtk_notebook_set_show_tabs(notebook, FALSE);
3533 else
3534 gtk_notebook_set_show_tabs(notebook, TRUE);
3537 void
3538 statusbar_set_visibility(void)
3540 struct tab *t;
3542 TAILQ_FOREACH(t, &tabs, entry) {
3543 if (show_statusbar == 0) {
3544 gtk_widget_hide(t->statusbar);
3545 focus_webview(t);
3546 } else
3547 gtk_widget_show(t->statusbar);
3551 void
3552 url_set(struct tab *t, int enable_url_entry)
3554 GdkPixbuf *pixbuf;
3555 int progress;
3557 show_url = enable_url_entry;
3559 if (enable_url_entry) {
3560 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3561 GTK_ENTRY_ICON_PRIMARY, NULL);
3562 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3563 } else {
3564 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3565 GTK_ENTRY_ICON_PRIMARY);
3566 progress =
3567 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3568 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3569 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3570 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3571 progress);
3576 fullscreen(struct tab *t, struct karg *args)
3578 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3580 if (t == NULL)
3581 return (XT_CB_PASSTHROUGH);
3583 if (show_url == 0) {
3584 url_set(t, 1);
3585 show_tabs = 1;
3586 } else {
3587 url_set(t, 0);
3588 show_tabs = 0;
3591 url_set_visibility();
3592 notebook_tab_set_visibility(notebook);
3594 return (XT_CB_HANDLED);
3598 statusaction(struct tab *t, struct karg *args)
3600 int rv = XT_CB_HANDLED;
3602 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3604 if (t == NULL)
3605 return (XT_CB_PASSTHROUGH);
3607 switch (args->i) {
3608 case XT_STATUSBAR_SHOW:
3609 if (show_statusbar == 0) {
3610 show_statusbar = 1;
3611 statusbar_set_visibility();
3613 break;
3614 case XT_STATUSBAR_HIDE:
3615 if (show_statusbar == 1) {
3616 show_statusbar = 0;
3617 statusbar_set_visibility();
3619 break;
3621 return (rv);
3625 urlaction(struct tab *t, struct karg *args)
3627 int rv = XT_CB_HANDLED;
3629 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3631 if (t == NULL)
3632 return (XT_CB_PASSTHROUGH);
3634 switch (args->i) {
3635 case XT_URL_SHOW:
3636 if (show_url == 0) {
3637 url_set(t, 1);
3638 url_set_visibility();
3640 break;
3641 case XT_URL_HIDE:
3642 if (show_url == 1) {
3643 url_set(t, 0);
3644 url_set_visibility();
3646 break;
3648 return (rv);
3652 tabaction(struct tab *t, struct karg *args)
3654 int rv = XT_CB_HANDLED;
3655 char *url = args->s;
3656 struct undo *u;
3658 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3660 if (t == NULL)
3661 return (XT_CB_PASSTHROUGH);
3663 switch (args->i) {
3664 case XT_TAB_NEW:
3665 if (strlen(url) > 0)
3666 create_new_tab(url, NULL, 1);
3667 else
3668 create_new_tab(NULL, NULL, 1);
3669 break;
3670 case XT_TAB_DELETE:
3671 delete_tab(t);
3672 break;
3673 case XT_TAB_DELQUIT:
3674 if (gtk_notebook_get_n_pages(notebook) > 1)
3675 delete_tab(t);
3676 else
3677 quit(t, args);
3678 break;
3679 case XT_TAB_OPEN:
3680 if (strlen(url) > 0)
3682 else {
3683 rv = XT_CB_PASSTHROUGH;
3684 goto done;
3686 load_uri(t, url);
3687 break;
3688 case XT_TAB_SHOW:
3689 if (show_tabs == 0) {
3690 show_tabs = 1;
3691 notebook_tab_set_visibility(notebook);
3693 break;
3694 case XT_TAB_HIDE:
3695 if (show_tabs == 1) {
3696 show_tabs = 0;
3697 notebook_tab_set_visibility(notebook);
3699 break;
3700 case XT_TAB_UNDO_CLOSE:
3701 if (undo_count == 0) {
3702 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3703 goto done;
3704 } else {
3705 undo_count--;
3706 u = TAILQ_FIRST(&undos);
3707 create_new_tab(u->uri, u, 1);
3709 TAILQ_REMOVE(&undos, u, entry);
3710 g_free(u->uri);
3711 /* u->history is freed in create_new_tab() */
3712 g_free(u);
3714 break;
3715 default:
3716 rv = XT_CB_PASSTHROUGH;
3717 goto done;
3720 done:
3721 if (args->s) {
3722 g_free(args->s);
3723 args->s = NULL;
3726 return (rv);
3730 resizetab(struct tab *t, struct karg *args)
3732 if (t == NULL || args == NULL) {
3733 show_oops_s("resizetab invalid parameters");
3734 return (XT_CB_PASSTHROUGH);
3737 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3738 t->tab_id, args->i);
3740 adjustfont_webkit(t, args->i);
3742 return (XT_CB_HANDLED);
3746 movetab(struct tab *t, struct karg *args)
3748 struct tab *tt;
3749 int x;
3751 if (t == NULL || args == NULL) {
3752 show_oops_s("movetab invalid parameters");
3753 return (XT_CB_PASSTHROUGH);
3756 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3757 t->tab_id, args->i);
3759 if (args->i == XT_TAB_INVALID)
3760 return (XT_CB_PASSTHROUGH);
3762 if (args->i < XT_TAB_INVALID) {
3763 /* next or previous tab */
3764 if (TAILQ_EMPTY(&tabs))
3765 return (XT_CB_PASSTHROUGH);
3767 switch (args->i) {
3768 case XT_TAB_NEXT:
3769 /* if at the last page, loop around to the first */
3770 if (gtk_notebook_get_current_page(notebook) ==
3771 gtk_notebook_get_n_pages(notebook) - 1)
3772 gtk_notebook_set_current_page(notebook, 0);
3773 else
3774 gtk_notebook_next_page(notebook);
3775 break;
3776 case XT_TAB_PREV:
3777 /* if at the first page, loop around to the last */
3778 if (gtk_notebook_current_page(notebook) == 0)
3779 gtk_notebook_set_current_page(notebook,
3780 gtk_notebook_get_n_pages(notebook) - 1);
3781 else
3782 gtk_notebook_prev_page(notebook);
3783 break;
3784 case XT_TAB_FIRST:
3785 gtk_notebook_set_current_page(notebook, 0);
3786 break;
3787 case XT_TAB_LAST:
3788 gtk_notebook_set_current_page(notebook, -1);
3789 break;
3790 default:
3791 return (XT_CB_PASSTHROUGH);
3794 return (XT_CB_HANDLED);
3797 /* jump to tab */
3798 x = args->i - 1;
3799 if (t->tab_id == x) {
3800 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3801 return (XT_CB_HANDLED);
3804 TAILQ_FOREACH(tt, &tabs, entry) {
3805 if (tt->tab_id == x) {
3806 gtk_notebook_set_current_page(notebook, x);
3807 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3808 if (tt->focus_wv)
3809 focus_webview(tt);
3813 return (XT_CB_HANDLED);
3817 command(struct tab *t, struct karg *args)
3819 char *s = NULL, *ss = NULL;
3820 GdkColor color;
3821 const gchar *uri;
3823 if (t == NULL || args == NULL) {
3824 show_oops_s("command invalid parameters");
3825 return (XT_CB_PASSTHROUGH);
3828 switch (args->i) {
3829 case '/':
3830 s = "/";
3831 break;
3832 case '?':
3833 s = "?";
3834 break;
3835 case ':':
3836 s = ":";
3837 break;
3838 case XT_CMD_OPEN:
3839 s = ":open ";
3840 break;
3841 case XT_CMD_TABNEW:
3842 s = ":tabnew ";
3843 break;
3844 case XT_CMD_OPEN_CURRENT:
3845 s = ":open ";
3846 /* FALL THROUGH */
3847 case XT_CMD_TABNEW_CURRENT:
3848 if (!s) /* FALL THROUGH? */
3849 s = ":tabnew ";
3850 if ((uri = get_uri(t->wv)) != NULL) {
3851 ss = g_strdup_printf("%s%s", s, uri);
3852 s = ss;
3854 break;
3855 default:
3856 show_oops(t, "command: invalid opcode %d", args->i);
3857 return (XT_CB_PASSTHROUGH);
3860 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3862 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3863 gdk_color_parse(XT_COLOR_WHITE, &color);
3864 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3865 show_cmd(t);
3866 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3867 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3869 if (ss)
3870 g_free(ss);
3872 return (XT_CB_HANDLED);
3876 * Return a new string with a download row (in html)
3877 * appended. Old string is freed.
3879 char *
3880 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3883 WebKitDownloadStatus stat;
3884 char *status_html = NULL, *cmd_html = NULL, *new_html;
3885 gdouble progress;
3886 char cur_sz[FMT_SCALED_STRSIZE];
3887 char tot_sz[FMT_SCALED_STRSIZE];
3888 char *xtp_prefix;
3890 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3892 /* All actions wil take this form:
3893 * xxxt://class/seskey
3895 xtp_prefix = g_strdup_printf("%s%d/%s/",
3896 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3898 stat = webkit_download_get_status(dl->download);
3900 switch (stat) {
3901 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3902 status_html = g_strdup_printf("Finished");
3903 cmd_html = g_strdup_printf(
3904 "<a href='%s%d/%d'>Remove</a>",
3905 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3906 break;
3907 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3908 /* gather size info */
3909 progress = 100 * webkit_download_get_progress(dl->download);
3911 fmt_scaled(
3912 webkit_download_get_current_size(dl->download), cur_sz);
3913 fmt_scaled(
3914 webkit_download_get_total_size(dl->download), tot_sz);
3916 status_html = g_strdup_printf(
3917 "<div style='width: 100%%' align='center'>"
3918 "<div class='progress-outer'>"
3919 "<div class='progress-inner' style='width: %.2f%%'>"
3920 "</div></div></div>"
3921 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3922 progress, cur_sz, tot_sz, progress);
3924 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3925 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3927 break;
3928 /* LLL */
3929 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3930 status_html = g_strdup_printf("Cancelled");
3931 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3932 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3933 break;
3934 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3935 status_html = g_strdup_printf("Error!");
3936 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3937 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3938 break;
3939 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3940 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3941 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3942 status_html = g_strdup_printf("Starting");
3943 break;
3944 default:
3945 show_oops(t, "%s: unknown download status", __func__);
3948 new_html = g_strdup_printf(
3949 "%s\n<tr><td>%s</td><td>%s</td>"
3950 "<td style='text-align:center'>%s</td></tr>\n",
3951 html, basename(webkit_download_get_destination_uri(dl->download)),
3952 status_html, cmd_html);
3953 g_free(html);
3955 if (status_html)
3956 g_free(status_html);
3958 if (cmd_html)
3959 g_free(cmd_html);
3961 g_free(xtp_prefix);
3963 return new_html;
3967 * update all download tabs apart from one. Pass NULL if
3968 * you want to update all.
3970 void
3971 update_download_tabs(struct tab *apart_from)
3973 struct tab *t;
3974 if (!updating_dl_tabs) {
3975 updating_dl_tabs = 1; /* stop infinite recursion */
3976 TAILQ_FOREACH(t, &tabs, entry)
3977 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3978 && (t != apart_from))
3979 xtp_page_dl(t, NULL);
3980 updating_dl_tabs = 0;
3985 * update all cookie tabs apart from one. Pass NULL if
3986 * you want to update all.
3988 void
3989 update_cookie_tabs(struct tab *apart_from)
3991 struct tab *t;
3992 if (!updating_cl_tabs) {
3993 updating_cl_tabs = 1; /* stop infinite recursion */
3994 TAILQ_FOREACH(t, &tabs, entry)
3995 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3996 && (t != apart_from))
3997 xtp_page_cl(t, NULL);
3998 updating_cl_tabs = 0;
4003 * update all history tabs apart from one. Pass NULL if
4004 * you want to update all.
4006 void
4007 update_history_tabs(struct tab *apart_from)
4009 struct tab *t;
4011 if (!updating_hl_tabs) {
4012 updating_hl_tabs = 1; /* stop infinite recursion */
4013 TAILQ_FOREACH(t, &tabs, entry)
4014 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4015 && (t != apart_from))
4016 xtp_page_hl(t, NULL);
4017 updating_hl_tabs = 0;
4021 /* cookie management XTP page */
4023 xtp_page_cl(struct tab *t, struct karg *args)
4025 char *header, *body, *footer, *page, *tmp;
4026 int i = 1; /* all ids start 1 */
4027 GSList *sc, *pc, *pc_start;
4028 SoupCookie *c;
4029 char *type, *table_headers;
4030 char *last_domain = strdup("");
4032 DNPRINTF(XT_D_CMD, "%s", __func__);
4034 if (t == NULL) {
4035 show_oops_s("%s invalid parameters", __func__);
4036 return (1);
4038 /* mark this tab as cookie jar */
4039 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4041 /* Generate a new session key */
4042 if (!updating_cl_tabs)
4043 generate_xtp_session_key(&cl_session_key);
4045 /* header */
4046 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4047 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4048 "</head><body><h1>Cookie Jar</h1>\n");
4050 /* table headers */
4051 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4052 "<th>Type</th>"
4053 "<th>Name</th>"
4054 "<th>Value</th>"
4055 "<th>Path</th>"
4056 "<th>Expires</th>"
4057 "<th>Secure</th>"
4058 "<th>HTTP<br />only</th>"
4059 "<th>Rm</th></tr>\n");
4061 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4062 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4063 pc_start = pc;
4065 body = NULL;
4066 for (; sc; sc = sc->next) {
4067 c = sc->data;
4069 if (strcmp(last_domain, c->domain) != 0) {
4070 /* new domain */
4071 free(last_domain);
4072 last_domain = strdup(c->domain);
4074 if (body != NULL) {
4075 tmp = body;
4076 body = g_strdup_printf("%s</table></div>"
4077 "<h2>%s</h2>%s\n",
4078 body, c->domain, table_headers);
4079 g_free(tmp);
4080 } else {
4081 /* first domain */
4082 body = g_strdup_printf("<h2>%s</h2>%s\n",
4083 c->domain, table_headers);
4087 type = "Session";
4088 for (pc = pc_start; pc; pc = pc->next)
4089 if (soup_cookie_equal(pc->data, c)) {
4090 type = "Session + Persistent";
4091 break;
4094 tmp = body;
4095 body = g_strdup_printf(
4096 "%s\n<tr>"
4097 "<td style='width: text-align: center'>%s</td>"
4098 "<td style='width: 1px'>%s</td>"
4099 "<td style='width=70%%;overflow: visible'>"
4100 " <textarea rows='4'>%s</textarea>"
4101 "</td>"
4102 "<td>%s</td>"
4103 "<td>%s</td>"
4104 "<td style='width: 1px; text-align: center'>%d</td>"
4105 "<td style='width: 1px; text-align: center'>%d</td>"
4106 "<td style='width: 1px; text-align: center'>"
4107 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4108 body,
4109 type,
4110 c->name,
4111 c->value,
4112 c->path,
4113 c->expires ?
4114 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4115 c->secure,
4116 c->http_only,
4118 XT_XTP_STR,
4119 XT_XTP_CL,
4120 cl_session_key,
4121 XT_XTP_CL_REMOVE,
4125 g_free(tmp);
4126 i++;
4129 soup_cookies_free(sc);
4130 soup_cookies_free(pc);
4132 /* small message if there are none */
4133 if (i == 1) {
4134 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4135 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4138 /* footer */
4139 footer = g_strdup_printf("</table></div></body></html>");
4141 page = g_strdup_printf("%s%s%s", header, body, footer);
4143 g_free(header);
4144 g_free(body);
4145 g_free(footer);
4146 g_free(table_headers);
4147 g_free(last_domain);
4149 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4150 update_cookie_tabs(t);
4152 g_free(page);
4154 return (0);
4158 xtp_page_hl(struct tab *t, struct karg *args)
4160 char *header, *body, *footer, *page, *tmp;
4161 struct history *h;
4162 int i = 1; /* all ids start 1 */
4164 DNPRINTF(XT_D_CMD, "%s", __func__);
4166 if (t == NULL) {
4167 show_oops_s("%s invalid parameters", __func__);
4168 return (1);
4171 /* mark this tab as history manager */
4172 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4174 /* Generate a new session key */
4175 if (!updating_hl_tabs)
4176 generate_xtp_session_key(&hl_session_key);
4178 /* header */
4179 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4180 "<title>History</title>\n"
4181 "%s"
4182 "</head>"
4183 "<h1>History</h1>\n",
4184 XT_PAGE_STYLE);
4186 /* body */
4187 body = g_strdup_printf("<div align='center'><table><tr>"
4188 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4190 RB_FOREACH_REVERSE(h, history_list, &hl) {
4191 tmp = body;
4192 body = g_strdup_printf(
4193 "%s\n<tr>"
4194 "<td><a href='%s'>%s</a></td>"
4195 "<td>%s</td>"
4196 "<td style='text-align: center'>"
4197 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4198 body, h->uri, h->uri, h->title,
4199 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4200 XT_XTP_HL_REMOVE, i);
4202 g_free(tmp);
4203 i++;
4206 /* small message if there are none */
4207 if (i == 1) {
4208 tmp = body;
4209 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4210 "colspan='3'>No History</td></tr>\n", body);
4211 g_free(tmp);
4214 /* footer */
4215 footer = g_strdup_printf("</table></div></body></html>");
4217 page = g_strdup_printf("%s%s%s", header, body, footer);
4220 * update all history manager tabs as the xtp session
4221 * key has now changed. No need to update the current tab.
4222 * Already did that above.
4224 update_history_tabs(t);
4226 g_free(header);
4227 g_free(body);
4228 g_free(footer);
4230 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4231 g_free(page);
4233 return (0);
4237 * Generate a web page detailing the status of any downloads
4240 xtp_page_dl(struct tab *t, struct karg *args)
4242 struct download *dl;
4243 char *header, *body, *footer, *page, *tmp;
4244 char *ref;
4245 int n_dl = 1;
4247 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4249 if (t == NULL) {
4250 show_oops_s("%s invalid parameters", __func__);
4251 return (1);
4253 /* mark as a download manager tab */
4254 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4257 * Generate a new session key for next page instance.
4258 * This only happens for the top level call to xtp_page_dl()
4259 * in which case updating_dl_tabs is 0.
4261 if (!updating_dl_tabs)
4262 generate_xtp_session_key(&dl_session_key);
4264 /* header - with refresh so as to update */
4265 if (refresh_interval >= 1)
4266 ref = g_strdup_printf(
4267 "<meta http-equiv='refresh' content='%u"
4268 ";url=%s%d/%s/%d' />\n",
4269 refresh_interval,
4270 XT_XTP_STR,
4271 XT_XTP_DL,
4272 dl_session_key,
4273 XT_XTP_DL_LIST);
4274 else
4275 ref = g_strdup("");
4278 header = g_strdup_printf(
4279 "%s\n<head>"
4280 "<title>Downloads</title>\n%s%s</head>\n",
4281 XT_DOCTYPE XT_HTML_TAG,
4282 ref,
4283 XT_PAGE_STYLE);
4285 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4286 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4287 "</p><table><tr><th style='width: 60%%'>"
4288 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4289 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4291 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4292 body = xtp_page_dl_row(t, body, dl);
4293 n_dl++;
4296 /* message if no downloads in list */
4297 if (n_dl == 1) {
4298 tmp = body;
4299 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4300 " style='text-align: center'>"
4301 "No downloads</td></tr>\n", body);
4302 g_free(tmp);
4305 /* footer */
4306 footer = g_strdup_printf("</table></div></body></html>");
4308 page = g_strdup_printf("%s%s%s", header, body, footer);
4312 * update all download manager tabs as the xtp session
4313 * key has now changed. No need to update the current tab.
4314 * Already did that above.
4316 update_download_tabs(t);
4318 g_free(ref);
4319 g_free(header);
4320 g_free(body);
4321 g_free(footer);
4323 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4324 g_free(page);
4326 return (0);
4330 search(struct tab *t, struct karg *args)
4332 gboolean d;
4334 if (t == NULL || args == NULL) {
4335 show_oops_s("search invalid parameters");
4336 return (1);
4338 if (t->search_text == NULL) {
4339 if (global_search == NULL)
4340 return (XT_CB_PASSTHROUGH);
4341 else {
4342 t->search_text = g_strdup(global_search);
4343 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4344 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4348 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4349 t->tab_id, args->i, t->search_forward, t->search_text);
4351 switch (args->i) {
4352 case XT_SEARCH_NEXT:
4353 d = t->search_forward;
4354 break;
4355 case XT_SEARCH_PREV:
4356 d = !t->search_forward;
4357 break;
4358 default:
4359 return (XT_CB_PASSTHROUGH);
4362 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4364 return (XT_CB_HANDLED);
4367 struct settings_args {
4368 char **body;
4369 int i;
4372 void
4373 print_setting(struct settings *s, char *val, void *cb_args)
4375 char *tmp, *color;
4376 struct settings_args *sa = cb_args;
4378 if (sa == NULL)
4379 return;
4381 if (s->flags & XT_SF_RUNTIME)
4382 color = "#22cc22";
4383 else
4384 color = "#cccccc";
4386 tmp = *sa->body;
4387 *sa->body = g_strdup_printf(
4388 "%s\n<tr>"
4389 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4390 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4391 *sa->body,
4392 color,
4393 s->name,
4394 color,
4397 g_free(tmp);
4398 sa->i++;
4402 set(struct tab *t, struct karg *args)
4404 char *header, *body, *footer, *page, *tmp;
4405 int i = 1;
4406 struct settings_args sa;
4408 bzero(&sa, sizeof sa);
4409 sa.body = &body;
4411 /* header */
4412 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4413 "\n<head><title>Settings</title>\n"
4414 "</head><body><h1>Settings</h1>\n");
4416 /* body */
4417 body = g_strdup_printf("<div align='center'><table><tr>"
4418 "<th align='left'>Setting</th>"
4419 "<th align='left'>Value</th></tr>\n");
4421 settings_walk(print_setting, &sa);
4422 i = sa.i;
4424 /* small message if there are none */
4425 if (i == 1) {
4426 tmp = body;
4427 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4428 "colspan='2'>No settings</td></tr>\n", body);
4429 g_free(tmp);
4432 /* footer */
4433 footer = g_strdup_printf("</table></div></body></html>");
4435 page = g_strdup_printf("%s%s%s", header, body, footer);
4437 g_free(header);
4438 g_free(body);
4439 g_free(footer);
4441 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4443 return (XT_CB_PASSTHROUGH);
4447 session_save(struct tab *t, char *filename)
4449 struct karg a;
4450 int rv = 1;
4452 if (strlen(filename) == 0)
4453 goto done;
4455 if (filename[0] == '.' || filename[0] == '/')
4456 goto done;
4458 a.s = filename;
4459 if (save_tabs(t, &a))
4460 goto done;
4461 strlcpy(named_session, filename, sizeof named_session);
4463 rv = 0;
4464 done:
4465 return (rv);
4469 session_open(struct tab *t, char *filename)
4471 struct karg a;
4472 int rv = 1;
4474 if (strlen(filename) == 0)
4475 goto done;
4477 if (filename[0] == '.' || filename[0] == '/')
4478 goto done;
4480 a.s = filename;
4481 a.i = XT_SES_CLOSETABS;
4482 if (open_tabs(t, &a))
4483 goto done;
4485 strlcpy(named_session, filename, sizeof named_session);
4487 rv = 0;
4488 done:
4489 return (rv);
4493 session_delete(struct tab *t, char *filename)
4495 char file[PATH_MAX];
4496 int rv = 1;
4498 if (strlen(filename) == 0)
4499 goto done;
4501 if (filename[0] == '.' || filename[0] == '/')
4502 goto done;
4504 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4505 if (unlink(file))
4506 goto done;
4508 if (!strcmp(filename, named_session))
4509 strlcpy(named_session, XT_SAVED_TABS_FILE,
4510 sizeof named_session);
4512 rv = 0;
4513 done:
4514 return (rv);
4518 session_cmd(struct tab *t, struct karg *args)
4520 char *filename = args->s;
4522 if (t == NULL)
4523 return (1);
4525 if (args->i & XT_SHOW)
4526 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4527 XT_SAVED_TABS_FILE : named_session);
4528 else if (args->i & XT_SAVE) {
4529 if (session_save(t, filename)) {
4530 show_oops(t, "Can't save session: %s",
4531 filename ? filename : "INVALID");
4532 goto done;
4534 } else if (args->i & XT_OPEN) {
4535 if (session_open(t, filename)) {
4536 show_oops(t, "Can't open session: %s",
4537 filename ? filename : "INVALID");
4538 goto done;
4540 } else if (args->i & XT_DELETE) {
4541 if (session_delete(t, filename)) {
4542 show_oops(t, "Can't delete session: %s",
4543 filename ? filename : "INVALID");
4544 goto done;
4547 done:
4548 return (XT_CB_PASSTHROUGH);
4552 * Make a hardcopy of the page
4555 print_page(struct tab *t, struct karg *args)
4557 WebKitWebFrame *frame;
4558 GtkPageSetup *ps;
4559 GtkPrintOperation *op;
4560 GtkPrintOperationAction action;
4561 GtkPrintOperationResult print_res;
4562 GError *g_err = NULL;
4563 int marg_l, marg_r, marg_t, marg_b;
4565 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4567 ps = gtk_page_setup_new();
4568 op = gtk_print_operation_new();
4569 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4570 frame = webkit_web_view_get_main_frame(t->wv);
4572 /* the default margins are too small, so we will bump them */
4573 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4574 XT_PRINT_EXTRA_MARGIN;
4575 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4576 XT_PRINT_EXTRA_MARGIN;
4577 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4578 XT_PRINT_EXTRA_MARGIN;
4579 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4580 XT_PRINT_EXTRA_MARGIN;
4582 /* set margins */
4583 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4584 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4585 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4586 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4588 gtk_print_operation_set_default_page_setup(op, ps);
4590 /* this appears to free 'op' and 'ps' */
4591 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4593 /* check it worked */
4594 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4595 show_oops_s("can't print: %s", g_err->message);
4596 g_error_free (g_err);
4597 return (1);
4600 return (0);
4604 go_home(struct tab *t, struct karg *args)
4606 load_uri(t, home);
4607 return (0);
4611 restart(struct tab *t, struct karg *args)
4613 struct karg a;
4615 a.s = XT_RESTART_TABS_FILE;
4616 save_tabs(t, &a);
4617 execvp(start_argv[0], start_argv);
4618 /* NOTREACHED */
4620 return (0);
4623 #define CTRL GDK_CONTROL_MASK
4624 #define MOD1 GDK_MOD1_MASK
4625 #define SHFT GDK_SHIFT_MASK
4627 /* inherent to GTK not all keys will be caught at all times */
4628 /* XXX sort key bindings */
4629 struct key_binding {
4630 char *cmd;
4631 guint mask;
4632 guint use_in_entry;
4633 guint key;
4634 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4635 } keys[] = {
4636 { "cookiejar", MOD1, 0, GDK_j },
4637 { "downloadmgr", MOD1, 0, GDK_d },
4638 { "history", MOD1, 0, GDK_h },
4639 { "print", CTRL, 0, GDK_p },
4640 { "search", 0, 0, GDK_slash },
4641 { "searchb", 0, 0, GDK_question },
4642 { "command", 0, 0, GDK_colon },
4643 { "quit", CTRL, 0, GDK_q },
4644 { "restart", MOD1, 0, GDK_q },
4645 { "js toggle", CTRL, 0, GDK_j },
4646 { "cookie toggle", MOD1, 0, GDK_c },
4647 { "togglesrc", CTRL, 0, GDK_s },
4648 { "yankuri", 0, 0, GDK_y },
4649 { "pasteuricur", 0, 0, GDK_p },
4650 { "pasteurinew", 0, 0, GDK_P },
4652 /* search */
4653 { "searchnext", 0, 0, GDK_n },
4654 { "searchprevious", 0, 0, GDK_N },
4656 /* focus */
4657 { "focusaddress", 0, 0, GDK_F6 },
4658 { "focussearch", 0, 0, GDK_F7 },
4660 /* hinting */
4661 { "hinting", 0, 0, GDK_f },
4663 /* custom stylesheet */
4664 { "userstyle", 0, 0, GDK_i },
4666 /* navigation */
4667 { "goback", 0, 0, GDK_BackSpace },
4668 { "goback", MOD1, 0, GDK_Left },
4669 { "goforward", SHFT, 0, GDK_BackSpace },
4670 { "goforward", MOD1, 0, GDK_Right },
4671 { "reload", 0, 0, GDK_F5 },
4672 { "reload", CTRL, 0, GDK_r },
4673 { "reloadforce", CTRL, 0, GDK_R },
4674 { "reload", CTRL, 0, GDK_l },
4675 { "favorites", MOD1, 1, GDK_f },
4677 /* vertical movement */
4678 { "scrolldown", 0, 0, GDK_j },
4679 { "scrolldown", 0, 0, GDK_Down },
4680 { "scrollup", 0, 0, GDK_Up },
4681 { "scrollup", 0, 0, GDK_k },
4682 { "scrollbottom", 0, 0, GDK_G },
4683 { "scrollbottom", 0, 0, GDK_End },
4684 { "scrolltop", 0, 0, GDK_Home },
4685 { "scrolltop", 0, 0, GDK_g },
4686 { "scrollpagedown", 0, 0, GDK_space },
4687 { "scrollpagedown", CTRL, 0, GDK_f },
4688 { "scrollhalfdown", CTRL, 0, GDK_d },
4689 { "scrollpagedown", 0, 0, GDK_Page_Down },
4690 { "scrollpageup", 0, 0, GDK_Page_Up },
4691 { "scrollpageup", CTRL, 0, GDK_b },
4692 { "scrollhalfup", CTRL, 0, GDK_u },
4693 /* horizontal movement */
4694 { "scrollright", 0, 0, GDK_l },
4695 { "scrollright", 0, 0, GDK_Right },
4696 { "scrollleft", 0, 0, GDK_Left },
4697 { "scrollleft", 0, 0, GDK_h },
4698 { "scrollfarright", 0, 0, GDK_dollar },
4699 { "scrollfarleft", 0, 0, GDK_0 },
4701 /* tabs */
4702 { "tabnew", CTRL, 0, GDK_t },
4703 { "tabclose", CTRL, 1, GDK_w },
4704 { "tabundoclose", 0, 0, GDK_U },
4705 { "tabgoto1", CTRL, 0, GDK_1 },
4706 { "tabgoto2", CTRL, 0, GDK_2 },
4707 { "tabgoto3", CTRL, 0, GDK_3 },
4708 { "tabgoto4", CTRL, 0, GDK_4 },
4709 { "tabgoto5", CTRL, 0, GDK_5 },
4710 { "tabgoto6", CTRL, 0, GDK_6 },
4711 { "tabgoto7", CTRL, 0, GDK_7 },
4712 { "tabgoto8", CTRL, 0, GDK_8 },
4713 { "tabgoto9", CTRL, 0, GDK_9 },
4714 { "tabgoto10", CTRL, 0, GDK_0 },
4715 { "tabfirst", CTRL, 0, GDK_less },
4716 { "tablast", CTRL, 0, GDK_greater },
4717 { "tabprevious", CTRL, 0, GDK_Left },
4718 { "tabnext", CTRL, 0, GDK_Right },
4719 { "focusout", CTRL, 0, GDK_minus },
4720 { "focusin", CTRL, 0, GDK_plus },
4721 { "focusin", CTRL, 0, GDK_equal },
4723 /* command aliases (handy when -S flag is used) */
4724 { "promptopen", 0, 0, GDK_F9 },
4725 { "promptopencurrent", 0, 0, GDK_F10 },
4726 { "prompttabnew", 0, 0, GDK_F11 },
4727 { "prompttabnewcurrent",0, 0, GDK_F12 },
4729 TAILQ_HEAD(keybinding_list, key_binding);
4731 void
4732 walk_kb(struct settings *s,
4733 void (*cb)(struct settings *, char *, void *), void *cb_args)
4735 struct key_binding *k;
4736 char str[1024];
4738 if (s == NULL || cb == NULL) {
4739 show_oops_s("walk_kb invalid parameters");
4740 return;
4743 TAILQ_FOREACH(k, &kbl, entry) {
4744 if (k->cmd == NULL)
4745 continue;
4746 str[0] = '\0';
4748 /* sanity */
4749 if (gdk_keyval_name(k->key) == NULL)
4750 continue;
4752 strlcat(str, k->cmd, sizeof str);
4753 strlcat(str, ",", sizeof str);
4755 if (k->mask & GDK_SHIFT_MASK)
4756 strlcat(str, "S-", sizeof str);
4757 if (k->mask & GDK_CONTROL_MASK)
4758 strlcat(str, "C-", sizeof str);
4759 if (k->mask & GDK_MOD1_MASK)
4760 strlcat(str, "M1-", sizeof str);
4761 if (k->mask & GDK_MOD2_MASK)
4762 strlcat(str, "M2-", sizeof str);
4763 if (k->mask & GDK_MOD3_MASK)
4764 strlcat(str, "M3-", sizeof str);
4765 if (k->mask & GDK_MOD4_MASK)
4766 strlcat(str, "M4-", sizeof str);
4767 if (k->mask & GDK_MOD5_MASK)
4768 strlcat(str, "M5-", sizeof str);
4770 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4771 cb(s, str, cb_args);
4774 void
4775 init_keybindings(void)
4777 int i;
4778 struct key_binding *k;
4780 for (i = 0; i < LENGTH(keys); i++) {
4781 k = g_malloc0(sizeof *k);
4782 k->cmd = keys[i].cmd;
4783 k->mask = keys[i].mask;
4784 k->use_in_entry = keys[i].use_in_entry;
4785 k->key = keys[i].key;
4786 TAILQ_INSERT_HEAD(&kbl, k, entry);
4788 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4789 k->cmd ? k->cmd : "unnamed key");
4793 void
4794 keybinding_clearall(void)
4796 struct key_binding *k, *next;
4798 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4799 next = TAILQ_NEXT(k, entry);
4800 if (k->cmd == NULL)
4801 continue;
4803 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4804 k->cmd ? k->cmd : "unnamed key");
4805 TAILQ_REMOVE(&kbl, k, entry);
4806 g_free(k);
4811 keybinding_add(char *kb, char *value, struct key_binding *orig)
4813 struct key_binding *k;
4814 guint keyval, mask = 0;
4815 int i;
4817 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->cmd);
4819 if (orig == NULL)
4820 return (1);
4821 if (strcmp(kb, orig->cmd))
4822 return (1);
4824 /* find modifier keys */
4825 if (strstr(value, "S-"))
4826 mask |= GDK_SHIFT_MASK;
4827 if (strstr(value, "C-"))
4828 mask |= GDK_CONTROL_MASK;
4829 if (strstr(value, "M1-"))
4830 mask |= GDK_MOD1_MASK;
4831 if (strstr(value, "M2-"))
4832 mask |= GDK_MOD2_MASK;
4833 if (strstr(value, "M3-"))
4834 mask |= GDK_MOD3_MASK;
4835 if (strstr(value, "M4-"))
4836 mask |= GDK_MOD4_MASK;
4837 if (strstr(value, "M5-"))
4838 mask |= GDK_MOD5_MASK;
4840 /* find keyname */
4841 for (i = strlen(value) - 1; i > 0; i--)
4842 if (value[i] == '-')
4843 value = &value[i + 1];
4845 /* validate keyname */
4846 keyval = gdk_keyval_from_name(value);
4847 if (keyval == GDK_VoidSymbol) {
4848 warnx("invalid keybinding name %s", value);
4849 return (1);
4851 /* must run this test too, gtk+ doesn't handle 10 for example */
4852 if (gdk_keyval_name(keyval) == NULL) {
4853 warnx("invalid keybinding name %s", value);
4854 return (1);
4857 /* make sure it isn't a dupe */
4858 TAILQ_FOREACH(k, &kbl, entry)
4859 if (k->key == keyval && k->mask == mask) {
4860 warnx("duplicate keybinding for %s", value);
4861 return (1);
4864 /* add keyname */
4865 k = g_malloc0(sizeof *k);
4866 k->cmd = orig->cmd;
4867 k->mask = mask;
4868 k->use_in_entry = orig->use_in_entry;
4869 k->key = keyval;
4871 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4872 k->name,
4873 k->mask,
4874 k->use_in_entry,
4875 k->key);
4876 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4877 k->name, gdk_keyval_name(keyval));
4879 TAILQ_INSERT_HEAD(&kbl, k, entry);
4881 return (0);
4885 add_kb(struct settings *s, char *entry)
4887 int i;
4888 char *kb, *value;
4890 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4892 /* clearall is special */
4893 if (!strcmp(entry, "clearall")) {
4894 keybinding_clearall();
4895 return (0);
4898 kb = strstr(entry, ",");
4899 if (kb == NULL)
4900 return (1);
4901 *kb = '\0';
4902 value = kb + 1;
4904 /* make sure it is a valid keybinding */
4905 for (i = 0; i < LENGTH(keys); i++)
4906 if (keys[i].cmd && !strcmp(entry, keys[i].cmd)) {
4907 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4908 keys[i].cmd,
4909 keys[i].mask,
4910 keys[i].use_in_entry,
4911 keys[i].key);
4913 return (keybinding_add(entry, value, &keys[i]));
4916 return (1);
4919 struct cmd {
4920 char *cmd;
4921 int level;
4922 int (*func)(struct tab *, struct karg *);
4923 struct karg arg;
4924 bool userarg; /* allow free text arg */
4925 } cmds[] = {
4926 { "command", 0, command, {.i = ':'}, FALSE },
4927 { "search", 0, command, {.i = '/'}, FALSE },
4928 { "searchb", 0, command, {.i = '?'}, FALSE },
4929 { "togglesrc", 0, toggle_src, {0}, FALSE },
4931 /* yanking and pasting */
4932 { "yankuri", 0, yank_uri, {0}, FALSE },
4933 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4934 { "pasteuricur", 0, paste_uri, {.i = XT_PASTE_CURRENT_TAB}, FALSE },
4935 { "pasteurinew", 0, paste_uri, {.i = XT_PASTE_NEW_TAB}, FALSE },
4937 /* search */
4938 { "searchnext", 0, search, {.i = XT_SEARCH_NEXT}, FALSE },
4939 { "searchprevious", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
4940 { "searchprev", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
4942 /* focus */
4943 { "focusaddress", 0, focus, {.i = XT_FOCUS_URI}, FALSE },
4944 { "focussearch", 0, focus, {.i = XT_FOCUS_SEARCH}, FALSE },
4946 /* hinting */
4947 { "hinting", 0, hint, {.i = 0}, FALSE },
4949 /* custom stylesheet */
4950 { "userstyle", 0, userstyle, {.i = 0 }, FALSE },
4952 /* navigation */
4953 { "goback", 0, navaction, {.i = XT_NAV_BACK}, FALSE },
4954 { "goforward", 0, navaction, {.i = XT_NAV_FORWARD}, FALSE },
4955 { "reload", 0, navaction, {.i = XT_NAV_RELOAD}, FALSE },
4956 { "reloadforce", 0, navaction, {.i = XT_NAV_RELOAD_CACHE}, FALSE },
4958 /* vertical movement */
4959 { "scrolldown", 0, move, {.i = XT_MOVE_DOWN}, FALSE },
4960 { "scrollup", 0, move, {.i = XT_MOVE_UP}, FALSE },
4961 { "scrollbottom", 0, move, {.i = XT_MOVE_BOTTOM}, FALSE },
4962 { "scrolltop", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4963 { "1", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4964 { "scrollhalfdown", 0, move, {.i = XT_MOVE_HALFDOWN},FALSE },
4965 { "scrollhalfup", 0, move, {.i = XT_MOVE_HALFUP}, FALSE },
4966 { "scrollpagedown", 0, move, {.i = XT_MOVE_PAGEDOWN},FALSE },
4967 { "scrollpageup", 0, move, {.i = XT_MOVE_PAGEUP}, FALSE },
4968 /* horizontal movement */
4969 { "scrollright", 0, move, {.i = XT_MOVE_RIGHT}, FALSE },
4970 { "scrollleft", 0, move, {.i = XT_MOVE_LEFT}, FALSE },
4971 { "scrollfarright", 0, move, {.i = XT_MOVE_FARRIGHT},FALSE },
4972 { "scrollfarleft", 0, move, {.i = XT_MOVE_FARLEFT}, FALSE },
4975 { "favorites", 0, xtp_page_fl, {0}, FALSE },
4976 { "fav", 0, xtp_page_fl, {0}, FALSE },
4977 { "favadd", 0, add_favorite, {0}, FALSE },
4979 { "quit", 0, quit, {0}, FALSE },
4980 { "q!", 0, quit, {0}, FALSE },
4981 { "qa", 0, quit, {0}, FALSE },
4982 { "qa!", 0, quit, {0}, FALSE },
4983 { "w", 0, save_tabs, {0}, FALSE },
4984 { "wq", 0, save_tabs_and_quit, {0}, FALSE },
4985 { "wq!", 0, save_tabs_and_quit, {0}, FALSE },
4986 { "help", 0, help, {0}, FALSE },
4987 { "about", 0, about, {0}, FALSE },
4988 { "stats", 0, stats, {0}, FALSE },
4989 { "version", 0, about, {0}, FALSE },
4990 { "cookiejar", 0, xtp_page_cl, {0}, FALSE },
4992 /* js command */
4993 { "js", 0, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4994 { "save", 1, js_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
4995 { "domain", 2, js_cmd, {.i = XT_SAVE | XT_WL_TOPLEVEL}, FALSE },
4996 { "fqdn", 2, js_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
4997 { "show", 1, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4998 { "all", 2, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4999 { "persistent", 2, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT}, FALSE },
5000 { "session", 2, js_cmd, {.i = XT_SHOW | XT_WL_SESSION}, FALSE },
5001 { "toggle", 1, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5002 { "domain", 2, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL}, FALSE },
5003 { "fqdn", 2, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5005 /* cookie command */
5006 { "cookie", 0, cookie_cmd, {0}, FALSE },
5007 { "save", 1, cookie_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
5008 { "domain", 2, cookie_cmd, {.i = XT_SAVE | XT_WL_TOPLEVEL}, FALSE },
5009 { "fqdn", 2, cookie_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
5010 { "show", 1, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
5011 { "all", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
5012 { "persistent", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT}, FALSE },
5013 { "session", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_SESSION}, FALSE },
5014 { "toggle", 1, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5015 { "domain", 2, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL}, FALSE },
5016 { "fqdn", 2, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5018 /* cert command */
5019 { "cert", 0, cert_cmd, {.i = XT_SHOW}, FALSE },
5020 { "save", 1, cert_cmd, {.i = XT_SAVE}, FALSE },
5021 { "show", 1, cert_cmd, {.i = XT_SHOW}, FALSE },
5023 { "ca", 0, ca_cmd, {0}, FALSE },
5024 { "downloadmgr", 0, xtp_page_dl, {0}, FALSE },
5025 { "dl", 0, xtp_page_dl, {0}, FALSE },
5026 { "h", 0, xtp_page_hl, {0}, FALSE },
5027 { "hist", 0, xtp_page_hl, {0}, FALSE },
5028 { "history", 0, xtp_page_hl, {0}, FALSE },
5029 { "home", 0, go_home, {0}, FALSE },
5030 { "restart", 0, restart, {0}, FALSE },
5031 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5032 { "urlh", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5033 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5034 { "urls", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5035 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5036 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5037 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5038 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5040 { "print", 0, print_page, {0}, FALSE },
5042 /* tabs */
5043 { "o", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5044 { "op", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5045 { "open", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5046 { "tabnew", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5047 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5048 { "tabe", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5049 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5050 { "tabundoclose", 0, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
5051 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5052 { "tabshow", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5053 { "tabs", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5054 { "tabhide", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5055 { "tabh", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5056 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5057 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5058 /* XXX add count to these commands */
5059 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5060 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5061 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5062 { "tabr", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5063 { "tablast", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5064 { "tabl", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5065 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5066 { "tabprev", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5067 { "tabp", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5068 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT}, FALSE },
5069 { "tabn", 0, movetab, {.i = XT_TAB_NEXT}, FALSE },
5070 { "tabgoto1", 0, movetab, {.i = 1}, FALSE },
5071 { "tabgoto2", 0, movetab, {.i = 2}, FALSE },
5072 { "tabgoto3", 0, movetab, {.i = 3}, FALSE },
5073 { "tabgoto4", 0, movetab, {.i = 4}, FALSE },
5074 { "tabgoto5", 0, movetab, {.i = 5}, FALSE },
5075 { "tabgoto6", 0, movetab, {.i = 6}, FALSE },
5076 { "tabgoto7", 0, movetab, {.i = 7}, FALSE },
5077 { "tabgoto8", 0, movetab, {.i = 8}, FALSE },
5078 { "tabgoto9", 0, movetab, {.i = 9}, FALSE },
5079 { "tabgoto10", 0, movetab, {.i = 10}, FALSE },
5080 { "focusout", 0, resizetab, {.i = -1}, FALSE },
5081 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5082 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5084 /* command aliases (handy when -S flag is used) */
5085 { "promptopen", 0, command, {.i = XT_CMD_OPEN}, FALSE },
5086 { "promptopencurrent", 0, command, {.i = XT_CMD_OPEN_CURRENT}, FALSE },
5087 { "prompttabnew", 0, command, {.i = XT_CMD_TABNEW}, FALSE },
5088 { "prompttabnewcurrent",0, command, {.i = XT_CMD_TABNEW_CURRENT}, FALSE },
5090 /* settings */
5091 { "set", 0, set, {0}, FALSE },
5092 { "fullscreen", 0, fullscreen, {0}, FALSE },
5093 { "f", 0, fullscreen, {0}, FALSE },
5095 /* sessions */
5096 { "session", 0, session_cmd, {.i = XT_SHOW}, FALSE },
5097 { "delete", 1, session_cmd, {.i = XT_DELETE}, TRUE },
5098 { "open", 1, session_cmd, {.i = XT_OPEN}, TRUE },
5099 { "save", 1, session_cmd, {.i = XT_SAVE}, TRUE },
5100 { "show", 1, session_cmd, {.i = XT_SHOW}, FALSE },
5103 struct {
5104 int index;
5105 int len;
5106 gchar *list[256];
5107 } cmd_status = {-1, 0};
5109 gboolean
5110 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5112 struct karg a;
5114 hide_oops(t);
5116 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5117 /* go backward */
5118 a.i = XT_NAV_BACK;
5119 navaction(t, &a);
5121 return (TRUE);
5122 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5123 /* go forward */
5124 a.i = XT_NAV_FORWARD;
5125 navaction(t, &a);
5127 return (TRUE);
5130 return (FALSE);
5133 gboolean
5134 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5136 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5138 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5139 delete_tab(t);
5141 return (FALSE);
5145 * cancel, remove, etc. downloads
5147 void
5148 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5150 struct download find, *d = NULL;
5152 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5154 /* some commands require a valid download id */
5155 if (cmd != XT_XTP_DL_LIST) {
5156 /* lookup download in question */
5157 find.id = id;
5158 d = RB_FIND(download_list, &downloads, &find);
5160 if (d == NULL) {
5161 show_oops(t, "%s: no such download", __func__);
5162 return;
5166 /* decide what to do */
5167 switch (cmd) {
5168 case XT_XTP_DL_CANCEL:
5169 webkit_download_cancel(d->download);
5170 break;
5171 case XT_XTP_DL_REMOVE:
5172 webkit_download_cancel(d->download); /* just incase */
5173 g_object_unref(d->download);
5174 RB_REMOVE(download_list, &downloads, d);
5175 break;
5176 case XT_XTP_DL_LIST:
5177 /* Nothing */
5178 break;
5179 default:
5180 show_oops(t, "%s: unknown command", __func__);
5181 break;
5183 xtp_page_dl(t, NULL);
5187 * Actions on history, only does one thing for now, but
5188 * we provide the function for future actions
5190 void
5191 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5193 struct history *h, *next;
5194 int i = 1;
5196 switch (cmd) {
5197 case XT_XTP_HL_REMOVE:
5198 /* walk backwards, as listed in reverse */
5199 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5200 next = RB_PREV(history_list, &hl, h);
5201 if (id == i) {
5202 RB_REMOVE(history_list, &hl, h);
5203 g_free((gpointer) h->title);
5204 g_free((gpointer) h->uri);
5205 g_free(h);
5206 break;
5208 i++;
5210 break;
5211 case XT_XTP_HL_LIST:
5212 /* Nothing - just xtp_page_hl() below */
5213 break;
5214 default:
5215 show_oops(t, "%s: unknown command", __func__);
5216 break;
5219 xtp_page_hl(t, NULL);
5222 /* remove a favorite */
5223 void
5224 remove_favorite(struct tab *t, int index)
5226 char file[PATH_MAX], *title, *uri = NULL;
5227 char *new_favs, *tmp;
5228 FILE *f;
5229 int i;
5230 size_t len, lineno;
5232 /* open favorites */
5233 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5235 if ((f = fopen(file, "r")) == NULL) {
5236 show_oops(t, "%s: can't open favorites: %s",
5237 __func__, strerror(errno));
5238 return;
5241 /* build a string which will become the new favroites file */
5242 new_favs = g_strdup_printf("%s", "");
5244 for (i = 1;;) {
5245 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5246 if (feof(f) || ferror(f))
5247 break;
5248 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5249 if (len == 0) {
5250 free(title);
5251 title = NULL;
5252 continue;
5255 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5256 if (feof(f) || ferror(f)) {
5257 show_oops(t, "%s: can't parse favorites %s",
5258 __func__, strerror(errno));
5259 goto clean;
5263 /* as long as this isn't the one we are deleting add to file */
5264 if (i != index) {
5265 tmp = new_favs;
5266 new_favs = g_strdup_printf("%s%s\n%s\n",
5267 new_favs, title, uri);
5268 g_free(tmp);
5271 free(uri);
5272 uri = NULL;
5273 free(title);
5274 title = NULL;
5275 i++;
5277 fclose(f);
5279 /* write back new favorites file */
5280 if ((f = fopen(file, "w")) == NULL) {
5281 show_oops(t, "%s: can't open favorites: %s",
5282 __func__, strerror(errno));
5283 goto clean;
5286 fwrite(new_favs, strlen(new_favs), 1, f);
5287 fclose(f);
5289 clean:
5290 if (uri)
5291 free(uri);
5292 if (title)
5293 free(title);
5295 g_free(new_favs);
5298 void
5299 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5301 switch (cmd) {
5302 case XT_XTP_FL_LIST:
5303 /* nothing, just the below call to xtp_page_fl() */
5304 break;
5305 case XT_XTP_FL_REMOVE:
5306 remove_favorite(t, arg);
5307 break;
5308 default:
5309 show_oops(t, "%s: invalid favorites command", __func__);
5310 break;
5313 xtp_page_fl(t, NULL);
5316 void
5317 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5319 switch (cmd) {
5320 case XT_XTP_CL_LIST:
5321 /* nothing, just xtp_page_cl() */
5322 break;
5323 case XT_XTP_CL_REMOVE:
5324 remove_cookie(arg);
5325 break;
5326 default:
5327 show_oops(t, "%s: unknown cookie xtp command", __func__);
5328 break;
5331 xtp_page_cl(t, NULL);
5334 /* link an XTP class to it's session key and handler function */
5335 struct xtp_despatch {
5336 uint8_t xtp_class;
5337 char **session_key;
5338 void (*handle_func)(struct tab *, uint8_t, int);
5341 struct xtp_despatch xtp_despatches[] = {
5342 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5343 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5344 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5345 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5346 { NULL, NULL, NULL }
5350 * is the url xtp protocol? (xxxt://)
5351 * if so, parse and despatch correct bahvior
5354 parse_xtp_url(struct tab *t, const char *url)
5356 char *dup = NULL, *p, *last;
5357 uint8_t n_tokens = 0;
5358 char *tokens[4] = {NULL, NULL, NULL, ""};
5359 struct xtp_despatch *dsp, *dsp_match = NULL;
5360 uint8_t req_class;
5363 * tokens array meaning:
5364 * tokens[0] = class
5365 * tokens[1] = session key
5366 * tokens[2] = action
5367 * tokens[3] = optional argument
5370 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5372 /*xtp tab meaning is normal unless proven special */
5373 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5375 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5376 return 0;
5378 dup = g_strdup(url + strlen(XT_XTP_STR));
5380 /* split out the url */
5381 for ((p = strtok_r(dup, "/", &last)); p;
5382 (p = strtok_r(NULL, "/", &last))) {
5383 if (n_tokens < 4)
5384 tokens[n_tokens++] = p;
5387 /* should be atleast three fields 'class/seskey/command/arg' */
5388 if (n_tokens < 3)
5389 goto clean;
5391 dsp = xtp_despatches;
5392 req_class = atoi(tokens[0]);
5393 while (dsp->xtp_class != NULL) {
5394 if (dsp->xtp_class == req_class) {
5395 dsp_match = dsp;
5396 break;
5398 dsp++;
5401 /* did we find one atall? */
5402 if (dsp_match == NULL) {
5403 show_oops(t, "%s: no matching xtp despatch found", __func__);
5404 goto clean;
5407 /* check session key and call despatch function */
5408 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5409 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5412 clean:
5413 if (dup)
5414 g_free(dup);
5416 return 1;
5421 void
5422 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5424 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5426 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5428 if (t == NULL) {
5429 show_oops_s("activate_uri_entry_cb invalid parameters");
5430 return;
5433 if (uri == NULL) {
5434 show_oops(t, "activate_uri_entry_cb no uri");
5435 return;
5438 uri += strspn(uri, "\t ");
5440 /* if xxxt:// treat specially */
5441 if (!parse_xtp_url(t, uri)) {
5442 load_uri(t, (gchar *)uri);
5443 focus_webview(t);
5447 void
5448 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5450 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5451 char *newuri = NULL;
5452 gchar *enc_search;
5454 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5456 if (t == NULL) {
5457 show_oops_s("activate_search_entry_cb invalid parameters");
5458 return;
5461 if (search_string == NULL) {
5462 show_oops(t, "no search_string");
5463 return;
5466 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5467 newuri = g_strdup_printf(search_string, enc_search);
5468 g_free(enc_search);
5470 webkit_web_view_load_uri(t->wv, newuri);
5471 focus_webview(t);
5473 if (newuri)
5474 g_free(newuri);
5477 void
5478 check_and_set_js(const gchar *uri, struct tab *t)
5480 struct domain *d = NULL;
5481 int es = 0;
5483 if (uri == NULL || t == NULL)
5484 return;
5486 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5487 es = 0;
5488 else
5489 es = 1;
5491 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5492 es ? "enable" : "disable", uri);
5494 g_object_set(G_OBJECT(t->settings),
5495 "enable-scripts", es, (char *)NULL);
5496 g_object_set(G_OBJECT(t->settings),
5497 "javascript-can-open-windows-automatically", es, (char *)NULL);
5498 webkit_web_view_set_settings(t->wv, t->settings);
5500 button_set_stockid(t->js_toggle,
5501 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5504 void
5505 show_ca_status(struct tab *t, const char *uri)
5507 WebKitWebFrame *frame;
5508 WebKitWebDataSource *source;
5509 WebKitNetworkRequest *request;
5510 SoupMessage *message;
5511 GdkColor color;
5512 gchar *col_str = XT_COLOR_WHITE;
5513 int r;
5515 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5516 ssl_strict_certs, ssl_ca_file, uri);
5518 if (uri == NULL)
5519 goto done;
5520 if (ssl_ca_file == NULL) {
5521 if (g_str_has_prefix(uri, "http://"))
5522 goto done;
5523 if (g_str_has_prefix(uri, "https://")) {
5524 col_str = XT_COLOR_RED;
5525 goto done;
5527 return;
5529 if (g_str_has_prefix(uri, "http://") ||
5530 !g_str_has_prefix(uri, "https://"))
5531 goto done;
5533 frame = webkit_web_view_get_main_frame(t->wv);
5534 source = webkit_web_frame_get_data_source(frame);
5535 request = webkit_web_data_source_get_request(source);
5536 message = webkit_network_request_get_message(request);
5538 if (message && (soup_message_get_flags(message) &
5539 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5540 col_str = XT_COLOR_GREEN;
5541 goto done;
5542 } else {
5543 r = load_compare_cert(t, NULL);
5544 if (r == 0)
5545 col_str = XT_COLOR_BLUE;
5546 else if (r == 1)
5547 col_str = XT_COLOR_YELLOW;
5548 else
5549 col_str = XT_COLOR_RED;
5550 goto done;
5552 done:
5553 if (col_str) {
5554 gdk_color_parse(col_str, &color);
5555 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5557 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5558 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5559 &color);
5560 gdk_color_parse(XT_COLOR_BLACK, &color);
5561 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5562 &color);
5563 } else {
5564 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5565 &color);
5566 gdk_color_parse(XT_COLOR_BLACK, &color);
5567 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5568 &color);
5573 void
5574 free_favicon(struct tab *t)
5576 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5577 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5579 if (t->icon_request)
5580 g_object_unref(t->icon_request);
5581 if (t->icon_pixbuf)
5582 g_object_unref(t->icon_pixbuf);
5583 if (t->icon_dest_uri)
5584 g_free(t->icon_dest_uri);
5586 t->icon_pixbuf = NULL;
5587 t->icon_request = NULL;
5588 t->icon_dest_uri = NULL;
5591 void
5592 xt_icon_from_name(struct tab *t, gchar *name)
5594 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5595 GTK_ENTRY_ICON_PRIMARY, "text-html");
5596 if (show_url == 0)
5597 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5598 GTK_ENTRY_ICON_PRIMARY, "text-html");
5599 else
5600 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5601 GTK_ENTRY_ICON_PRIMARY, NULL);
5604 void
5605 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5607 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5608 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5609 if (show_url == 0)
5610 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5611 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5612 else
5613 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5614 GTK_ENTRY_ICON_PRIMARY, NULL);
5617 gboolean
5618 is_valid_icon(char *file)
5620 gboolean valid = 0;
5621 const char *mime_type;
5622 GFileInfo *fi;
5623 GFile *gf;
5625 gf = g_file_new_for_path(file);
5626 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5627 NULL, NULL);
5628 mime_type = g_file_info_get_content_type(fi);
5629 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5630 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5631 g_strcmp0(mime_type, "image/png") == 0 ||
5632 g_strcmp0(mime_type, "image/gif") == 0 ||
5633 g_strcmp0(mime_type, "application/octet-stream") == 0;
5634 g_object_unref(fi);
5635 g_object_unref(gf);
5637 return (valid);
5640 void
5641 set_favicon_from_file(struct tab *t, char *file)
5643 gint width, height;
5644 GdkPixbuf *pixbuf, *scaled;
5645 struct stat sb;
5647 if (t == NULL || file == NULL)
5648 return;
5649 if (t->icon_pixbuf) {
5650 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5651 return;
5654 if (g_str_has_prefix(file, "file://"))
5655 file += strlen("file://");
5656 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5658 if (!stat(file, &sb)) {
5659 if (sb.st_size == 0 || !is_valid_icon(file)) {
5660 /* corrupt icon so trash it */
5661 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5662 __func__, file);
5663 unlink(file);
5664 /* no need to set icon to default here */
5665 return;
5669 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5670 if (pixbuf == NULL) {
5671 xt_icon_from_name(t, "text-html");
5672 return;
5675 g_object_get(pixbuf, "width", &width, "height", &height,
5676 (char *)NULL);
5677 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5678 __func__, t->tab_id, width, height);
5680 if (width > 16 || height > 16) {
5681 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5682 GDK_INTERP_BILINEAR);
5683 g_object_unref(pixbuf);
5684 } else
5685 scaled = pixbuf;
5687 if (scaled == NULL) {
5688 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5689 GDK_INTERP_BILINEAR);
5690 return;
5693 t->icon_pixbuf = scaled;
5694 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5697 void
5698 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5699 WebKitWebView *wv)
5701 WebKitDownloadStatus status = webkit_download_get_status(download);
5702 struct tab *tt = NULL, *t = NULL;
5705 * find the webview instead of passing in the tab as it could have been
5706 * deleted from underneath us.
5708 TAILQ_FOREACH(tt, &tabs, entry) {
5709 if (tt->wv == wv) {
5710 t = tt;
5711 break;
5714 if (t == NULL)
5715 return;
5717 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5718 __func__, t->tab_id, status);
5720 switch (status) {
5721 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5722 /* -1 */
5723 t->icon_download = NULL;
5724 free_favicon(t);
5725 break;
5726 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5727 /* 0 */
5728 break;
5729 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5730 /* 1 */
5731 break;
5732 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5733 /* 2 */
5734 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5735 __func__, t->tab_id);
5736 t->icon_download = NULL;
5737 free_favicon(t);
5738 break;
5739 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5740 /* 3 */
5742 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5743 __func__, t->icon_dest_uri);
5744 set_favicon_from_file(t, t->icon_dest_uri);
5745 /* these will be freed post callback */
5746 t->icon_request = NULL;
5747 t->icon_download = NULL;
5748 break;
5749 default:
5750 break;
5754 void
5755 abort_favicon_download(struct tab *t)
5757 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5759 if (t->icon_download) {
5760 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5761 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5762 webkit_download_cancel(t->icon_download);
5763 t->icon_download = NULL;
5764 } else
5765 free_favicon(t);
5767 xt_icon_from_name(t, "text-html");
5770 void
5771 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5773 gchar *name_hash, file[PATH_MAX];
5774 struct stat sb;
5776 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5778 if (uri == NULL || t == NULL)
5779 return;
5781 if (t->icon_request) {
5782 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5783 return;
5786 /* check to see if we got the icon in cache */
5787 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5788 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5789 g_free(name_hash);
5791 if (!stat(file, &sb)) {
5792 if (sb.st_size > 0) {
5793 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5794 __func__, file);
5795 set_favicon_from_file(t, file);
5796 return;
5799 /* corrupt icon so trash it */
5800 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5801 __func__, file);
5802 unlink(file);
5805 /* create download for icon */
5806 t->icon_request = webkit_network_request_new(uri);
5807 if (t->icon_request == NULL) {
5808 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5809 __func__, uri);
5810 return;
5813 t->icon_download = webkit_download_new(t->icon_request);
5814 if (t->icon_download == NULL) {
5815 fprintf(stderr, "%s: icon_download", __func__);
5816 return;
5819 /* we have to free icon_dest_uri later */
5820 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5821 webkit_download_set_destination_uri(t->icon_download,
5822 t->icon_dest_uri);
5824 if (webkit_download_get_status(t->icon_download) ==
5825 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5826 fprintf(stderr, "%s: download failed to start", __func__);
5827 g_object_unref(t->icon_request);
5828 g_free(t->icon_dest_uri);
5829 t->icon_request = NULL;
5830 t->icon_dest_uri = NULL;
5831 return;
5834 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5835 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5837 webkit_download_start(t->icon_download);
5840 void
5841 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5843 const gchar *set = NULL, *uri = NULL, *title = NULL;
5844 struct history *h, find;
5845 const gchar *s_loading;
5846 struct karg a;
5848 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5849 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5851 if (t == NULL) {
5852 show_oops_s("notify_load_status_cb invalid paramters");
5853 return;
5856 switch (webkit_web_view_get_load_status(wview)) {
5857 case WEBKIT_LOAD_PROVISIONAL:
5858 /* 0 */
5859 abort_favicon_download(t);
5860 #if GTK_CHECK_VERSION(2, 20, 0)
5861 gtk_widget_show(t->spinner);
5862 gtk_spinner_start(GTK_SPINNER(t->spinner));
5863 #endif
5864 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5866 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5868 t->focus_wv = 1;
5870 break;
5872 case WEBKIT_LOAD_COMMITTED:
5873 /* 1 */
5874 if ((uri = get_uri(wview)) != NULL) {
5875 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5876 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5878 if (t->status) {
5879 g_free(t->status);
5880 t->status = NULL;
5882 set_status(t, (char *)uri, XT_STATUS_LOADING);
5885 /* check if js white listing is enabled */
5886 if (enable_js_whitelist) {
5887 uri = get_uri(wview);
5888 check_and_set_js(uri, t);
5891 if (t->styled)
5892 apply_style(t);
5894 show_ca_status(t, uri);
5896 /* we know enough to autosave the session */
5897 if (session_autosave) {
5898 a.s = NULL;
5899 save_tabs(t, &a);
5901 break;
5903 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5904 /* 3 */
5905 break;
5907 case WEBKIT_LOAD_FINISHED:
5908 /* 2 */
5909 uri = get_uri(wview);
5910 if (uri == NULL)
5911 return;
5913 if (!strncmp(uri, "http://", strlen("http://")) ||
5914 !strncmp(uri, "https://", strlen("https://")) ||
5915 !strncmp(uri, "file://", strlen("file://"))) {
5916 find.uri = uri;
5917 h = RB_FIND(history_list, &hl, &find);
5918 if (!h) {
5919 title = webkit_web_view_get_title(wview);
5920 set = title ? title: uri;
5921 h = g_malloc(sizeof *h);
5922 h->uri = g_strdup(uri);
5923 h->title = g_strdup(set);
5924 RB_INSERT(history_list, &hl, h);
5925 completion_add_uri(h->uri);
5926 update_history_tabs(NULL);
5930 set_status(t, (char *)uri, XT_STATUS_URI);
5931 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5932 case WEBKIT_LOAD_FAILED:
5933 /* 4 */
5934 #endif
5935 #if GTK_CHECK_VERSION(2, 20, 0)
5936 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5937 gtk_widget_hide(t->spinner);
5938 #endif
5939 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5940 if (s_loading && !strcmp(s_loading, "Loading"))
5941 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5942 default:
5943 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5944 break;
5947 if (t->item)
5948 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5949 else
5950 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5951 webkit_web_view_can_go_back(wview));
5953 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5954 webkit_web_view_can_go_forward(wview));
5956 /* take focus if we are visible */
5957 focus_webview(t);
5960 void
5961 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5963 const gchar *set = NULL, *title = NULL;
5965 title = webkit_web_view_get_title(wview);
5966 set = title ? title: get_uri(wview);
5967 gtk_label_set_text(GTK_LABEL(t->label), set);
5968 gtk_window_set_title(GTK_WINDOW(main_window), set);
5971 void
5972 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5974 run_script(t, JS_HINTING);
5977 void
5978 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5980 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5981 progress == 100 ? 0 : (double)progress / 100);
5982 if (show_url == 0) {
5983 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5984 progress == 100 ? 0 : (double)progress / 100);
5989 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5990 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5991 WebKitWebPolicyDecision *pd, struct tab *t)
5993 char *uri;
5994 WebKitWebNavigationReason reason;
5995 struct domain *d = NULL;
5997 if (t == NULL) {
5998 show_oops_s("webview_npd_cb invalid parameters");
5999 return (FALSE);
6002 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6003 t->ctrl_click,
6004 webkit_network_request_get_uri(request));
6006 uri = (char *)webkit_network_request_get_uri(request);
6008 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
6009 t->ctrl_click = 0;
6010 create_new_tab(uri, NULL, ctrl_click_focus);
6011 webkit_web_policy_decision_ignore(pd);
6012 return (TRUE); /* we made the decission */
6016 * This is a little hairy but it comes down to this:
6017 * when we run in whitelist mode we have to assist the browser in
6018 * opening the URL that it would have opened in a new tab.
6020 reason = webkit_web_navigation_action_get_reason(na);
6021 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6022 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6023 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6024 load_uri(t, uri);
6026 webkit_web_policy_decision_use(pd);
6027 return (TRUE); /* we made the decission */
6030 return (FALSE);
6033 WebKitWebView *
6034 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6036 struct tab *tt;
6037 struct domain *d = NULL;
6038 const gchar *uri;
6039 WebKitWebView *webview = NULL;
6041 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6042 webkit_web_view_get_uri(wv));
6044 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6045 uri = webkit_web_view_get_uri(wv);
6046 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6047 return (NULL);
6049 tt = create_new_tab(NULL, NULL, 1);
6050 webview = tt->wv;
6051 } else if (enable_scripts == 1) {
6052 tt = create_new_tab(NULL, NULL, 1);
6053 webview = tt->wv;
6056 return (webview);
6059 gboolean
6060 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6062 const gchar *uri;
6063 struct domain *d = NULL;
6065 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6067 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6068 uri = webkit_web_view_get_uri(wv);
6069 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6070 return (FALSE);
6072 delete_tab(t);
6073 } else if (enable_scripts == 1)
6074 delete_tab(t);
6076 return (TRUE);
6080 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6082 /* we can not eat the event without throwing gtk off so defer it */
6084 /* catch middle click */
6085 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6086 t->ctrl_click = 1;
6087 goto done;
6090 /* catch ctrl click */
6091 if (e->type == GDK_BUTTON_RELEASE &&
6092 CLEAN(e->state) == GDK_CONTROL_MASK)
6093 t->ctrl_click = 1;
6094 else
6095 t->ctrl_click = 0;
6096 done:
6097 return (XT_CB_PASSTHROUGH);
6101 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6103 struct mime_type *m;
6105 m = find_mime_type(mime_type);
6106 if (m == NULL)
6107 return (1);
6108 if (m->mt_download)
6109 return (1);
6111 switch (fork()) {
6112 case -1:
6113 show_oops(t, "can't fork mime handler");
6114 /* NOTREACHED */
6115 case 0:
6116 break;
6117 default:
6118 return (0);
6121 /* child */
6122 execlp(m->mt_action, m->mt_action,
6123 webkit_network_request_get_uri(request), (void *)NULL);
6125 _exit(0);
6127 /* NOTREACHED */
6128 return (0);
6131 const gchar *
6132 get_mime_type(char *file)
6134 const char *mime_type;
6135 GFileInfo *fi;
6136 GFile *gf;
6138 if (g_str_has_prefix(file, "file://"))
6139 file += strlen("file://");
6141 gf = g_file_new_for_path(file);
6142 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6143 NULL, NULL);
6144 mime_type = g_file_info_get_content_type(fi);
6145 g_object_unref(fi);
6146 g_object_unref(gf);
6148 return (mime_type);
6152 run_download_mimehandler(char *mime_type, char *file)
6154 struct mime_type *m;
6156 m = find_mime_type(mime_type);
6157 if (m == NULL)
6158 return (1);
6160 switch (fork()) {
6161 case -1:
6162 show_oops_s("can't fork download mime handler");
6163 /* NOTREACHED */
6164 case 0:
6165 break;
6166 default:
6167 return (0);
6170 /* child */
6171 if (g_str_has_prefix(file, "file://"))
6172 file += strlen("file://");
6173 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6175 _exit(0);
6177 /* NOTREACHED */
6178 return (0);
6181 void
6182 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6183 WebKitWebView *wv)
6185 WebKitDownloadStatus status;
6186 const gchar *file = NULL, *mime = NULL;
6188 if (download == NULL)
6189 return;
6190 status = webkit_download_get_status(download);
6191 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6192 return;
6194 file = webkit_download_get_destination_uri(download);
6195 if (file == NULL)
6196 return;
6197 mime = get_mime_type((char *)file);
6198 if (mime == NULL)
6199 return;
6201 run_download_mimehandler((char *)mime, (char *)file);
6205 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6206 WebKitNetworkRequest *request, char *mime_type,
6207 WebKitWebPolicyDecision *decision, struct tab *t)
6209 if (t == NULL) {
6210 show_oops_s("webview_mimetype_cb invalid parameters");
6211 return (FALSE);
6214 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6215 t->tab_id, mime_type);
6217 if (run_mimehandler(t, mime_type, request) == 0) {
6218 webkit_web_policy_decision_ignore(decision);
6219 focus_webview(t);
6220 return (TRUE);
6223 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6224 webkit_web_policy_decision_download(decision);
6225 return (TRUE);
6228 return (FALSE);
6232 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6233 struct tab *t)
6235 const gchar *filename;
6236 char *uri = NULL;
6237 struct download *download_entry;
6238 int ret = TRUE;
6240 if (wk_download == NULL || t == NULL) {
6241 show_oops_s("%s invalid parameters", __func__);
6242 return (FALSE);
6245 filename = webkit_download_get_suggested_filename(wk_download);
6246 if (filename == NULL)
6247 return (FALSE); /* abort download */
6249 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6251 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6252 "local %s\n", __func__, t->tab_id, filename, uri);
6254 webkit_download_set_destination_uri(wk_download, uri);
6256 if (webkit_download_get_status(wk_download) ==
6257 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6258 show_oops(t, "%s: download failed to start", __func__);
6259 ret = FALSE;
6260 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6261 } else {
6262 /* connect "download first" mime handler */
6263 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6264 G_CALLBACK(download_status_changed_cb), NULL);
6266 download_entry = g_malloc(sizeof(struct download));
6267 download_entry->download = wk_download;
6268 download_entry->tab = t;
6269 download_entry->id = next_download_id++;
6270 RB_INSERT(download_list, &downloads, download_entry);
6271 /* get from history */
6272 g_object_ref(wk_download);
6273 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6274 show_oops(t, "Download of '%s' started...",
6275 basename(webkit_download_get_destination_uri(wk_download)));
6278 if (uri)
6279 g_free(uri);
6281 /* sync other download manager tabs */
6282 update_download_tabs(NULL);
6285 * NOTE: never redirect/render the current tab before this
6286 * function returns. This will cause the download to never start.
6288 return (ret); /* start download */
6291 void
6292 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6294 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6296 if (t == NULL) {
6297 show_oops_s("webview_hover_cb");
6298 return;
6301 if (uri)
6302 set_status(t, uri, XT_STATUS_LINK);
6303 else {
6304 if (t->status)
6305 set_status(t, t->status, XT_STATUS_NOTHING);
6309 gboolean
6310 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6312 struct key_binding *k;
6314 TAILQ_FOREACH(k, &kbl, entry)
6315 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6316 if (k->mask == 0) {
6317 if ((e->state & (CTRL | MOD1)) == 0)
6318 return (cmd_execute(t, k->cmd));
6319 } else if ((e->state & k->mask) == k->mask) {
6320 return (cmd_execute(t, k->cmd));
6324 return (XT_CB_PASSTHROUGH);
6328 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6330 char s[2], buf[128];
6331 const char *errstr = NULL;
6332 long long link;
6334 /* don't use w directly; use t->whatever instead */
6336 if (t == NULL) {
6337 show_oops_s("wv_keypress_after_cb");
6338 return (XT_CB_PASSTHROUGH);
6341 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6342 e->keyval, e->state, t);
6344 if (t->hints_on) {
6345 /* ESC */
6346 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6347 disable_hints(t);
6348 return (XT_CB_HANDLED);
6351 /* RETURN */
6352 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6353 link = strtonum(t->hint_num, 1, 1000, &errstr);
6354 if (errstr) {
6355 /* we have a string */
6356 } else {
6357 /* we have a number */
6358 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6359 t->hint_num);
6360 run_script(t, buf);
6362 disable_hints(t);
6365 /* BACKSPACE */
6366 /* XXX unfuck this */
6367 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6368 if (t->hint_mode == XT_HINT_NUMERICAL) {
6369 /* last input was numerical */
6370 int l;
6371 l = strlen(t->hint_num);
6372 if (l > 0) {
6373 l--;
6374 if (l == 0) {
6375 disable_hints(t);
6376 enable_hints(t);
6377 } else {
6378 t->hint_num[l] = '\0';
6379 goto num;
6382 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6383 /* last input was alphanumerical */
6384 int l;
6385 l = strlen(t->hint_buf);
6386 if (l > 0) {
6387 l--;
6388 if (l == 0) {
6389 disable_hints(t);
6390 enable_hints(t);
6391 } else {
6392 t->hint_buf[l] = '\0';
6393 goto anum;
6396 } else {
6397 /* bogus */
6398 disable_hints(t);
6402 /* numerical input */
6403 if (CLEAN(e->state) == 0 &&
6404 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6405 snprintf(s, sizeof s, "%c", e->keyval);
6406 strlcat(t->hint_num, s, sizeof t->hint_num);
6407 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6408 t->hint_num);
6409 num:
6410 link = strtonum(t->hint_num, 1, 1000, &errstr);
6411 if (errstr) {
6412 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6413 disable_hints(t);
6414 } else {
6415 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6416 t->hint_num);
6417 t->hint_mode = XT_HINT_NUMERICAL;
6418 run_script(t, buf);
6421 /* empty the counter buffer */
6422 bzero(t->hint_buf, sizeof t->hint_buf);
6423 return (XT_CB_HANDLED);
6426 /* alphanumerical input */
6427 if (
6428 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6429 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6430 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6431 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6432 snprintf(s, sizeof s, "%c", e->keyval);
6433 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6434 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6435 t->hint_buf);
6436 anum:
6437 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6438 run_script(t, buf);
6440 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6441 t->hint_buf);
6442 t->hint_mode = XT_HINT_ALPHANUM;
6443 run_script(t, buf);
6445 /* empty the counter buffer */
6446 bzero(t->hint_num, sizeof t->hint_num);
6447 return (XT_CB_HANDLED);
6450 return (XT_CB_HANDLED);
6453 return (handle_keypress(t, e, 0));
6457 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6459 hide_oops(t);
6461 return (XT_CB_PASSTHROUGH);
6465 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6467 const gchar *c = gtk_entry_get_text(w);
6468 GdkColor color;
6469 int forward = TRUE;
6471 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6472 e->keyval, e->state, t);
6474 if (t == NULL) {
6475 show_oops_s("cmd_keyrelease_cb invalid parameters");
6476 return (XT_CB_PASSTHROUGH);
6479 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6480 e->keyval, e->state, t);
6482 if (c[0] == ':')
6483 goto done;
6484 if (strlen(c) == 1) {
6485 webkit_web_view_unmark_text_matches(t->wv);
6486 goto done;
6489 if (c[0] == '/')
6490 forward = TRUE;
6491 else if (c[0] == '?')
6492 forward = FALSE;
6493 else
6494 goto done;
6496 /* search */
6497 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6498 FALSE) {
6499 /* not found, mark red */
6500 gdk_color_parse(XT_COLOR_RED, &color);
6501 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6502 /* unmark and remove selection */
6503 webkit_web_view_unmark_text_matches(t->wv);
6504 /* my kingdom for a way to unselect text in webview */
6505 } else {
6506 /* found, highlight all */
6507 webkit_web_view_unmark_text_matches(t->wv);
6508 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6509 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6510 gdk_color_parse(XT_COLOR_WHITE, &color);
6511 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6513 done:
6514 return (XT_CB_PASSTHROUGH);
6517 gboolean
6518 match_uri(const gchar *uri, const gchar *key) {
6519 gchar *voffset;
6520 size_t len;
6521 gboolean match = FALSE;
6523 len = strlen(key);
6525 if (!strncmp(key, uri, len))
6526 match = TRUE;
6527 else {
6528 voffset = strstr(uri, "/") + 2;
6529 if (!strncmp(key, voffset, len))
6530 match = TRUE;
6531 else if (g_str_has_prefix(voffset, "www.")) {
6532 voffset = voffset + strlen("www.");
6533 if (!strncmp(key, voffset, len))
6534 match = TRUE;
6538 return (match);
6541 void
6542 cmd_getlist(int id, char *key)
6544 int i, dep, c = 0;
6545 struct history *h;
6547 if (id >= 0 && (cmds[id].userarg && (cmds[id].arg.i == XT_TAB_OPEN || cmds[id].arg.i == XT_TAB_NEW))) {
6548 RB_FOREACH_REVERSE(h, history_list, &hl)
6549 if (match_uri(h->uri, key)) {
6550 cmd_status.list[c] = (char *)h->uri;
6551 if (++c > 255)
6552 break;
6555 cmd_status.len = c;
6556 return;
6559 dep = (id == -1) ? 0 : cmds[id].level + 1;
6561 for (i = id + 1; i < LENGTH(cmds); i++) {
6562 if(cmds[i].level < dep)
6563 break;
6564 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6565 cmd_status.list[c++] = cmds[i].cmd;
6569 cmd_status.len = c;
6572 char *
6573 cmd_getnext(int dir)
6575 cmd_status.index += dir;
6577 if (cmd_status.index<0)
6578 cmd_status.index = cmd_status.len-1;
6579 else if (cmd_status.index >= cmd_status.len)
6580 cmd_status.index = 0;
6582 return cmd_status.list[cmd_status.index];
6586 cmd_tokenize(char *s, char *tokens[])
6588 int i = 0;
6589 char *tok, *last;
6590 size_t len = strlen(s);
6591 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6593 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6594 tokens[i] = tok;
6596 if (blank && i < 3)
6597 tokens[i++] = "";
6599 return (i);
6602 void
6603 cmd_complete(struct tab *t, char *str, int dir)
6605 GtkEntry *w = GTK_ENTRY(t->cmd);
6606 int i, j, levels, c = 0, dep = 0, parent = -1;
6607 char *tok, *match, *s = strdup(str);
6608 char *tokens[3];
6609 char res[XT_MAX_URL_LENGTH + 32] = ":";
6611 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", str);
6613 levels = cmd_tokenize(s, tokens);
6615 for (i = 0; i < levels - 1; i++) {
6616 tok = tokens[i];
6617 for (j = c; j < LENGTH(cmds); j++)
6618 if (cmds[j].level == dep && !strcmp(tok, cmds[j].cmd)) {
6619 strlcat(res, tok, sizeof res);
6620 strlcat(res, " ", sizeof res);
6621 dep++;
6622 c = j;
6623 break;
6626 parent = c;
6629 if (cmd_status.index == -1)
6630 cmd_getlist(parent, tokens[i]);
6632 if (cmd_status.len > 0) {
6633 match = cmd_getnext(dir);
6634 strlcat(res, match, sizeof res);
6635 gtk_entry_set_text(w, res);
6636 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6640 gboolean
6641 cmd_valid(char *str)
6643 char *tok, *last, *s = g_strdup(str);
6644 int i, c = 0, dep = 0;
6646 for (tok = strtok_r(s, " ", &last); tok;
6647 tok = strtok_r(NULL, " ", &last)) {
6648 for (i = c; i < LENGTH(cmds); i++) {
6649 if (cmds[i].level < dep) {
6650 return (XT_CB_PASSTHROUGH);
6652 if (cmds[i].level == dep && !strcmp(tok, cmds[i].cmd)) {
6653 if (cmds[i].userarg) {
6654 return (XT_CB_HANDLED);
6656 c = i + 1;
6657 dep++;
6658 break;
6661 if (i == LENGTH(cmds)) {
6662 return (XT_CB_PASSTHROUGH);
6665 return (XT_CB_HANDLED);
6668 gboolean
6669 cmd_execute(struct tab *t, char *str)
6671 struct cmd *cmd = NULL;
6672 char *tok, *last, *s = g_strdup(str);
6673 int j, c = 0, dep = 0;
6675 for (tok = strtok_r(s, " ", &last); tok;
6676 tok = strtok_r(NULL, " ", &last)) {
6677 for (j = c; j < LENGTH(cmds); j++) {
6678 if (cmds[j].level < dep) {
6679 show_oops(t, "Invalid command: %s", str);
6680 return (XT_CB_PASSTHROUGH);
6682 if (cmds[j].level == dep && !strcmp(tok, cmds[j].cmd)) {
6683 cmd = &cmds[j];
6684 if (cmd->userarg) {
6685 cmd->arg.s = last ? g_strdup(last) : g_strdup("");
6686 goto execute_cmd;
6688 c = j + 1;
6689 dep++;
6690 break;
6693 if (j == LENGTH(cmds)) {
6694 show_oops(t, "Invalid command: %s", str);
6695 return (XT_CB_PASSTHROUGH);
6698 cmd->arg.s = g_strdup(tok);
6699 execute_cmd:
6700 /* arg->s contains last token */
6701 cmd->func(t, &cmd->arg);
6702 if (cmd->arg.s)
6703 g_free(cmd->arg.s);
6705 return (XT_CB_HANDLED);
6709 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6711 if (t == NULL) {
6712 show_oops_s("entry_key_cb invalid parameters");
6713 return (XT_CB_PASSTHROUGH);
6716 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6717 e->keyval, e->state, t);
6719 hide_oops(t);
6721 if (e->keyval == GDK_Escape) {
6722 /* don't use focus_webview(t) because we want to type :cmds */
6723 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6726 return (handle_keypress(t, e, 1));
6730 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6732 int rv = XT_CB_HANDLED;
6733 const gchar *c = gtk_entry_get_text(w);
6735 if (t == NULL) {
6736 show_oops_s("cmd_keypress_cb parameters");
6737 return (XT_CB_PASSTHROUGH);
6740 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6741 e->keyval, e->state, t);
6743 /* sanity */
6744 if (c == NULL)
6745 e->keyval = GDK_Escape;
6746 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6747 e->keyval = GDK_Escape;
6749 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6750 cmd_status.index = -1;
6752 switch (e->keyval) {
6753 case GDK_Tab:
6754 if (c[0] == ':')
6755 cmd_complete(t, (char *)&c[1], 1);
6756 goto done;
6757 case GDK_ISO_Left_Tab:
6758 if (c[0] == ':')
6759 cmd_complete(t, (char *)&c[1], -1);
6761 goto done;
6762 case GDK_BackSpace:
6763 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6764 break;
6765 /* FALLTHROUGH */
6766 case GDK_Escape:
6767 hide_cmd(t);
6768 focus_webview(t);
6770 /* cancel search */
6771 if (c[0] == '/' || c[0] == '?')
6772 webkit_web_view_unmark_text_matches(t->wv);
6773 goto done;
6776 rv = XT_CB_PASSTHROUGH;
6777 done:
6778 return (rv);
6782 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6784 if (t == NULL) {
6785 show_oops_s("cmd_focusout_cb invalid parameters");
6786 return (XT_CB_PASSTHROUGH);
6788 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6790 hide_cmd(t);
6791 hide_oops(t);
6793 if (show_url == 0 || t->focus_wv)
6794 focus_webview(t);
6795 else
6796 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6798 return (XT_CB_PASSTHROUGH);
6801 void
6802 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6804 char *s;
6805 const gchar *c = gtk_entry_get_text(entry);
6807 if (t == NULL) {
6808 show_oops_s("cmd_activate_cb invalid parameters");
6809 return;
6812 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6814 /* sanity */
6815 if (c == NULL)
6816 goto done;
6817 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6818 goto done;
6819 if (strlen(c) < 2)
6820 goto done;
6821 s = (char *)&c[1];
6823 if (c[0] == '/' || c[0] == '?') {
6824 if (t->search_text) {
6825 g_free(t->search_text);
6826 t->search_text = NULL;
6829 t->search_text = g_strdup(s);
6830 if (global_search)
6831 g_free(global_search);
6832 global_search = g_strdup(s);
6833 t->search_forward = c[0] == '/';
6835 goto done;
6838 cmd_execute(t, s);
6840 done:
6841 hide_cmd(t);
6844 void
6845 backward_cb(GtkWidget *w, struct tab *t)
6847 struct karg a;
6849 if (t == NULL) {
6850 show_oops_s("backward_cb invalid parameters");
6851 return;
6854 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6856 a.i = XT_NAV_BACK;
6857 navaction(t, &a);
6860 void
6861 forward_cb(GtkWidget *w, struct tab *t)
6863 struct karg a;
6865 if (t == NULL) {
6866 show_oops_s("forward_cb invalid parameters");
6867 return;
6870 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6872 a.i = XT_NAV_FORWARD;
6873 navaction(t, &a);
6876 void
6877 home_cb(GtkWidget *w, struct tab *t)
6879 if (t == NULL) {
6880 show_oops_s("home_cb invalid parameters");
6881 return;
6884 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6886 load_uri(t, home);
6889 void
6890 stop_cb(GtkWidget *w, struct tab *t)
6892 WebKitWebFrame *frame;
6894 if (t == NULL) {
6895 show_oops_s("stop_cb invalid parameters");
6896 return;
6899 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6901 frame = webkit_web_view_get_main_frame(t->wv);
6902 if (frame == NULL) {
6903 show_oops(t, "stop_cb: no frame");
6904 return;
6907 webkit_web_frame_stop_loading(frame);
6908 abort_favicon_download(t);
6911 void
6912 setup_webkit(struct tab *t)
6914 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6915 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6916 FALSE, (char *)NULL);
6917 else
6918 warnx("webkit does not have \"enable-dns-prefetching\" property");
6919 g_object_set(G_OBJECT(t->settings),
6920 "user-agent", t->user_agent, (char *)NULL);
6921 g_object_set(G_OBJECT(t->settings),
6922 "enable-scripts", enable_scripts, (char *)NULL);
6923 g_object_set(G_OBJECT(t->settings),
6924 "enable-plugins", enable_plugins, (char *)NULL);
6925 g_object_set(G_OBJECT(t->settings),
6926 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6927 g_object_set(G_OBJECT(t->wv),
6928 "full-content-zoom", TRUE, (char *)NULL);
6929 adjustfont_webkit(t, XT_FONT_SET);
6931 webkit_web_view_set_settings(t->wv, t->settings);
6934 GtkWidget *
6935 create_browser(struct tab *t)
6937 GtkWidget *w;
6938 gchar *strval;
6940 if (t == NULL) {
6941 show_oops_s("create_browser invalid parameters");
6942 return (NULL);
6945 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6946 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6947 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6948 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6950 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6951 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6952 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6954 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6955 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6957 /* set defaults */
6958 t->settings = webkit_web_settings_new();
6960 if (user_agent == NULL) {
6961 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6962 (char *)NULL);
6963 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6964 g_free(strval);
6965 } else
6966 t->user_agent = g_strdup(user_agent);
6968 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
6970 setup_webkit(t);
6972 return (w);
6975 GtkWidget *
6976 create_window(void)
6978 GtkWidget *w;
6980 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6981 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6982 gtk_widget_set_name(w, "xxxterm");
6983 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6984 g_signal_connect(G_OBJECT(w), "delete_event",
6985 G_CALLBACK (gtk_main_quit), NULL);
6987 return (w);
6990 GtkWidget *
6991 create_kiosk_toolbar(struct tab *t)
6993 GtkWidget *toolbar = NULL, *b;
6995 b = gtk_hbox_new(FALSE, 0);
6996 toolbar = b;
6997 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6999 /* backward button */
7000 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7001 gtk_widget_set_sensitive(t->backward, FALSE);
7002 g_signal_connect(G_OBJECT(t->backward), "clicked",
7003 G_CALLBACK(backward_cb), t);
7004 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7006 /* forward button */
7007 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7008 gtk_widget_set_sensitive(t->forward, FALSE);
7009 g_signal_connect(G_OBJECT(t->forward), "clicked",
7010 G_CALLBACK(forward_cb), t);
7011 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7013 /* home button */
7014 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7015 gtk_widget_set_sensitive(t->gohome, true);
7016 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7017 G_CALLBACK(home_cb), t);
7018 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7020 /* create widgets but don't use them */
7021 t->uri_entry = gtk_entry_new();
7022 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7023 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7024 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7026 return (toolbar);
7029 GtkWidget *
7030 create_toolbar(struct tab *t)
7032 GtkWidget *toolbar = NULL, *b, *eb1;
7034 b = gtk_hbox_new(FALSE, 0);
7035 toolbar = b;
7036 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7038 if (fancy_bar) {
7039 /* backward button */
7040 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7041 gtk_widget_set_sensitive(t->backward, FALSE);
7042 g_signal_connect(G_OBJECT(t->backward), "clicked",
7043 G_CALLBACK(backward_cb), t);
7044 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7046 /* forward button */
7047 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7048 gtk_widget_set_sensitive(t->forward, FALSE);
7049 g_signal_connect(G_OBJECT(t->forward), "clicked",
7050 G_CALLBACK(forward_cb), t);
7051 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7052 FALSE, 0);
7054 /* stop button */
7055 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7056 gtk_widget_set_sensitive(t->stop, FALSE);
7057 g_signal_connect(G_OBJECT(t->stop), "clicked",
7058 G_CALLBACK(stop_cb), t);
7059 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7060 FALSE, 0);
7062 /* JS button */
7063 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7064 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7065 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7066 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7067 G_CALLBACK(js_toggle_cb), t);
7068 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7071 t->uri_entry = gtk_entry_new();
7072 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7073 G_CALLBACK(activate_uri_entry_cb), t);
7074 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7075 G_CALLBACK(entry_key_cb), t);
7076 completion_add(t);
7077 eb1 = gtk_hbox_new(FALSE, 0);
7078 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7079 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7080 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7082 /* search entry */
7083 if (fancy_bar && search_string) {
7084 GtkWidget *eb2;
7085 t->search_entry = gtk_entry_new();
7086 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7087 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7088 G_CALLBACK(activate_search_entry_cb), t);
7089 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7090 G_CALLBACK(entry_key_cb), t);
7091 gtk_widget_set_size_request(t->search_entry, -1, -1);
7092 eb2 = gtk_hbox_new(FALSE, 0);
7093 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7094 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7096 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7098 return (toolbar);
7101 void
7102 recalc_tabs(void)
7104 struct tab *t;
7106 TAILQ_FOREACH(t, &tabs, entry)
7107 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7111 undo_close_tab_save(struct tab *t)
7113 int m, n;
7114 const gchar *uri;
7115 struct undo *u1, *u2;
7116 GList *items;
7117 WebKitWebHistoryItem *item;
7119 if ((uri = get_uri(t->wv)) == NULL)
7120 return (1);
7122 u1 = g_malloc0(sizeof(struct undo));
7123 u1->uri = g_strdup(uri);
7125 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7127 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7128 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7129 u1->back = n;
7131 /* forward history */
7132 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7134 while (items) {
7135 item = items->data;
7136 u1->history = g_list_prepend(u1->history,
7137 webkit_web_history_item_copy(item));
7138 items = g_list_next(items);
7141 /* current item */
7142 if (m) {
7143 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7144 u1->history = g_list_prepend(u1->history,
7145 webkit_web_history_item_copy(item));
7148 /* back history */
7149 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7151 while (items) {
7152 item = items->data;
7153 u1->history = g_list_prepend(u1->history,
7154 webkit_web_history_item_copy(item));
7155 items = g_list_next(items);
7158 TAILQ_INSERT_HEAD(&undos, u1, entry);
7160 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7161 u2 = TAILQ_LAST(&undos, undo_tailq);
7162 TAILQ_REMOVE(&undos, u2, entry);
7163 g_free(u2->uri);
7164 g_list_free(u2->history);
7165 g_free(u2);
7166 } else
7167 undo_count++;
7169 return (0);
7172 void
7173 delete_tab(struct tab *t)
7175 struct karg a;
7177 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7179 if (t == NULL)
7180 return;
7182 TAILQ_REMOVE(&tabs, t, entry);
7184 /* halt all webkit activity */
7185 abort_favicon_download(t);
7186 webkit_web_view_stop_loading(t->wv);
7187 undo_close_tab_save(t);
7189 if (browser_mode == XT_BM_KIOSK) {
7190 gtk_widget_destroy(t->uri_entry);
7191 gtk_widget_destroy(t->stop);
7192 gtk_widget_destroy(t->js_toggle);
7196 gtk_widget_destroy(t->vbox);
7197 g_free(t->user_agent);
7198 g_free(t->stylesheet);
7199 g_free(t);
7201 recalc_tabs();
7202 if (TAILQ_EMPTY(&tabs)) {
7203 if (browser_mode == XT_BM_KIOSK)
7204 create_new_tab(home, NULL, 1);
7205 else
7206 create_new_tab(NULL, NULL, 1);
7209 /* recreate session */
7210 if (session_autosave) {
7211 a.s = NULL;
7212 save_tabs(t, &a);
7216 void
7217 adjustfont_webkit(struct tab *t, int adjust)
7219 gfloat zoom;
7221 if (t == NULL) {
7222 show_oops_s("adjustfont_webkit invalid parameters");
7223 return;
7226 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7227 if (adjust == XT_FONT_SET) {
7228 t->font_size = default_font_size;
7229 zoom = default_zoom_level;
7230 t->font_size += adjust;
7231 g_object_set(G_OBJECT(t->settings), "default-font-size",
7232 t->font_size, (char *)NULL);
7233 g_object_get(G_OBJECT(t->settings), "default-font-size",
7234 &t->font_size, (char *)NULL);
7235 } else {
7236 t->font_size += adjust;
7237 zoom += adjust/25.0;
7238 if (zoom < 0.0) {
7239 zoom = 0.04;
7242 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7243 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7246 void
7247 append_tab(struct tab *t)
7249 if (t == NULL)
7250 return;
7252 TAILQ_INSERT_TAIL(&tabs, t, entry);
7253 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7256 struct tab *
7257 create_new_tab(char *title, struct undo *u, int focus)
7259 struct tab *t, *tt;
7260 int load = 1, id, notfound;
7261 GtkWidget *b, *bb;
7262 WebKitWebHistoryItem *item;
7263 GList *items;
7264 GdkColor color;
7266 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7268 if (tabless && !TAILQ_EMPTY(&tabs)) {
7269 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7270 return (NULL);
7273 t = g_malloc0(sizeof *t);
7275 if (title == NULL) {
7276 title = "(untitled)";
7277 load = 0;
7280 t->vbox = gtk_vbox_new(FALSE, 0);
7282 /* label + button for tab */
7283 b = gtk_hbox_new(FALSE, 0);
7284 t->tab_content = b;
7286 #if GTK_CHECK_VERSION(2, 20, 0)
7287 t->spinner = gtk_spinner_new ();
7288 #endif
7289 t->label = gtk_label_new(title);
7290 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7291 gtk_widget_set_size_request(t->label, 100, 0);
7292 gtk_widget_set_size_request(b, 130, 0);
7294 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7295 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7296 #if GTK_CHECK_VERSION(2, 20, 0)
7297 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7298 #endif
7300 /* toolbar */
7301 if (browser_mode == XT_BM_KIOSK)
7302 t->toolbar = create_kiosk_toolbar(t);
7303 else
7304 t->toolbar = create_toolbar(t);
7306 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7308 /* browser */
7309 t->browser_win = create_browser(t);
7310 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7312 /* oops message for user feedback */
7313 t->oops = gtk_entry_new();
7314 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7315 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7316 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7317 gdk_color_parse(XT_COLOR_RED, &color);
7318 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7319 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7321 /* command entry */
7322 t->cmd = gtk_entry_new();
7323 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7324 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7325 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7327 /* status bar */
7328 t->statusbar = gtk_entry_new();
7329 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7330 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7331 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7332 gdk_color_parse(XT_COLOR_BLACK, &color);
7333 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7334 gdk_color_parse(XT_COLOR_WHITE, &color);
7335 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7336 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7338 /* xtp meaning is normal by default */
7339 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7341 /* set empty favicon */
7342 xt_icon_from_name(t, "text-html");
7344 /* and show it all */
7345 gtk_widget_show_all(b);
7346 gtk_widget_show_all(t->vbox);
7348 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7349 append_tab(t);
7350 else {
7351 notfound = 1;
7352 id = gtk_notebook_get_current_page(notebook);
7353 TAILQ_FOREACH(tt, &tabs, entry) {
7354 if (tt->tab_id == id) {
7355 notfound = 0;
7356 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7357 gtk_notebook_insert_page(notebook, t->vbox, b,
7358 id + 1);
7359 recalc_tabs();
7360 break;
7363 if (notfound)
7364 append_tab(t);
7367 #if GTK_CHECK_VERSION(2, 20, 0)
7368 /* turn spinner off if we are a new tab without uri */
7369 if (!load) {
7370 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7371 gtk_widget_hide(t->spinner);
7373 #endif
7374 /* make notebook tabs reorderable */
7375 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7377 g_object_connect(G_OBJECT(t->cmd),
7378 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7379 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7380 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7381 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7382 (char *)NULL);
7384 /* reuse wv_button_cb to hide oops */
7385 g_object_connect(G_OBJECT(t->oops),
7386 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7387 (char *)NULL);
7389 g_object_connect(G_OBJECT(t->wv),
7390 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7391 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7392 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7393 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7394 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7395 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7396 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7397 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7398 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7399 "signal::event", G_CALLBACK(webview_event_cb), t,
7400 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7401 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7402 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7403 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7404 #endif
7405 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7406 (char *)NULL);
7407 g_signal_connect(t->wv,
7408 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7409 g_signal_connect(t->wv,
7410 "notify::title", G_CALLBACK(notify_title_cb), t);
7412 /* hijack the unused keys as if we were the browser */
7413 g_object_connect(G_OBJECT(t->toolbar),
7414 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7415 (char *)NULL);
7417 g_signal_connect(G_OBJECT(bb), "button_press_event",
7418 G_CALLBACK(tab_close_cb), t);
7420 /* hide stuff */
7421 hide_cmd(t);
7422 hide_oops(t);
7423 url_set_visibility();
7424 statusbar_set_visibility();
7426 if (focus) {
7427 gtk_notebook_set_current_page(notebook, t->tab_id);
7428 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7429 t->tab_id);
7432 if (load) {
7433 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7434 load_uri(t, title);
7435 } else {
7436 if (show_url == 1)
7437 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7438 else
7439 focus_webview(t);
7442 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7443 /* restore the tab's history */
7444 if (u && u->history) {
7445 items = u->history;
7446 while (items) {
7447 item = items->data;
7448 webkit_web_back_forward_list_add_item(t->bfl, item);
7449 items = g_list_next(items);
7452 item = g_list_nth_data(u->history, u->back);
7453 if (item)
7454 webkit_web_view_go_to_back_forward_item(t->wv, item);
7456 g_list_free(items);
7457 g_list_free(u->history);
7458 } else
7459 webkit_web_back_forward_list_clear(t->bfl);
7461 return (t);
7464 void
7465 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7466 gpointer *udata)
7468 struct tab *t;
7469 const gchar *uri;
7471 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7473 TAILQ_FOREACH(t, &tabs, entry) {
7474 if (t->tab_id == pn) {
7475 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7476 "%d\n", pn);
7478 uri = webkit_web_view_get_title(t->wv);
7479 if (uri == NULL)
7480 uri = XT_NAME;
7481 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7483 hide_cmd(t);
7484 hide_oops(t);
7486 if (t->focus_wv)
7487 focus_webview(t);
7492 void
7493 menuitem_response(struct tab *t)
7495 gtk_notebook_set_current_page(notebook, t->tab_id);
7498 gboolean
7499 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7501 GtkWidget *menu, *menu_items;
7502 GdkEventButton *bevent;
7503 const gchar *uri;
7504 struct tab *ti;
7506 if (event->type == GDK_BUTTON_PRESS) {
7507 bevent = (GdkEventButton *) event;
7508 menu = gtk_menu_new();
7510 TAILQ_FOREACH(ti, &tabs, entry) {
7511 if ((uri = get_uri(ti->wv)) == NULL)
7512 /* XXX make sure there is something to print */
7513 /* XXX add gui pages in here to look purdy */
7514 uri = "(untitled)";
7515 menu_items = gtk_menu_item_new_with_label(uri);
7516 gtk_menu_append(GTK_MENU (menu), menu_items);
7517 gtk_widget_show(menu_items);
7519 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7520 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7521 (gpointer)ti);
7524 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7525 bevent->button, bevent->time);
7527 /* unref object so it'll free itself when popped down */
7528 g_object_ref_sink(menu);
7529 g_object_unref(menu);
7531 return (TRUE /* eat event */);
7534 return (FALSE /* propagate */);
7538 icon_size_map(int icon_size)
7540 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7541 icon_size > GTK_ICON_SIZE_DIALOG)
7542 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7544 return (icon_size);
7547 GtkWidget *
7548 create_button(char *name, char *stockid, int size)
7550 GtkWidget *button, *image;
7551 gchar *rcstring;
7552 int gtk_icon_size;
7554 rcstring = g_strdup_printf(
7555 "style \"%s-style\"\n"
7556 "{\n"
7557 " GtkWidget::focus-padding = 0\n"
7558 " GtkWidget::focus-line-width = 0\n"
7559 " xthickness = 0\n"
7560 " ythickness = 0\n"
7561 "}\n"
7562 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7563 gtk_rc_parse_string(rcstring);
7564 g_free(rcstring);
7565 button = gtk_button_new();
7566 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7567 gtk_icon_size = icon_size_map(size ? size : icon_size);
7569 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7570 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7571 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7572 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7573 gtk_widget_set_name(button, name);
7574 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7576 return (button);
7579 void
7580 button_set_stockid(GtkWidget *button, char *stockid)
7582 GtkWidget *image;
7584 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7585 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7586 gtk_button_set_image(GTK_BUTTON(button), image);
7589 void
7590 create_canvas(void)
7592 GtkWidget *vbox;
7593 GList *l = NULL;
7594 GdkPixbuf *pb;
7595 char file[PATH_MAX];
7596 int i;
7598 vbox = gtk_vbox_new(FALSE, 0);
7599 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7600 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7601 gtk_notebook_set_tab_hborder(notebook, 0);
7602 gtk_notebook_set_tab_vborder(notebook, 0);
7603 gtk_notebook_set_scrollable(notebook, TRUE);
7604 notebook_tab_set_visibility(notebook);
7605 gtk_notebook_set_show_border(notebook, FALSE);
7606 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7608 abtn = gtk_button_new();
7609 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7610 gtk_widget_set_size_request(arrow, -1, -1);
7611 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7612 gtk_widget_set_size_request(abtn, -1, 20);
7613 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7615 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7616 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7617 gtk_widget_set_size_request(vbox, -1, -1);
7619 g_object_connect(G_OBJECT(notebook),
7620 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7621 (char *)NULL);
7622 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7623 G_CALLBACK(arrow_cb), NULL);
7625 main_window = create_window();
7626 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7627 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7629 /* icons */
7630 for (i = 0; i < LENGTH(icons); i++) {
7631 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7632 pb = gdk_pixbuf_new_from_file(file, NULL);
7633 l = g_list_append(l, pb);
7635 gtk_window_set_default_icon_list(l);
7637 gtk_widget_show_all(abtn);
7638 gtk_widget_show_all(main_window);
7641 void
7642 set_hook(void **hook, char *name)
7644 if (hook == NULL)
7645 errx(1, "set_hook");
7647 if (*hook == NULL) {
7648 *hook = dlsym(RTLD_NEXT, name);
7649 if (*hook == NULL)
7650 errx(1, "can't hook %s", name);
7654 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7655 gboolean
7656 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7658 g_return_val_if_fail(cookie1, FALSE);
7659 g_return_val_if_fail(cookie2, FALSE);
7661 return (!strcmp (cookie1->name, cookie2->name) &&
7662 !strcmp (cookie1->value, cookie2->value) &&
7663 !strcmp (cookie1->path, cookie2->path) &&
7664 !strcmp (cookie1->domain, cookie2->domain));
7667 void
7668 transfer_cookies(void)
7670 GSList *cf;
7671 SoupCookie *sc, *pc;
7673 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7675 for (;cf; cf = cf->next) {
7676 pc = cf->data;
7677 sc = soup_cookie_copy(pc);
7678 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7681 soup_cookies_free(cf);
7684 void
7685 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7687 GSList *cf;
7688 SoupCookie *ci;
7690 print_cookie("soup_cookie_jar_delete_cookie", c);
7692 if (cookies_enabled == 0)
7693 return;
7695 if (jar == NULL || c == NULL)
7696 return;
7698 /* find and remove from persistent jar */
7699 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7701 for (;cf; cf = cf->next) {
7702 ci = cf->data;
7703 if (soup_cookie_equal(ci, c)) {
7704 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7705 break;
7709 soup_cookies_free(cf);
7711 /* delete from session jar */
7712 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7715 void
7716 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7718 struct domain *d = NULL;
7719 SoupCookie *c;
7720 FILE *r_cookie_f;
7722 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7723 jar, p_cookiejar, s_cookiejar);
7725 if (cookies_enabled == 0)
7726 return;
7728 /* see if we are up and running */
7729 if (p_cookiejar == NULL) {
7730 _soup_cookie_jar_add_cookie(jar, cookie);
7731 return;
7733 /* disallow p_cookiejar adds, shouldn't happen */
7734 if (jar == p_cookiejar)
7735 return;
7737 /* sanity */
7738 if (jar == NULL || cookie == NULL)
7739 return;
7741 if (enable_cookie_whitelist &&
7742 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7743 blocked_cookies++;
7744 DNPRINTF(XT_D_COOKIE,
7745 "soup_cookie_jar_add_cookie: reject %s\n",
7746 cookie->domain);
7747 if (save_rejected_cookies) {
7748 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7749 show_oops_s("can't open reject cookie file");
7750 return;
7752 fseek(r_cookie_f, 0, SEEK_END);
7753 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7754 cookie->http_only ? "#HttpOnly_" : "",
7755 cookie->domain,
7756 *cookie->domain == '.' ? "TRUE" : "FALSE",
7757 cookie->path,
7758 cookie->secure ? "TRUE" : "FALSE",
7759 cookie->expires ?
7760 (gulong)soup_date_to_time_t(cookie->expires) :
7762 cookie->name,
7763 cookie->value);
7764 fflush(r_cookie_f);
7765 fclose(r_cookie_f);
7767 if (!allow_volatile_cookies)
7768 return;
7771 if (cookie->expires == NULL && session_timeout) {
7772 soup_cookie_set_expires(cookie,
7773 soup_date_new_from_now(session_timeout));
7774 print_cookie("modified add cookie", cookie);
7777 /* see if we are white listed for persistence */
7778 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7779 /* add to persistent jar */
7780 c = soup_cookie_copy(cookie);
7781 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7782 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7785 /* add to session jar */
7786 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7787 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7790 void
7791 setup_cookies(void)
7793 char file[PATH_MAX];
7795 set_hook((void *)&_soup_cookie_jar_add_cookie,
7796 "soup_cookie_jar_add_cookie");
7797 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7798 "soup_cookie_jar_delete_cookie");
7800 if (cookies_enabled == 0)
7801 return;
7804 * the following code is intricate due to overriding several libsoup
7805 * functions.
7806 * do not alter order of these operations.
7809 /* rejected cookies */
7810 if (save_rejected_cookies)
7811 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7813 /* persistent cookies */
7814 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7815 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7817 /* session cookies */
7818 s_cookiejar = soup_cookie_jar_new();
7819 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7820 cookie_policy, (void *)NULL);
7821 transfer_cookies();
7823 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7826 void
7827 setup_proxy(char *uri)
7829 if (proxy_uri) {
7830 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7831 soup_uri_free(proxy_uri);
7832 proxy_uri = NULL;
7834 if (http_proxy) {
7835 if (http_proxy != uri) {
7836 g_free(http_proxy);
7837 http_proxy = NULL;
7841 if (uri) {
7842 http_proxy = g_strdup(uri);
7843 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7844 proxy_uri = soup_uri_new(http_proxy);
7845 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7850 send_cmd_to_socket(char *cmd)
7852 int s, len, rv = 1;
7853 struct sockaddr_un sa;
7855 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7856 warnx("%s: socket", __func__);
7857 return (rv);
7860 sa.sun_family = AF_UNIX;
7861 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7862 work_dir, XT_SOCKET_FILE);
7863 len = SUN_LEN(&sa);
7865 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7866 warnx("%s: connect", __func__);
7867 goto done;
7870 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7871 warnx("%s: send", __func__);
7872 goto done;
7875 rv = 0;
7876 done:
7877 close(s);
7878 return (rv);
7881 void
7882 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7884 int s, n;
7885 char str[XT_MAX_URL_LENGTH];
7886 socklen_t t = sizeof(struct sockaddr_un);
7887 struct sockaddr_un sa;
7888 struct passwd *p;
7889 uid_t uid;
7890 gid_t gid;
7891 struct tab *tt;
7893 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7894 warn("accept");
7895 return;
7898 if (getpeereid(s, &uid, &gid) == -1) {
7899 warn("getpeereid");
7900 return;
7902 if (uid != getuid() || gid != getgid()) {
7903 warnx("unauthorized user");
7904 return;
7907 p = getpwuid(uid);
7908 if (p == NULL) {
7909 warnx("not a valid user");
7910 return;
7913 n = recv(s, str, sizeof(str), 0);
7914 if (n <= 0)
7915 return;
7917 tt = TAILQ_LAST(&tabs, tab_list);
7918 cmd_execute(tt, str);
7922 is_running(void)
7924 int s, len, rv = 1;
7925 struct sockaddr_un sa;
7927 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7928 warn("is_running: socket");
7929 return (-1);
7932 sa.sun_family = AF_UNIX;
7933 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7934 work_dir, XT_SOCKET_FILE);
7935 len = SUN_LEN(&sa);
7937 /* connect to see if there is a listener */
7938 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7939 rv = 0; /* not running */
7940 else
7941 rv = 1; /* already running */
7943 close(s);
7945 return (rv);
7949 build_socket(void)
7951 int s, len;
7952 struct sockaddr_un sa;
7954 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7955 warn("build_socket: socket");
7956 return (-1);
7959 sa.sun_family = AF_UNIX;
7960 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7961 work_dir, XT_SOCKET_FILE);
7962 len = SUN_LEN(&sa);
7964 /* connect to see if there is a listener */
7965 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7966 /* no listener so we will */
7967 unlink(sa.sun_path);
7969 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7970 warn("build_socket: bind");
7971 goto done;
7974 if (listen(s, 1) == -1) {
7975 warn("build_socket: listen");
7976 goto done;
7979 return (s);
7982 done:
7983 close(s);
7984 return (-1);
7987 static gboolean
7988 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7989 GtkTreeIter *iter, struct tab *t)
7991 gchar *value;
7993 gtk_tree_model_get(model, iter, 0, &value, -1);
7994 load_uri(t, value);
7996 return (FALSE);
7999 void
8000 completion_add_uri(const gchar *uri)
8002 GtkTreeIter iter;
8004 /* add uri to list_store */
8005 gtk_list_store_append(completion_model, &iter);
8006 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8009 gboolean
8010 completion_match(GtkEntryCompletion *completion, const gchar *key,
8011 GtkTreeIter *iter, gpointer user_data)
8013 gchar *value;
8014 gboolean match = FALSE;
8016 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8017 -1);
8019 if (value == NULL)
8020 return FALSE;
8022 match = match_uri(value, key);
8024 g_free(value);
8025 return (match);
8028 void
8029 completion_add(struct tab *t)
8031 /* enable completion for tab */
8032 t->completion = gtk_entry_completion_new();
8033 gtk_entry_completion_set_text_column(t->completion, 0);
8034 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8035 gtk_entry_completion_set_model(t->completion,
8036 GTK_TREE_MODEL(completion_model));
8037 gtk_entry_completion_set_match_func(t->completion, completion_match,
8038 NULL, NULL);
8039 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8040 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8041 G_CALLBACK(completion_select_cb), t);
8044 void
8045 usage(void)
8047 fprintf(stderr,
8048 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8049 exit(0);
8053 main(int argc, char *argv[])
8055 struct stat sb;
8056 int c, s, optn = 0, opte = 0, focus = 1;
8057 char conf[PATH_MAX] = { '\0' };
8058 char file[PATH_MAX];
8059 char *env_proxy = NULL;
8060 FILE *f = NULL;
8061 struct karg a;
8062 struct sigaction sact;
8063 gchar *priority = g_strdup("NORMAL");
8065 start_argv = argv;
8067 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8069 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8070 switch (c) {
8071 case 'S':
8072 show_url = 0;
8073 break;
8074 case 'T':
8075 show_tabs = 0;
8076 break;
8077 case 'V':
8078 errx(0 , "Version: %s", version);
8079 break;
8080 case 'f':
8081 strlcpy(conf, optarg, sizeof(conf));
8082 break;
8083 case 's':
8084 strlcpy(named_session, optarg, sizeof(named_session));
8085 break;
8086 case 't':
8087 tabless = 1;
8088 break;
8089 case 'n':
8090 optn = 1;
8091 break;
8092 case 'e':
8093 opte = 1;
8094 break;
8095 default:
8096 usage();
8097 /* NOTREACHED */
8100 argc -= optind;
8101 argv += optind;
8103 RB_INIT(&hl);
8104 RB_INIT(&js_wl);
8105 RB_INIT(&downloads);
8107 TAILQ_INIT(&tabs);
8108 TAILQ_INIT(&mtl);
8109 TAILQ_INIT(&aliases);
8110 TAILQ_INIT(&undos);
8111 TAILQ_INIT(&kbl);
8113 init_keybindings();
8115 gnutls_global_init();
8117 /* generate session keys for xtp pages */
8118 generate_xtp_session_key(&dl_session_key);
8119 generate_xtp_session_key(&hl_session_key);
8120 generate_xtp_session_key(&cl_session_key);
8121 generate_xtp_session_key(&fl_session_key);
8123 /* prepare gtk */
8124 gtk_init(&argc, &argv);
8125 if (!g_thread_supported())
8126 g_thread_init(NULL);
8128 /* signals */
8129 bzero(&sact, sizeof(sact));
8130 sigemptyset(&sact.sa_mask);
8131 sact.sa_handler = sigchild;
8132 sact.sa_flags = SA_NOCLDSTOP;
8133 sigaction(SIGCHLD, &sact, NULL);
8135 /* set download dir */
8136 pwd = getpwuid(getuid());
8137 if (pwd == NULL)
8138 errx(1, "invalid user %d", getuid());
8139 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8141 /* set default string settings */
8142 home = g_strdup("http://www.peereboom.us");
8143 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8144 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8145 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8147 /* read config file */
8148 if (strlen(conf) == 0)
8149 snprintf(conf, sizeof conf, "%s/.%s",
8150 pwd->pw_dir, XT_CONF_FILE);
8151 config_parse(conf, 0);
8153 /* working directory */
8154 if (strlen(work_dir) == 0)
8155 snprintf(work_dir, sizeof work_dir, "%s/%s",
8156 pwd->pw_dir, XT_DIR);
8157 if (stat(work_dir, &sb)) {
8158 if (mkdir(work_dir, S_IRWXU) == -1)
8159 err(1, "mkdir work_dir");
8160 if (stat(work_dir, &sb))
8161 err(1, "stat work_dir");
8163 if (S_ISDIR(sb.st_mode) == 0)
8164 errx(1, "%s not a dir", work_dir);
8165 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8166 warnx("fixing invalid permissions on %s", work_dir);
8167 if (chmod(work_dir, S_IRWXU) == -1)
8168 err(1, "chmod");
8171 /* icon cache dir */
8172 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8173 if (stat(cache_dir, &sb)) {
8174 if (mkdir(cache_dir, S_IRWXU) == -1)
8175 err(1, "mkdir cache_dir");
8176 if (stat(cache_dir, &sb))
8177 err(1, "stat cache_dir");
8179 if (S_ISDIR(sb.st_mode) == 0)
8180 errx(1, "%s not a dir", cache_dir);
8181 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8182 warnx("fixing invalid permissions on %s", cache_dir);
8183 if (chmod(cache_dir, S_IRWXU) == -1)
8184 err(1, "chmod");
8187 /* certs dir */
8188 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8189 if (stat(certs_dir, &sb)) {
8190 if (mkdir(certs_dir, S_IRWXU) == -1)
8191 err(1, "mkdir certs_dir");
8192 if (stat(certs_dir, &sb))
8193 err(1, "stat certs_dir");
8195 if (S_ISDIR(sb.st_mode) == 0)
8196 errx(1, "%s not a dir", certs_dir);
8197 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8198 warnx("fixing invalid permissions on %s", certs_dir);
8199 if (chmod(certs_dir, S_IRWXU) == -1)
8200 err(1, "chmod");
8203 /* sessions dir */
8204 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8205 work_dir, XT_SESSIONS_DIR);
8206 if (stat(sessions_dir, &sb)) {
8207 if (mkdir(sessions_dir, S_IRWXU) == -1)
8208 err(1, "mkdir sessions_dir");
8209 if (stat(sessions_dir, &sb))
8210 err(1, "stat sessions_dir");
8212 if (S_ISDIR(sb.st_mode) == 0)
8213 errx(1, "%s not a dir", sessions_dir);
8214 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8215 warnx("fixing invalid permissions on %s", sessions_dir);
8216 if (chmod(sessions_dir, S_IRWXU) == -1)
8217 err(1, "chmod");
8219 /* runtime settings that can override config file */
8220 if (runtime_settings[0] != '\0')
8221 config_parse(runtime_settings, 1);
8223 /* download dir */
8224 if (!strcmp(download_dir, pwd->pw_dir))
8225 strlcat(download_dir, "/downloads", sizeof download_dir);
8226 if (stat(download_dir, &sb)) {
8227 if (mkdir(download_dir, S_IRWXU) == -1)
8228 err(1, "mkdir download_dir");
8229 if (stat(download_dir, &sb))
8230 err(1, "stat download_dir");
8232 if (S_ISDIR(sb.st_mode) == 0)
8233 errx(1, "%s not a dir", download_dir);
8234 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8235 warnx("fixing invalid permissions on %s", download_dir);
8236 if (chmod(download_dir, S_IRWXU) == -1)
8237 err(1, "chmod");
8240 /* favorites file */
8241 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8242 if (stat(file, &sb)) {
8243 warnx("favorites file doesn't exist, creating it");
8244 if ((f = fopen(file, "w")) == NULL)
8245 err(1, "favorites");
8246 fclose(f);
8249 /* cookies */
8250 session = webkit_get_default_session();
8251 /* XXX ssl-priority property not quite available yet */
8252 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8253 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8254 (char *)NULL);
8255 else
8256 warnx("session does not have \"ssl-priority\" property");
8257 setup_cookies();
8259 /* certs */
8260 if (ssl_ca_file) {
8261 if (stat(ssl_ca_file, &sb)) {
8262 warn("no CA file: %s", ssl_ca_file);
8263 g_free(ssl_ca_file);
8264 ssl_ca_file = NULL;
8265 } else
8266 g_object_set(session,
8267 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8268 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8269 (void *)NULL);
8272 /* proxy */
8273 env_proxy = getenv("http_proxy");
8274 if (env_proxy)
8275 setup_proxy(env_proxy);
8276 else
8277 setup_proxy(http_proxy);
8279 if (opte) {
8280 send_cmd_to_socket(argv[0]);
8281 exit(0);
8284 /* set some connection parameters */
8285 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8286 g_object_set(session, "max-conns-per-host", max_host_connections,
8287 (char *)NULL);
8289 /* see if there is already an xxxterm running */
8290 if (single_instance && is_running()) {
8291 optn = 1;
8292 warnx("already running");
8295 char *cmd = NULL;
8296 if (optn) {
8297 while (argc) {
8298 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8299 send_cmd_to_socket(cmd);
8300 if (cmd)
8301 g_free(cmd);
8303 argc--;
8304 argv++;
8306 exit(0);
8309 /* uri completion */
8310 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8312 /* go graphical */
8313 create_canvas();
8315 if (save_global_history)
8316 restore_global_history();
8318 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8319 restore_saved_tabs();
8320 else {
8321 a.s = named_session;
8322 a.i = XT_SES_DONOTHING;
8323 open_tabs(NULL, &a);
8326 while (argc) {
8327 create_new_tab(argv[0], NULL, focus);
8328 focus = 0;
8330 argc--;
8331 argv++;
8334 if (TAILQ_EMPTY(&tabs))
8335 create_new_tab(home, NULL, 1);
8337 if (enable_socket)
8338 if ((s = build_socket()) != -1)
8339 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8341 gtk_main();
8343 gnutls_global_deinit();
8345 return (0);