dont crash when running in tabless and trying to create a new tab; open in
[xxxterm.git] / xxxterm.c
blobc43d49f085a24b98bec020c372d1cb015fa43e29
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 (tabless) {
6045 /* open in current tab */
6046 webview = t->wv;
6047 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6048 uri = webkit_web_view_get_uri(wv);
6049 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6050 return (NULL);
6052 tt = create_new_tab(NULL, NULL, 1);
6053 webview = tt->wv;
6054 } else if (enable_scripts == 1) {
6055 tt = create_new_tab(NULL, NULL, 1);
6056 webview = tt->wv;
6059 return (webview);
6062 gboolean
6063 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6065 const gchar *uri;
6066 struct domain *d = NULL;
6068 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6070 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6071 uri = webkit_web_view_get_uri(wv);
6072 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6073 return (FALSE);
6075 delete_tab(t);
6076 } else if (enable_scripts == 1)
6077 delete_tab(t);
6079 return (TRUE);
6083 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6085 /* we can not eat the event without throwing gtk off so defer it */
6087 /* catch middle click */
6088 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6089 t->ctrl_click = 1;
6090 goto done;
6093 /* catch ctrl click */
6094 if (e->type == GDK_BUTTON_RELEASE &&
6095 CLEAN(e->state) == GDK_CONTROL_MASK)
6096 t->ctrl_click = 1;
6097 else
6098 t->ctrl_click = 0;
6099 done:
6100 return (XT_CB_PASSTHROUGH);
6104 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6106 struct mime_type *m;
6108 m = find_mime_type(mime_type);
6109 if (m == NULL)
6110 return (1);
6111 if (m->mt_download)
6112 return (1);
6114 switch (fork()) {
6115 case -1:
6116 show_oops(t, "can't fork mime handler");
6117 /* NOTREACHED */
6118 case 0:
6119 break;
6120 default:
6121 return (0);
6124 /* child */
6125 execlp(m->mt_action, m->mt_action,
6126 webkit_network_request_get_uri(request), (void *)NULL);
6128 _exit(0);
6130 /* NOTREACHED */
6131 return (0);
6134 const gchar *
6135 get_mime_type(char *file)
6137 const char *mime_type;
6138 GFileInfo *fi;
6139 GFile *gf;
6141 if (g_str_has_prefix(file, "file://"))
6142 file += strlen("file://");
6144 gf = g_file_new_for_path(file);
6145 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6146 NULL, NULL);
6147 mime_type = g_file_info_get_content_type(fi);
6148 g_object_unref(fi);
6149 g_object_unref(gf);
6151 return (mime_type);
6155 run_download_mimehandler(char *mime_type, char *file)
6157 struct mime_type *m;
6159 m = find_mime_type(mime_type);
6160 if (m == NULL)
6161 return (1);
6163 switch (fork()) {
6164 case -1:
6165 show_oops_s("can't fork download mime handler");
6166 /* NOTREACHED */
6167 case 0:
6168 break;
6169 default:
6170 return (0);
6173 /* child */
6174 if (g_str_has_prefix(file, "file://"))
6175 file += strlen("file://");
6176 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6178 _exit(0);
6180 /* NOTREACHED */
6181 return (0);
6184 void
6185 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6186 WebKitWebView *wv)
6188 WebKitDownloadStatus status;
6189 const gchar *file = NULL, *mime = NULL;
6191 if (download == NULL)
6192 return;
6193 status = webkit_download_get_status(download);
6194 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6195 return;
6197 file = webkit_download_get_destination_uri(download);
6198 if (file == NULL)
6199 return;
6200 mime = get_mime_type((char *)file);
6201 if (mime == NULL)
6202 return;
6204 run_download_mimehandler((char *)mime, (char *)file);
6208 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6209 WebKitNetworkRequest *request, char *mime_type,
6210 WebKitWebPolicyDecision *decision, struct tab *t)
6212 if (t == NULL) {
6213 show_oops_s("webview_mimetype_cb invalid parameters");
6214 return (FALSE);
6217 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6218 t->tab_id, mime_type);
6220 if (run_mimehandler(t, mime_type, request) == 0) {
6221 webkit_web_policy_decision_ignore(decision);
6222 focus_webview(t);
6223 return (TRUE);
6226 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6227 webkit_web_policy_decision_download(decision);
6228 return (TRUE);
6231 return (FALSE);
6235 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6236 struct tab *t)
6238 const gchar *filename;
6239 char *uri = NULL;
6240 struct download *download_entry;
6241 int ret = TRUE;
6243 if (wk_download == NULL || t == NULL) {
6244 show_oops_s("%s invalid parameters", __func__);
6245 return (FALSE);
6248 filename = webkit_download_get_suggested_filename(wk_download);
6249 if (filename == NULL)
6250 return (FALSE); /* abort download */
6252 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6254 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6255 "local %s\n", __func__, t->tab_id, filename, uri);
6257 webkit_download_set_destination_uri(wk_download, uri);
6259 if (webkit_download_get_status(wk_download) ==
6260 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6261 show_oops(t, "%s: download failed to start", __func__);
6262 ret = FALSE;
6263 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6264 } else {
6265 /* connect "download first" mime handler */
6266 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6267 G_CALLBACK(download_status_changed_cb), NULL);
6269 download_entry = g_malloc(sizeof(struct download));
6270 download_entry->download = wk_download;
6271 download_entry->tab = t;
6272 download_entry->id = next_download_id++;
6273 RB_INSERT(download_list, &downloads, download_entry);
6274 /* get from history */
6275 g_object_ref(wk_download);
6276 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6277 show_oops(t, "Download of '%s' started...",
6278 basename(webkit_download_get_destination_uri(wk_download)));
6281 if (uri)
6282 g_free(uri);
6284 /* sync other download manager tabs */
6285 update_download_tabs(NULL);
6288 * NOTE: never redirect/render the current tab before this
6289 * function returns. This will cause the download to never start.
6291 return (ret); /* start download */
6294 void
6295 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6297 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6299 if (t == NULL) {
6300 show_oops_s("webview_hover_cb");
6301 return;
6304 if (uri)
6305 set_status(t, uri, XT_STATUS_LINK);
6306 else {
6307 if (t->status)
6308 set_status(t, t->status, XT_STATUS_NOTHING);
6312 gboolean
6313 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6315 struct key_binding *k;
6317 TAILQ_FOREACH(k, &kbl, entry)
6318 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6319 if (k->mask == 0) {
6320 if ((e->state & (CTRL | MOD1)) == 0)
6321 return (cmd_execute(t, k->cmd));
6322 } else if ((e->state & k->mask) == k->mask) {
6323 return (cmd_execute(t, k->cmd));
6327 return (XT_CB_PASSTHROUGH);
6331 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6333 char s[2], buf[128];
6334 const char *errstr = NULL;
6335 long long link;
6337 /* don't use w directly; use t->whatever instead */
6339 if (t == NULL) {
6340 show_oops_s("wv_keypress_after_cb");
6341 return (XT_CB_PASSTHROUGH);
6344 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6345 e->keyval, e->state, t);
6347 if (t->hints_on) {
6348 /* ESC */
6349 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6350 disable_hints(t);
6351 return (XT_CB_HANDLED);
6354 /* RETURN */
6355 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6356 link = strtonum(t->hint_num, 1, 1000, &errstr);
6357 if (errstr) {
6358 /* we have a string */
6359 } else {
6360 /* we have a number */
6361 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6362 t->hint_num);
6363 run_script(t, buf);
6365 disable_hints(t);
6368 /* BACKSPACE */
6369 /* XXX unfuck this */
6370 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6371 if (t->hint_mode == XT_HINT_NUMERICAL) {
6372 /* last input was numerical */
6373 int l;
6374 l = strlen(t->hint_num);
6375 if (l > 0) {
6376 l--;
6377 if (l == 0) {
6378 disable_hints(t);
6379 enable_hints(t);
6380 } else {
6381 t->hint_num[l] = '\0';
6382 goto num;
6385 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6386 /* last input was alphanumerical */
6387 int l;
6388 l = strlen(t->hint_buf);
6389 if (l > 0) {
6390 l--;
6391 if (l == 0) {
6392 disable_hints(t);
6393 enable_hints(t);
6394 } else {
6395 t->hint_buf[l] = '\0';
6396 goto anum;
6399 } else {
6400 /* bogus */
6401 disable_hints(t);
6405 /* numerical input */
6406 if (CLEAN(e->state) == 0 &&
6407 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6408 snprintf(s, sizeof s, "%c", e->keyval);
6409 strlcat(t->hint_num, s, sizeof t->hint_num);
6410 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6411 t->hint_num);
6412 num:
6413 link = strtonum(t->hint_num, 1, 1000, &errstr);
6414 if (errstr) {
6415 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6416 disable_hints(t);
6417 } else {
6418 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6419 t->hint_num);
6420 t->hint_mode = XT_HINT_NUMERICAL;
6421 run_script(t, buf);
6424 /* empty the counter buffer */
6425 bzero(t->hint_buf, sizeof t->hint_buf);
6426 return (XT_CB_HANDLED);
6429 /* alphanumerical input */
6430 if (
6431 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6432 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6433 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6434 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6435 snprintf(s, sizeof s, "%c", e->keyval);
6436 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6437 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6438 t->hint_buf);
6439 anum:
6440 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6441 run_script(t, buf);
6443 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6444 t->hint_buf);
6445 t->hint_mode = XT_HINT_ALPHANUM;
6446 run_script(t, buf);
6448 /* empty the counter buffer */
6449 bzero(t->hint_num, sizeof t->hint_num);
6450 return (XT_CB_HANDLED);
6453 return (XT_CB_HANDLED);
6456 return (handle_keypress(t, e, 0));
6460 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6462 hide_oops(t);
6464 return (XT_CB_PASSTHROUGH);
6468 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6470 const gchar *c = gtk_entry_get_text(w);
6471 GdkColor color;
6472 int forward = TRUE;
6474 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6475 e->keyval, e->state, t);
6477 if (t == NULL) {
6478 show_oops_s("cmd_keyrelease_cb invalid parameters");
6479 return (XT_CB_PASSTHROUGH);
6482 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6483 e->keyval, e->state, t);
6485 if (c[0] == ':')
6486 goto done;
6487 if (strlen(c) == 1) {
6488 webkit_web_view_unmark_text_matches(t->wv);
6489 goto done;
6492 if (c[0] == '/')
6493 forward = TRUE;
6494 else if (c[0] == '?')
6495 forward = FALSE;
6496 else
6497 goto done;
6499 /* search */
6500 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6501 FALSE) {
6502 /* not found, mark red */
6503 gdk_color_parse(XT_COLOR_RED, &color);
6504 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6505 /* unmark and remove selection */
6506 webkit_web_view_unmark_text_matches(t->wv);
6507 /* my kingdom for a way to unselect text in webview */
6508 } else {
6509 /* found, highlight all */
6510 webkit_web_view_unmark_text_matches(t->wv);
6511 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6512 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6513 gdk_color_parse(XT_COLOR_WHITE, &color);
6514 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6516 done:
6517 return (XT_CB_PASSTHROUGH);
6520 gboolean
6521 match_uri(const gchar *uri, const gchar *key) {
6522 gchar *voffset;
6523 size_t len;
6524 gboolean match = FALSE;
6526 len = strlen(key);
6528 if (!strncmp(key, uri, len))
6529 match = TRUE;
6530 else {
6531 voffset = strstr(uri, "/") + 2;
6532 if (!strncmp(key, voffset, len))
6533 match = TRUE;
6534 else if (g_str_has_prefix(voffset, "www.")) {
6535 voffset = voffset + strlen("www.");
6536 if (!strncmp(key, voffset, len))
6537 match = TRUE;
6541 return (match);
6544 void
6545 cmd_getlist(int id, char *key)
6547 int i, dep, c = 0;
6548 struct history *h;
6550 if (id >= 0 && (cmds[id].userarg && (cmds[id].arg.i == XT_TAB_OPEN || cmds[id].arg.i == XT_TAB_NEW))) {
6551 RB_FOREACH_REVERSE(h, history_list, &hl)
6552 if (match_uri(h->uri, key)) {
6553 cmd_status.list[c] = (char *)h->uri;
6554 if (++c > 255)
6555 break;
6558 cmd_status.len = c;
6559 return;
6562 dep = (id == -1) ? 0 : cmds[id].level + 1;
6564 for (i = id + 1; i < LENGTH(cmds); i++) {
6565 if(cmds[i].level < dep)
6566 break;
6567 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6568 cmd_status.list[c++] = cmds[i].cmd;
6572 cmd_status.len = c;
6575 char *
6576 cmd_getnext(int dir)
6578 cmd_status.index += dir;
6580 if (cmd_status.index<0)
6581 cmd_status.index = cmd_status.len-1;
6582 else if (cmd_status.index >= cmd_status.len)
6583 cmd_status.index = 0;
6585 return cmd_status.list[cmd_status.index];
6589 cmd_tokenize(char *s, char *tokens[])
6591 int i = 0;
6592 char *tok, *last;
6593 size_t len = strlen(s);
6594 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6596 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6597 tokens[i] = tok;
6599 if (blank && i < 3)
6600 tokens[i++] = "";
6602 return (i);
6605 void
6606 cmd_complete(struct tab *t, char *str, int dir)
6608 GtkEntry *w = GTK_ENTRY(t->cmd);
6609 int i, j, levels, c = 0, dep = 0, parent = -1;
6610 char *tok, *match, *s = strdup(str);
6611 char *tokens[3];
6612 char res[XT_MAX_URL_LENGTH + 32] = ":";
6614 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", str);
6616 levels = cmd_tokenize(s, tokens);
6618 for (i = 0; i < levels - 1; i++) {
6619 tok = tokens[i];
6620 for (j = c; j < LENGTH(cmds); j++)
6621 if (cmds[j].level == dep && !strcmp(tok, cmds[j].cmd)) {
6622 strlcat(res, tok, sizeof res);
6623 strlcat(res, " ", sizeof res);
6624 dep++;
6625 c = j;
6626 break;
6629 parent = c;
6632 if (cmd_status.index == -1)
6633 cmd_getlist(parent, tokens[i]);
6635 if (cmd_status.len > 0) {
6636 match = cmd_getnext(dir);
6637 strlcat(res, match, sizeof res);
6638 gtk_entry_set_text(w, res);
6639 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6643 gboolean
6644 cmd_valid(char *str)
6646 char *tok, *last, *s = g_strdup(str);
6647 int i, c = 0, dep = 0;
6649 for (tok = strtok_r(s, " ", &last); tok;
6650 tok = strtok_r(NULL, " ", &last)) {
6651 for (i = c; i < LENGTH(cmds); i++) {
6652 if (cmds[i].level < dep) {
6653 return (XT_CB_PASSTHROUGH);
6655 if (cmds[i].level == dep && !strcmp(tok, cmds[i].cmd)) {
6656 if (cmds[i].userarg) {
6657 return (XT_CB_HANDLED);
6659 c = i + 1;
6660 dep++;
6661 break;
6664 if (i == LENGTH(cmds)) {
6665 return (XT_CB_PASSTHROUGH);
6668 return (XT_CB_HANDLED);
6671 gboolean
6672 cmd_execute(struct tab *t, char *str)
6674 struct cmd *cmd = NULL;
6675 char *tok, *last, *s = g_strdup(str);
6676 int j, c = 0, dep = 0;
6678 for (tok = strtok_r(s, " ", &last); tok;
6679 tok = strtok_r(NULL, " ", &last)) {
6680 for (j = c; j < LENGTH(cmds); j++) {
6681 if (cmds[j].level < dep) {
6682 show_oops(t, "Invalid command: %s", str);
6683 return (XT_CB_PASSTHROUGH);
6685 if (cmds[j].level == dep && !strcmp(tok, cmds[j].cmd)) {
6686 cmd = &cmds[j];
6687 if (cmd->userarg) {
6688 cmd->arg.s = last ? g_strdup(last) : g_strdup("");
6689 goto execute_cmd;
6691 c = j + 1;
6692 dep++;
6693 break;
6696 if (j == LENGTH(cmds)) {
6697 show_oops(t, "Invalid command: %s", str);
6698 return (XT_CB_PASSTHROUGH);
6701 cmd->arg.s = g_strdup(tok);
6702 execute_cmd:
6703 /* arg->s contains last token */
6704 cmd->func(t, &cmd->arg);
6705 if (cmd->arg.s)
6706 g_free(cmd->arg.s);
6708 return (XT_CB_HANDLED);
6712 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6714 if (t == NULL) {
6715 show_oops_s("entry_key_cb invalid parameters");
6716 return (XT_CB_PASSTHROUGH);
6719 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6720 e->keyval, e->state, t);
6722 hide_oops(t);
6724 if (e->keyval == GDK_Escape) {
6725 /* don't use focus_webview(t) because we want to type :cmds */
6726 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6729 return (handle_keypress(t, e, 1));
6733 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6735 int rv = XT_CB_HANDLED;
6736 const gchar *c = gtk_entry_get_text(w);
6738 if (t == NULL) {
6739 show_oops_s("cmd_keypress_cb parameters");
6740 return (XT_CB_PASSTHROUGH);
6743 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6744 e->keyval, e->state, t);
6746 /* sanity */
6747 if (c == NULL)
6748 e->keyval = GDK_Escape;
6749 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6750 e->keyval = GDK_Escape;
6752 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6753 cmd_status.index = -1;
6755 switch (e->keyval) {
6756 case GDK_Tab:
6757 if (c[0] == ':')
6758 cmd_complete(t, (char *)&c[1], 1);
6759 goto done;
6760 case GDK_ISO_Left_Tab:
6761 if (c[0] == ':')
6762 cmd_complete(t, (char *)&c[1], -1);
6764 goto done;
6765 case GDK_BackSpace:
6766 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6767 break;
6768 /* FALLTHROUGH */
6769 case GDK_Escape:
6770 hide_cmd(t);
6771 focus_webview(t);
6773 /* cancel search */
6774 if (c[0] == '/' || c[0] == '?')
6775 webkit_web_view_unmark_text_matches(t->wv);
6776 goto done;
6779 rv = XT_CB_PASSTHROUGH;
6780 done:
6781 return (rv);
6785 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6787 if (t == NULL) {
6788 show_oops_s("cmd_focusout_cb invalid parameters");
6789 return (XT_CB_PASSTHROUGH);
6791 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6793 hide_cmd(t);
6794 hide_oops(t);
6796 if (show_url == 0 || t->focus_wv)
6797 focus_webview(t);
6798 else
6799 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6801 return (XT_CB_PASSTHROUGH);
6804 void
6805 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6807 char *s;
6808 const gchar *c = gtk_entry_get_text(entry);
6810 if (t == NULL) {
6811 show_oops_s("cmd_activate_cb invalid parameters");
6812 return;
6815 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6817 /* sanity */
6818 if (c == NULL)
6819 goto done;
6820 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6821 goto done;
6822 if (strlen(c) < 2)
6823 goto done;
6824 s = (char *)&c[1];
6826 if (c[0] == '/' || c[0] == '?') {
6827 if (t->search_text) {
6828 g_free(t->search_text);
6829 t->search_text = NULL;
6832 t->search_text = g_strdup(s);
6833 if (global_search)
6834 g_free(global_search);
6835 global_search = g_strdup(s);
6836 t->search_forward = c[0] == '/';
6838 goto done;
6841 cmd_execute(t, s);
6843 done:
6844 hide_cmd(t);
6847 void
6848 backward_cb(GtkWidget *w, struct tab *t)
6850 struct karg a;
6852 if (t == NULL) {
6853 show_oops_s("backward_cb invalid parameters");
6854 return;
6857 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6859 a.i = XT_NAV_BACK;
6860 navaction(t, &a);
6863 void
6864 forward_cb(GtkWidget *w, struct tab *t)
6866 struct karg a;
6868 if (t == NULL) {
6869 show_oops_s("forward_cb invalid parameters");
6870 return;
6873 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6875 a.i = XT_NAV_FORWARD;
6876 navaction(t, &a);
6879 void
6880 home_cb(GtkWidget *w, struct tab *t)
6882 if (t == NULL) {
6883 show_oops_s("home_cb invalid parameters");
6884 return;
6887 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6889 load_uri(t, home);
6892 void
6893 stop_cb(GtkWidget *w, struct tab *t)
6895 WebKitWebFrame *frame;
6897 if (t == NULL) {
6898 show_oops_s("stop_cb invalid parameters");
6899 return;
6902 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6904 frame = webkit_web_view_get_main_frame(t->wv);
6905 if (frame == NULL) {
6906 show_oops(t, "stop_cb: no frame");
6907 return;
6910 webkit_web_frame_stop_loading(frame);
6911 abort_favicon_download(t);
6914 void
6915 setup_webkit(struct tab *t)
6917 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6918 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6919 FALSE, (char *)NULL);
6920 else
6921 warnx("webkit does not have \"enable-dns-prefetching\" property");
6922 g_object_set(G_OBJECT(t->settings),
6923 "user-agent", t->user_agent, (char *)NULL);
6924 g_object_set(G_OBJECT(t->settings),
6925 "enable-scripts", enable_scripts, (char *)NULL);
6926 g_object_set(G_OBJECT(t->settings),
6927 "enable-plugins", enable_plugins, (char *)NULL);
6928 g_object_set(G_OBJECT(t->settings),
6929 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6930 g_object_set(G_OBJECT(t->wv),
6931 "full-content-zoom", TRUE, (char *)NULL);
6932 adjustfont_webkit(t, XT_FONT_SET);
6934 webkit_web_view_set_settings(t->wv, t->settings);
6937 GtkWidget *
6938 create_browser(struct tab *t)
6940 GtkWidget *w;
6941 gchar *strval;
6943 if (t == NULL) {
6944 show_oops_s("create_browser invalid parameters");
6945 return (NULL);
6948 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6949 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6950 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6951 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6953 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6954 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6955 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6957 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6958 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6960 /* set defaults */
6961 t->settings = webkit_web_settings_new();
6963 if (user_agent == NULL) {
6964 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6965 (char *)NULL);
6966 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6967 g_free(strval);
6968 } else
6969 t->user_agent = g_strdup(user_agent);
6971 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
6973 setup_webkit(t);
6975 return (w);
6978 GtkWidget *
6979 create_window(void)
6981 GtkWidget *w;
6983 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6984 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6985 gtk_widget_set_name(w, "xxxterm");
6986 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6987 g_signal_connect(G_OBJECT(w), "delete_event",
6988 G_CALLBACK (gtk_main_quit), NULL);
6990 return (w);
6993 GtkWidget *
6994 create_kiosk_toolbar(struct tab *t)
6996 GtkWidget *toolbar = NULL, *b;
6998 b = gtk_hbox_new(FALSE, 0);
6999 toolbar = b;
7000 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7002 /* backward button */
7003 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7004 gtk_widget_set_sensitive(t->backward, FALSE);
7005 g_signal_connect(G_OBJECT(t->backward), "clicked",
7006 G_CALLBACK(backward_cb), t);
7007 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7009 /* forward button */
7010 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7011 gtk_widget_set_sensitive(t->forward, FALSE);
7012 g_signal_connect(G_OBJECT(t->forward), "clicked",
7013 G_CALLBACK(forward_cb), t);
7014 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7016 /* home button */
7017 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7018 gtk_widget_set_sensitive(t->gohome, true);
7019 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7020 G_CALLBACK(home_cb), t);
7021 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7023 /* create widgets but don't use them */
7024 t->uri_entry = gtk_entry_new();
7025 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7026 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7027 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7029 return (toolbar);
7032 GtkWidget *
7033 create_toolbar(struct tab *t)
7035 GtkWidget *toolbar = NULL, *b, *eb1;
7037 b = gtk_hbox_new(FALSE, 0);
7038 toolbar = b;
7039 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7041 if (fancy_bar) {
7042 /* backward button */
7043 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7044 gtk_widget_set_sensitive(t->backward, FALSE);
7045 g_signal_connect(G_OBJECT(t->backward), "clicked",
7046 G_CALLBACK(backward_cb), t);
7047 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7049 /* forward button */
7050 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7051 gtk_widget_set_sensitive(t->forward, FALSE);
7052 g_signal_connect(G_OBJECT(t->forward), "clicked",
7053 G_CALLBACK(forward_cb), t);
7054 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7055 FALSE, 0);
7057 /* stop button */
7058 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7059 gtk_widget_set_sensitive(t->stop, FALSE);
7060 g_signal_connect(G_OBJECT(t->stop), "clicked",
7061 G_CALLBACK(stop_cb), t);
7062 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7063 FALSE, 0);
7065 /* JS button */
7066 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7067 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7068 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7069 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7070 G_CALLBACK(js_toggle_cb), t);
7071 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7074 t->uri_entry = gtk_entry_new();
7075 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7076 G_CALLBACK(activate_uri_entry_cb), t);
7077 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7078 G_CALLBACK(entry_key_cb), t);
7079 completion_add(t);
7080 eb1 = gtk_hbox_new(FALSE, 0);
7081 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7082 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7083 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7085 /* search entry */
7086 if (fancy_bar && search_string) {
7087 GtkWidget *eb2;
7088 t->search_entry = gtk_entry_new();
7089 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7090 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7091 G_CALLBACK(activate_search_entry_cb), t);
7092 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7093 G_CALLBACK(entry_key_cb), t);
7094 gtk_widget_set_size_request(t->search_entry, -1, -1);
7095 eb2 = gtk_hbox_new(FALSE, 0);
7096 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7097 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7099 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7101 return (toolbar);
7104 void
7105 recalc_tabs(void)
7107 struct tab *t;
7109 TAILQ_FOREACH(t, &tabs, entry)
7110 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7114 undo_close_tab_save(struct tab *t)
7116 int m, n;
7117 const gchar *uri;
7118 struct undo *u1, *u2;
7119 GList *items;
7120 WebKitWebHistoryItem *item;
7122 if ((uri = get_uri(t->wv)) == NULL)
7123 return (1);
7125 u1 = g_malloc0(sizeof(struct undo));
7126 u1->uri = g_strdup(uri);
7128 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7130 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7131 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7132 u1->back = n;
7134 /* forward history */
7135 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7137 while (items) {
7138 item = items->data;
7139 u1->history = g_list_prepend(u1->history,
7140 webkit_web_history_item_copy(item));
7141 items = g_list_next(items);
7144 /* current item */
7145 if (m) {
7146 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7147 u1->history = g_list_prepend(u1->history,
7148 webkit_web_history_item_copy(item));
7151 /* back history */
7152 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7154 while (items) {
7155 item = items->data;
7156 u1->history = g_list_prepend(u1->history,
7157 webkit_web_history_item_copy(item));
7158 items = g_list_next(items);
7161 TAILQ_INSERT_HEAD(&undos, u1, entry);
7163 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7164 u2 = TAILQ_LAST(&undos, undo_tailq);
7165 TAILQ_REMOVE(&undos, u2, entry);
7166 g_free(u2->uri);
7167 g_list_free(u2->history);
7168 g_free(u2);
7169 } else
7170 undo_count++;
7172 return (0);
7175 void
7176 delete_tab(struct tab *t)
7178 struct karg a;
7180 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7182 if (t == NULL)
7183 return;
7185 TAILQ_REMOVE(&tabs, t, entry);
7187 /* halt all webkit activity */
7188 abort_favicon_download(t);
7189 webkit_web_view_stop_loading(t->wv);
7190 undo_close_tab_save(t);
7192 if (browser_mode == XT_BM_KIOSK) {
7193 gtk_widget_destroy(t->uri_entry);
7194 gtk_widget_destroy(t->stop);
7195 gtk_widget_destroy(t->js_toggle);
7199 gtk_widget_destroy(t->vbox);
7200 g_free(t->user_agent);
7201 g_free(t->stylesheet);
7202 g_free(t);
7204 recalc_tabs();
7205 if (TAILQ_EMPTY(&tabs)) {
7206 if (browser_mode == XT_BM_KIOSK)
7207 create_new_tab(home, NULL, 1);
7208 else
7209 create_new_tab(NULL, NULL, 1);
7212 /* recreate session */
7213 if (session_autosave) {
7214 a.s = NULL;
7215 save_tabs(t, &a);
7219 void
7220 adjustfont_webkit(struct tab *t, int adjust)
7222 gfloat zoom;
7224 if (t == NULL) {
7225 show_oops_s("adjustfont_webkit invalid parameters");
7226 return;
7229 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7230 if (adjust == XT_FONT_SET) {
7231 t->font_size = default_font_size;
7232 zoom = default_zoom_level;
7233 t->font_size += adjust;
7234 g_object_set(G_OBJECT(t->settings), "default-font-size",
7235 t->font_size, (char *)NULL);
7236 g_object_get(G_OBJECT(t->settings), "default-font-size",
7237 &t->font_size, (char *)NULL);
7238 } else {
7239 t->font_size += adjust;
7240 zoom += adjust/25.0;
7241 if (zoom < 0.0) {
7242 zoom = 0.04;
7245 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7246 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7249 void
7250 append_tab(struct tab *t)
7252 if (t == NULL)
7253 return;
7255 TAILQ_INSERT_TAIL(&tabs, t, entry);
7256 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7259 struct tab *
7260 create_new_tab(char *title, struct undo *u, int focus)
7262 struct tab *t, *tt;
7263 int load = 1, id, notfound;
7264 GtkWidget *b, *bb;
7265 WebKitWebHistoryItem *item;
7266 GList *items;
7267 GdkColor color;
7269 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7271 if (tabless && !TAILQ_EMPTY(&tabs)) {
7272 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7273 return (NULL);
7276 t = g_malloc0(sizeof *t);
7278 if (title == NULL) {
7279 title = "(untitled)";
7280 load = 0;
7283 t->vbox = gtk_vbox_new(FALSE, 0);
7285 /* label + button for tab */
7286 b = gtk_hbox_new(FALSE, 0);
7287 t->tab_content = b;
7289 #if GTK_CHECK_VERSION(2, 20, 0)
7290 t->spinner = gtk_spinner_new ();
7291 #endif
7292 t->label = gtk_label_new(title);
7293 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7294 gtk_widget_set_size_request(t->label, 100, 0);
7295 gtk_widget_set_size_request(b, 130, 0);
7297 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7298 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7299 #if GTK_CHECK_VERSION(2, 20, 0)
7300 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7301 #endif
7303 /* toolbar */
7304 if (browser_mode == XT_BM_KIOSK)
7305 t->toolbar = create_kiosk_toolbar(t);
7306 else
7307 t->toolbar = create_toolbar(t);
7309 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7311 /* browser */
7312 t->browser_win = create_browser(t);
7313 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7315 /* oops message for user feedback */
7316 t->oops = gtk_entry_new();
7317 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7318 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7319 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7320 gdk_color_parse(XT_COLOR_RED, &color);
7321 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7322 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7324 /* command entry */
7325 t->cmd = gtk_entry_new();
7326 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7327 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7328 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7330 /* status bar */
7331 t->statusbar = gtk_entry_new();
7332 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7333 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7334 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7335 gdk_color_parse(XT_COLOR_BLACK, &color);
7336 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7337 gdk_color_parse(XT_COLOR_WHITE, &color);
7338 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7339 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7341 /* xtp meaning is normal by default */
7342 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7344 /* set empty favicon */
7345 xt_icon_from_name(t, "text-html");
7347 /* and show it all */
7348 gtk_widget_show_all(b);
7349 gtk_widget_show_all(t->vbox);
7351 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7352 append_tab(t);
7353 else {
7354 notfound = 1;
7355 id = gtk_notebook_get_current_page(notebook);
7356 TAILQ_FOREACH(tt, &tabs, entry) {
7357 if (tt->tab_id == id) {
7358 notfound = 0;
7359 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7360 gtk_notebook_insert_page(notebook, t->vbox, b,
7361 id + 1);
7362 recalc_tabs();
7363 break;
7366 if (notfound)
7367 append_tab(t);
7370 #if GTK_CHECK_VERSION(2, 20, 0)
7371 /* turn spinner off if we are a new tab without uri */
7372 if (!load) {
7373 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7374 gtk_widget_hide(t->spinner);
7376 #endif
7377 /* make notebook tabs reorderable */
7378 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7380 g_object_connect(G_OBJECT(t->cmd),
7381 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7382 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7383 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7384 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7385 (char *)NULL);
7387 /* reuse wv_button_cb to hide oops */
7388 g_object_connect(G_OBJECT(t->oops),
7389 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7390 (char *)NULL);
7392 g_object_connect(G_OBJECT(t->wv),
7393 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7394 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7395 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7396 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7397 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7398 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7399 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7400 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7401 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7402 "signal::event", G_CALLBACK(webview_event_cb), t,
7403 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7404 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7405 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7406 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7407 #endif
7408 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7409 (char *)NULL);
7410 g_signal_connect(t->wv,
7411 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7412 g_signal_connect(t->wv,
7413 "notify::title", G_CALLBACK(notify_title_cb), t);
7415 /* hijack the unused keys as if we were the browser */
7416 g_object_connect(G_OBJECT(t->toolbar),
7417 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7418 (char *)NULL);
7420 g_signal_connect(G_OBJECT(bb), "button_press_event",
7421 G_CALLBACK(tab_close_cb), t);
7423 /* hide stuff */
7424 hide_cmd(t);
7425 hide_oops(t);
7426 url_set_visibility();
7427 statusbar_set_visibility();
7429 if (focus) {
7430 gtk_notebook_set_current_page(notebook, t->tab_id);
7431 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7432 t->tab_id);
7435 if (load) {
7436 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7437 load_uri(t, title);
7438 } else {
7439 if (show_url == 1)
7440 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7441 else
7442 focus_webview(t);
7445 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7446 /* restore the tab's history */
7447 if (u && u->history) {
7448 items = u->history;
7449 while (items) {
7450 item = items->data;
7451 webkit_web_back_forward_list_add_item(t->bfl, item);
7452 items = g_list_next(items);
7455 item = g_list_nth_data(u->history, u->back);
7456 if (item)
7457 webkit_web_view_go_to_back_forward_item(t->wv, item);
7459 g_list_free(items);
7460 g_list_free(u->history);
7461 } else
7462 webkit_web_back_forward_list_clear(t->bfl);
7464 return (t);
7467 void
7468 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7469 gpointer *udata)
7471 struct tab *t;
7472 const gchar *uri;
7474 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7476 TAILQ_FOREACH(t, &tabs, entry) {
7477 if (t->tab_id == pn) {
7478 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7479 "%d\n", pn);
7481 uri = webkit_web_view_get_title(t->wv);
7482 if (uri == NULL)
7483 uri = XT_NAME;
7484 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7486 hide_cmd(t);
7487 hide_oops(t);
7489 if (t->focus_wv)
7490 focus_webview(t);
7495 void
7496 menuitem_response(struct tab *t)
7498 gtk_notebook_set_current_page(notebook, t->tab_id);
7501 gboolean
7502 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7504 GtkWidget *menu, *menu_items;
7505 GdkEventButton *bevent;
7506 const gchar *uri;
7507 struct tab *ti;
7509 if (event->type == GDK_BUTTON_PRESS) {
7510 bevent = (GdkEventButton *) event;
7511 menu = gtk_menu_new();
7513 TAILQ_FOREACH(ti, &tabs, entry) {
7514 if ((uri = get_uri(ti->wv)) == NULL)
7515 /* XXX make sure there is something to print */
7516 /* XXX add gui pages in here to look purdy */
7517 uri = "(untitled)";
7518 menu_items = gtk_menu_item_new_with_label(uri);
7519 gtk_menu_append(GTK_MENU (menu), menu_items);
7520 gtk_widget_show(menu_items);
7522 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7523 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7524 (gpointer)ti);
7527 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7528 bevent->button, bevent->time);
7530 /* unref object so it'll free itself when popped down */
7531 g_object_ref_sink(menu);
7532 g_object_unref(menu);
7534 return (TRUE /* eat event */);
7537 return (FALSE /* propagate */);
7541 icon_size_map(int icon_size)
7543 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7544 icon_size > GTK_ICON_SIZE_DIALOG)
7545 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7547 return (icon_size);
7550 GtkWidget *
7551 create_button(char *name, char *stockid, int size)
7553 GtkWidget *button, *image;
7554 gchar *rcstring;
7555 int gtk_icon_size;
7557 rcstring = g_strdup_printf(
7558 "style \"%s-style\"\n"
7559 "{\n"
7560 " GtkWidget::focus-padding = 0\n"
7561 " GtkWidget::focus-line-width = 0\n"
7562 " xthickness = 0\n"
7563 " ythickness = 0\n"
7564 "}\n"
7565 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7566 gtk_rc_parse_string(rcstring);
7567 g_free(rcstring);
7568 button = gtk_button_new();
7569 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7570 gtk_icon_size = icon_size_map(size ? size : icon_size);
7572 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7573 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7574 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7575 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7576 gtk_widget_set_name(button, name);
7577 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7579 return (button);
7582 void
7583 button_set_stockid(GtkWidget *button, char *stockid)
7585 GtkWidget *image;
7587 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7588 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7589 gtk_button_set_image(GTK_BUTTON(button), image);
7592 void
7593 create_canvas(void)
7595 GtkWidget *vbox;
7596 GList *l = NULL;
7597 GdkPixbuf *pb;
7598 char file[PATH_MAX];
7599 int i;
7601 vbox = gtk_vbox_new(FALSE, 0);
7602 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7603 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7604 gtk_notebook_set_tab_hborder(notebook, 0);
7605 gtk_notebook_set_tab_vborder(notebook, 0);
7606 gtk_notebook_set_scrollable(notebook, TRUE);
7607 notebook_tab_set_visibility(notebook);
7608 gtk_notebook_set_show_border(notebook, FALSE);
7609 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7611 abtn = gtk_button_new();
7612 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7613 gtk_widget_set_size_request(arrow, -1, -1);
7614 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7615 gtk_widget_set_size_request(abtn, -1, 20);
7616 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7618 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7619 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7620 gtk_widget_set_size_request(vbox, -1, -1);
7622 g_object_connect(G_OBJECT(notebook),
7623 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7624 (char *)NULL);
7625 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7626 G_CALLBACK(arrow_cb), NULL);
7628 main_window = create_window();
7629 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7630 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7632 /* icons */
7633 for (i = 0; i < LENGTH(icons); i++) {
7634 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7635 pb = gdk_pixbuf_new_from_file(file, NULL);
7636 l = g_list_append(l, pb);
7638 gtk_window_set_default_icon_list(l);
7640 gtk_widget_show_all(abtn);
7641 gtk_widget_show_all(main_window);
7644 void
7645 set_hook(void **hook, char *name)
7647 if (hook == NULL)
7648 errx(1, "set_hook");
7650 if (*hook == NULL) {
7651 *hook = dlsym(RTLD_NEXT, name);
7652 if (*hook == NULL)
7653 errx(1, "can't hook %s", name);
7657 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7658 gboolean
7659 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7661 g_return_val_if_fail(cookie1, FALSE);
7662 g_return_val_if_fail(cookie2, FALSE);
7664 return (!strcmp (cookie1->name, cookie2->name) &&
7665 !strcmp (cookie1->value, cookie2->value) &&
7666 !strcmp (cookie1->path, cookie2->path) &&
7667 !strcmp (cookie1->domain, cookie2->domain));
7670 void
7671 transfer_cookies(void)
7673 GSList *cf;
7674 SoupCookie *sc, *pc;
7676 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7678 for (;cf; cf = cf->next) {
7679 pc = cf->data;
7680 sc = soup_cookie_copy(pc);
7681 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7684 soup_cookies_free(cf);
7687 void
7688 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7690 GSList *cf;
7691 SoupCookie *ci;
7693 print_cookie("soup_cookie_jar_delete_cookie", c);
7695 if (cookies_enabled == 0)
7696 return;
7698 if (jar == NULL || c == NULL)
7699 return;
7701 /* find and remove from persistent jar */
7702 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7704 for (;cf; cf = cf->next) {
7705 ci = cf->data;
7706 if (soup_cookie_equal(ci, c)) {
7707 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7708 break;
7712 soup_cookies_free(cf);
7714 /* delete from session jar */
7715 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7718 void
7719 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7721 struct domain *d = NULL;
7722 SoupCookie *c;
7723 FILE *r_cookie_f;
7725 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7726 jar, p_cookiejar, s_cookiejar);
7728 if (cookies_enabled == 0)
7729 return;
7731 /* see if we are up and running */
7732 if (p_cookiejar == NULL) {
7733 _soup_cookie_jar_add_cookie(jar, cookie);
7734 return;
7736 /* disallow p_cookiejar adds, shouldn't happen */
7737 if (jar == p_cookiejar)
7738 return;
7740 /* sanity */
7741 if (jar == NULL || cookie == NULL)
7742 return;
7744 if (enable_cookie_whitelist &&
7745 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7746 blocked_cookies++;
7747 DNPRINTF(XT_D_COOKIE,
7748 "soup_cookie_jar_add_cookie: reject %s\n",
7749 cookie->domain);
7750 if (save_rejected_cookies) {
7751 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7752 show_oops_s("can't open reject cookie file");
7753 return;
7755 fseek(r_cookie_f, 0, SEEK_END);
7756 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7757 cookie->http_only ? "#HttpOnly_" : "",
7758 cookie->domain,
7759 *cookie->domain == '.' ? "TRUE" : "FALSE",
7760 cookie->path,
7761 cookie->secure ? "TRUE" : "FALSE",
7762 cookie->expires ?
7763 (gulong)soup_date_to_time_t(cookie->expires) :
7765 cookie->name,
7766 cookie->value);
7767 fflush(r_cookie_f);
7768 fclose(r_cookie_f);
7770 if (!allow_volatile_cookies)
7771 return;
7774 if (cookie->expires == NULL && session_timeout) {
7775 soup_cookie_set_expires(cookie,
7776 soup_date_new_from_now(session_timeout));
7777 print_cookie("modified add cookie", cookie);
7780 /* see if we are white listed for persistence */
7781 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7782 /* add to persistent jar */
7783 c = soup_cookie_copy(cookie);
7784 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7785 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7788 /* add to session jar */
7789 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7790 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7793 void
7794 setup_cookies(void)
7796 char file[PATH_MAX];
7798 set_hook((void *)&_soup_cookie_jar_add_cookie,
7799 "soup_cookie_jar_add_cookie");
7800 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7801 "soup_cookie_jar_delete_cookie");
7803 if (cookies_enabled == 0)
7804 return;
7807 * the following code is intricate due to overriding several libsoup
7808 * functions.
7809 * do not alter order of these operations.
7812 /* rejected cookies */
7813 if (save_rejected_cookies)
7814 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7816 /* persistent cookies */
7817 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7818 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7820 /* session cookies */
7821 s_cookiejar = soup_cookie_jar_new();
7822 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7823 cookie_policy, (void *)NULL);
7824 transfer_cookies();
7826 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7829 void
7830 setup_proxy(char *uri)
7832 if (proxy_uri) {
7833 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7834 soup_uri_free(proxy_uri);
7835 proxy_uri = NULL;
7837 if (http_proxy) {
7838 if (http_proxy != uri) {
7839 g_free(http_proxy);
7840 http_proxy = NULL;
7844 if (uri) {
7845 http_proxy = g_strdup(uri);
7846 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7847 proxy_uri = soup_uri_new(http_proxy);
7848 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7853 send_cmd_to_socket(char *cmd)
7855 int s, len, rv = 1;
7856 struct sockaddr_un sa;
7858 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7859 warnx("%s: socket", __func__);
7860 return (rv);
7863 sa.sun_family = AF_UNIX;
7864 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7865 work_dir, XT_SOCKET_FILE);
7866 len = SUN_LEN(&sa);
7868 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7869 warnx("%s: connect", __func__);
7870 goto done;
7873 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7874 warnx("%s: send", __func__);
7875 goto done;
7878 rv = 0;
7879 done:
7880 close(s);
7881 return (rv);
7884 void
7885 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7887 int s, n;
7888 char str[XT_MAX_URL_LENGTH];
7889 socklen_t t = sizeof(struct sockaddr_un);
7890 struct sockaddr_un sa;
7891 struct passwd *p;
7892 uid_t uid;
7893 gid_t gid;
7894 struct tab *tt;
7896 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7897 warn("accept");
7898 return;
7901 if (getpeereid(s, &uid, &gid) == -1) {
7902 warn("getpeereid");
7903 return;
7905 if (uid != getuid() || gid != getgid()) {
7906 warnx("unauthorized user");
7907 return;
7910 p = getpwuid(uid);
7911 if (p == NULL) {
7912 warnx("not a valid user");
7913 return;
7916 n = recv(s, str, sizeof(str), 0);
7917 if (n <= 0)
7918 return;
7920 tt = TAILQ_LAST(&tabs, tab_list);
7921 cmd_execute(tt, str);
7925 is_running(void)
7927 int s, len, rv = 1;
7928 struct sockaddr_un sa;
7930 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7931 warn("is_running: socket");
7932 return (-1);
7935 sa.sun_family = AF_UNIX;
7936 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7937 work_dir, XT_SOCKET_FILE);
7938 len = SUN_LEN(&sa);
7940 /* connect to see if there is a listener */
7941 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7942 rv = 0; /* not running */
7943 else
7944 rv = 1; /* already running */
7946 close(s);
7948 return (rv);
7952 build_socket(void)
7954 int s, len;
7955 struct sockaddr_un sa;
7957 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7958 warn("build_socket: socket");
7959 return (-1);
7962 sa.sun_family = AF_UNIX;
7963 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7964 work_dir, XT_SOCKET_FILE);
7965 len = SUN_LEN(&sa);
7967 /* connect to see if there is a listener */
7968 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7969 /* no listener so we will */
7970 unlink(sa.sun_path);
7972 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7973 warn("build_socket: bind");
7974 goto done;
7977 if (listen(s, 1) == -1) {
7978 warn("build_socket: listen");
7979 goto done;
7982 return (s);
7985 done:
7986 close(s);
7987 return (-1);
7990 static gboolean
7991 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7992 GtkTreeIter *iter, struct tab *t)
7994 gchar *value;
7996 gtk_tree_model_get(model, iter, 0, &value, -1);
7997 load_uri(t, value);
7999 return (FALSE);
8002 void
8003 completion_add_uri(const gchar *uri)
8005 GtkTreeIter iter;
8007 /* add uri to list_store */
8008 gtk_list_store_append(completion_model, &iter);
8009 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8012 gboolean
8013 completion_match(GtkEntryCompletion *completion, const gchar *key,
8014 GtkTreeIter *iter, gpointer user_data)
8016 gchar *value;
8017 gboolean match = FALSE;
8019 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8020 -1);
8022 if (value == NULL)
8023 return FALSE;
8025 match = match_uri(value, key);
8027 g_free(value);
8028 return (match);
8031 void
8032 completion_add(struct tab *t)
8034 /* enable completion for tab */
8035 t->completion = gtk_entry_completion_new();
8036 gtk_entry_completion_set_text_column(t->completion, 0);
8037 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8038 gtk_entry_completion_set_model(t->completion,
8039 GTK_TREE_MODEL(completion_model));
8040 gtk_entry_completion_set_match_func(t->completion, completion_match,
8041 NULL, NULL);
8042 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8043 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8044 G_CALLBACK(completion_select_cb), t);
8047 void
8048 usage(void)
8050 fprintf(stderr,
8051 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8052 exit(0);
8056 main(int argc, char *argv[])
8058 struct stat sb;
8059 int c, s, optn = 0, opte = 0, focus = 1;
8060 char conf[PATH_MAX] = { '\0' };
8061 char file[PATH_MAX];
8062 char *env_proxy = NULL;
8063 FILE *f = NULL;
8064 struct karg a;
8065 struct sigaction sact;
8066 gchar *priority = g_strdup("NORMAL");
8068 start_argv = argv;
8070 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8072 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8073 switch (c) {
8074 case 'S':
8075 show_url = 0;
8076 break;
8077 case 'T':
8078 show_tabs = 0;
8079 break;
8080 case 'V':
8081 errx(0 , "Version: %s", version);
8082 break;
8083 case 'f':
8084 strlcpy(conf, optarg, sizeof(conf));
8085 break;
8086 case 's':
8087 strlcpy(named_session, optarg, sizeof(named_session));
8088 break;
8089 case 't':
8090 tabless = 1;
8091 break;
8092 case 'n':
8093 optn = 1;
8094 break;
8095 case 'e':
8096 opte = 1;
8097 break;
8098 default:
8099 usage();
8100 /* NOTREACHED */
8103 argc -= optind;
8104 argv += optind;
8106 RB_INIT(&hl);
8107 RB_INIT(&js_wl);
8108 RB_INIT(&downloads);
8110 TAILQ_INIT(&tabs);
8111 TAILQ_INIT(&mtl);
8112 TAILQ_INIT(&aliases);
8113 TAILQ_INIT(&undos);
8114 TAILQ_INIT(&kbl);
8116 init_keybindings();
8118 gnutls_global_init();
8120 /* generate session keys for xtp pages */
8121 generate_xtp_session_key(&dl_session_key);
8122 generate_xtp_session_key(&hl_session_key);
8123 generate_xtp_session_key(&cl_session_key);
8124 generate_xtp_session_key(&fl_session_key);
8126 /* prepare gtk */
8127 gtk_init(&argc, &argv);
8128 if (!g_thread_supported())
8129 g_thread_init(NULL);
8131 /* signals */
8132 bzero(&sact, sizeof(sact));
8133 sigemptyset(&sact.sa_mask);
8134 sact.sa_handler = sigchild;
8135 sact.sa_flags = SA_NOCLDSTOP;
8136 sigaction(SIGCHLD, &sact, NULL);
8138 /* set download dir */
8139 pwd = getpwuid(getuid());
8140 if (pwd == NULL)
8141 errx(1, "invalid user %d", getuid());
8142 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8144 /* set default string settings */
8145 home = g_strdup("http://www.peereboom.us");
8146 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8147 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8148 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8150 /* read config file */
8151 if (strlen(conf) == 0)
8152 snprintf(conf, sizeof conf, "%s/.%s",
8153 pwd->pw_dir, XT_CONF_FILE);
8154 config_parse(conf, 0);
8156 /* working directory */
8157 if (strlen(work_dir) == 0)
8158 snprintf(work_dir, sizeof work_dir, "%s/%s",
8159 pwd->pw_dir, XT_DIR);
8160 if (stat(work_dir, &sb)) {
8161 if (mkdir(work_dir, S_IRWXU) == -1)
8162 err(1, "mkdir work_dir");
8163 if (stat(work_dir, &sb))
8164 err(1, "stat work_dir");
8166 if (S_ISDIR(sb.st_mode) == 0)
8167 errx(1, "%s not a dir", work_dir);
8168 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8169 warnx("fixing invalid permissions on %s", work_dir);
8170 if (chmod(work_dir, S_IRWXU) == -1)
8171 err(1, "chmod");
8174 /* icon cache dir */
8175 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8176 if (stat(cache_dir, &sb)) {
8177 if (mkdir(cache_dir, S_IRWXU) == -1)
8178 err(1, "mkdir cache_dir");
8179 if (stat(cache_dir, &sb))
8180 err(1, "stat cache_dir");
8182 if (S_ISDIR(sb.st_mode) == 0)
8183 errx(1, "%s not a dir", cache_dir);
8184 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8185 warnx("fixing invalid permissions on %s", cache_dir);
8186 if (chmod(cache_dir, S_IRWXU) == -1)
8187 err(1, "chmod");
8190 /* certs dir */
8191 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8192 if (stat(certs_dir, &sb)) {
8193 if (mkdir(certs_dir, S_IRWXU) == -1)
8194 err(1, "mkdir certs_dir");
8195 if (stat(certs_dir, &sb))
8196 err(1, "stat certs_dir");
8198 if (S_ISDIR(sb.st_mode) == 0)
8199 errx(1, "%s not a dir", certs_dir);
8200 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8201 warnx("fixing invalid permissions on %s", certs_dir);
8202 if (chmod(certs_dir, S_IRWXU) == -1)
8203 err(1, "chmod");
8206 /* sessions dir */
8207 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8208 work_dir, XT_SESSIONS_DIR);
8209 if (stat(sessions_dir, &sb)) {
8210 if (mkdir(sessions_dir, S_IRWXU) == -1)
8211 err(1, "mkdir sessions_dir");
8212 if (stat(sessions_dir, &sb))
8213 err(1, "stat sessions_dir");
8215 if (S_ISDIR(sb.st_mode) == 0)
8216 errx(1, "%s not a dir", sessions_dir);
8217 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8218 warnx("fixing invalid permissions on %s", sessions_dir);
8219 if (chmod(sessions_dir, S_IRWXU) == -1)
8220 err(1, "chmod");
8222 /* runtime settings that can override config file */
8223 if (runtime_settings[0] != '\0')
8224 config_parse(runtime_settings, 1);
8226 /* download dir */
8227 if (!strcmp(download_dir, pwd->pw_dir))
8228 strlcat(download_dir, "/downloads", sizeof download_dir);
8229 if (stat(download_dir, &sb)) {
8230 if (mkdir(download_dir, S_IRWXU) == -1)
8231 err(1, "mkdir download_dir");
8232 if (stat(download_dir, &sb))
8233 err(1, "stat download_dir");
8235 if (S_ISDIR(sb.st_mode) == 0)
8236 errx(1, "%s not a dir", download_dir);
8237 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8238 warnx("fixing invalid permissions on %s", download_dir);
8239 if (chmod(download_dir, S_IRWXU) == -1)
8240 err(1, "chmod");
8243 /* favorites file */
8244 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8245 if (stat(file, &sb)) {
8246 warnx("favorites file doesn't exist, creating it");
8247 if ((f = fopen(file, "w")) == NULL)
8248 err(1, "favorites");
8249 fclose(f);
8252 /* cookies */
8253 session = webkit_get_default_session();
8254 /* XXX ssl-priority property not quite available yet */
8255 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8256 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8257 (char *)NULL);
8258 else
8259 warnx("session does not have \"ssl-priority\" property");
8260 setup_cookies();
8262 /* certs */
8263 if (ssl_ca_file) {
8264 if (stat(ssl_ca_file, &sb)) {
8265 warn("no CA file: %s", ssl_ca_file);
8266 g_free(ssl_ca_file);
8267 ssl_ca_file = NULL;
8268 } else
8269 g_object_set(session,
8270 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8271 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8272 (void *)NULL);
8275 /* proxy */
8276 env_proxy = getenv("http_proxy");
8277 if (env_proxy)
8278 setup_proxy(env_proxy);
8279 else
8280 setup_proxy(http_proxy);
8282 if (opte) {
8283 send_cmd_to_socket(argv[0]);
8284 exit(0);
8287 /* set some connection parameters */
8288 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8289 g_object_set(session, "max-conns-per-host", max_host_connections,
8290 (char *)NULL);
8292 /* see if there is already an xxxterm running */
8293 if (single_instance && is_running()) {
8294 optn = 1;
8295 warnx("already running");
8298 char *cmd = NULL;
8299 if (optn) {
8300 while (argc) {
8301 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8302 send_cmd_to_socket(cmd);
8303 if (cmd)
8304 g_free(cmd);
8306 argc--;
8307 argv++;
8309 exit(0);
8312 /* uri completion */
8313 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8315 /* go graphical */
8316 create_canvas();
8318 if (save_global_history)
8319 restore_global_history();
8321 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8322 restore_saved_tabs();
8323 else {
8324 a.s = named_session;
8325 a.i = XT_SES_DONOTHING;
8326 open_tabs(NULL, &a);
8329 while (argc) {
8330 create_new_tab(argv[0], NULL, focus);
8331 focus = 0;
8333 argc--;
8334 argv++;
8337 if (TAILQ_EMPTY(&tabs))
8338 create_new_tab(home, NULL, 1);
8340 if (enable_socket)
8341 if ((s = build_socket()) != -1)
8342 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8344 gtk_main();
8346 gnutls_global_deinit();
8348 return (0);