make labels that hover over buttons human readable.
[xxxterm.git] / xxxterm.c
blobe930db48f3c433aa751687cc06b985ae49a01b33
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 } else
1001 return (1);
1003 return (0);
1006 char *
1007 get_browser_mode(struct settings *s)
1009 char *r = NULL;
1011 if (browser_mode == XT_BM_WHITELIST)
1012 r = g_strdup("whitelist");
1013 else if (browser_mode == XT_BM_NORMAL)
1014 r = g_strdup("normal");
1015 else if (browser_mode == XT_BM_KIOSK)
1016 r = g_strdup("kiosk");
1017 else
1018 return (NULL);
1020 return (r);
1024 set_cookie_policy(struct settings *s, char *val)
1026 if (!strcmp(val, "no3rdparty"))
1027 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1028 else if (!strcmp(val, "accept"))
1029 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1030 else if (!strcmp(val, "reject"))
1031 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1032 else
1033 return (1);
1035 return (0);
1038 char *
1039 get_cookie_policy(struct settings *s)
1041 char *r = NULL;
1043 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1044 r = g_strdup("no3rdparty");
1045 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1046 r = g_strdup("accept");
1047 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1048 r = g_strdup("reject");
1049 else
1050 return (NULL);
1052 return (r);
1055 char *
1056 get_download_dir(struct settings *s)
1058 if (download_dir[0] == '\0')
1059 return (0);
1060 return (g_strdup(download_dir));
1064 set_download_dir(struct settings *s, char *val)
1066 if (val[0] == '~')
1067 snprintf(download_dir, sizeof download_dir, "%s/%s",
1068 pwd->pw_dir, &val[1]);
1069 else
1070 strlcpy(download_dir, val, sizeof download_dir);
1072 return (0);
1076 * Session IDs.
1077 * We use these to prevent people putting xxxt:// URLs on
1078 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1080 #define XT_XTP_SES_KEY_SZ 8
1081 #define XT_XTP_SES_KEY_HEX_FMT \
1082 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1083 char *dl_session_key; /* downloads */
1084 char *hl_session_key; /* history list */
1085 char *cl_session_key; /* cookie list */
1086 char *fl_session_key; /* favorites list */
1088 char work_dir[PATH_MAX];
1089 char certs_dir[PATH_MAX];
1090 char cache_dir[PATH_MAX];
1091 char sessions_dir[PATH_MAX];
1092 char cookie_file[PATH_MAX];
1093 SoupURI *proxy_uri = NULL;
1094 SoupSession *session;
1095 SoupCookieJar *s_cookiejar;
1096 SoupCookieJar *p_cookiejar;
1097 char rc_fname[PATH_MAX];
1099 struct mime_type_list mtl;
1100 struct alias_list aliases;
1102 /* protos */
1103 struct tab *create_new_tab(char *, struct undo *, int);
1104 void delete_tab(struct tab *);
1105 void adjustfont_webkit(struct tab *, int);
1106 int run_script(struct tab *, char *);
1107 int download_rb_cmp(struct download *, struct download *);
1108 gboolean cmd_execute(struct tab *t, char *str);
1111 history_rb_cmp(struct history *h1, struct history *h2)
1113 return (strcmp(h1->uri, h2->uri));
1115 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1118 domain_rb_cmp(struct domain *d1, struct domain *d2)
1120 return (strcmp(d1->d, d2->d));
1122 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1124 char *
1125 get_work_dir(struct settings *s)
1127 if (work_dir[0] == '\0')
1128 return (0);
1129 return (g_strdup(work_dir));
1133 set_work_dir(struct settings *s, char *val)
1135 if (val[0] == '~')
1136 snprintf(work_dir, sizeof work_dir, "%s/%s",
1137 pwd->pw_dir, &val[1]);
1138 else
1139 strlcpy(work_dir, val, sizeof work_dir);
1141 return (0);
1145 * generate a session key to secure xtp commands.
1146 * pass in a ptr to the key in question and it will
1147 * be modified in place.
1149 void
1150 generate_xtp_session_key(char **key)
1152 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1154 /* free old key */
1155 if (*key)
1156 g_free(*key);
1158 /* make a new one */
1159 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1160 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1161 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1162 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1164 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1168 * validate a xtp session key.
1169 * return 1 if OK
1172 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1174 if (strcmp(trusted, untrusted) != 0) {
1175 show_oops(t, "%s: xtp session key mismatch possible spoof",
1176 __func__);
1177 return (0);
1180 return (1);
1184 download_rb_cmp(struct download *e1, struct download *e2)
1186 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1188 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1190 struct valid_url_types {
1191 char *type;
1192 } vut[] = {
1193 { "http://" },
1194 { "https://" },
1195 { "ftp://" },
1196 { "file://" },
1197 { XT_XTP_STR },
1201 valid_url_type(char *url)
1203 int i;
1205 for (i = 0; i < LENGTH(vut); i++)
1206 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1207 return (0);
1209 return (1);
1212 void
1213 print_cookie(char *msg, SoupCookie *c)
1215 if (c == NULL)
1216 return;
1218 if (msg)
1219 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1220 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1221 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1222 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1223 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1224 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1225 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1226 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1227 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1228 DNPRINTF(XT_D_COOKIE, "====================================\n");
1231 void
1232 walk_alias(struct settings *s,
1233 void (*cb)(struct settings *, char *, void *), void *cb_args)
1235 struct alias *a;
1236 char *str;
1238 if (s == NULL || cb == NULL) {
1239 show_oops_s("walk_alias invalid parameters");
1240 return;
1243 TAILQ_FOREACH(a, &aliases, entry) {
1244 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1245 cb(s, str, cb_args);
1246 g_free(str);
1250 char *
1251 match_alias(char *url_in)
1253 struct alias *a;
1254 char *arg;
1255 char *url_out = NULL, *search, *enc_arg;
1257 search = g_strdup(url_in);
1258 arg = search;
1259 if (strsep(&arg, " \t") == NULL) {
1260 show_oops_s("match_alias: NULL URL");
1261 goto done;
1264 TAILQ_FOREACH(a, &aliases, entry) {
1265 if (!strcmp(search, a->a_name))
1266 break;
1269 if (a != NULL) {
1270 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1271 a->a_name);
1272 if (arg != NULL) {
1273 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1274 url_out = g_strdup_printf(a->a_uri, enc_arg);
1275 g_free(enc_arg);
1276 } else
1277 url_out = g_strdup(a->a_uri);
1279 done:
1280 g_free(search);
1281 return (url_out);
1284 char *
1285 guess_url_type(char *url_in)
1287 struct stat sb;
1288 char *url_out = NULL, *enc_search = NULL;
1290 url_out = match_alias(url_in);
1291 if (url_out != NULL)
1292 return (url_out);
1294 if (guess_search) {
1296 * If there is no dot nor slash in the string and it isn't a
1297 * path to a local file and doesn't resolves to an IP, assume
1298 * that the user wants to search for the string.
1301 if (strchr(url_in, '.') == NULL &&
1302 strchr(url_in, '/') == NULL &&
1303 stat(url_in, &sb) != 0 &&
1304 gethostbyname(url_in) == NULL) {
1306 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1307 url_out = g_strdup_printf(search_string, enc_search);
1308 g_free(enc_search);
1309 return (url_out);
1313 /* XXX not sure about this heuristic */
1314 if (stat(url_in, &sb) == 0)
1315 url_out = g_strdup_printf("file://%s", url_in);
1316 else
1317 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1319 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1321 return (url_out);
1324 void
1325 load_uri(struct tab *t, gchar *uri)
1327 struct karg args;
1328 gchar *newuri = NULL;
1329 int i;
1331 if (uri == NULL)
1332 return;
1334 /* Strip leading spaces. */
1335 while(*uri && isspace(*uri))
1336 uri++;
1338 if (strlen(uri) == 0) {
1339 blank(t, NULL);
1340 return;
1343 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1344 for (i = 0; i < LENGTH(about_list); i++)
1345 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1346 bzero(&args, sizeof args);
1347 about_list[i].func(t, &args);
1348 return;
1350 show_oops(t, "invalid about page");
1351 return;
1354 if (valid_url_type(uri)) {
1355 newuri = guess_url_type(uri);
1356 uri = newuri;
1359 set_status(t, (char *)uri, XT_STATUS_LOADING);
1360 webkit_web_view_load_uri(t->wv, uri);
1362 if (newuri)
1363 g_free(newuri);
1366 const gchar *
1367 get_uri(WebKitWebView *wv)
1369 WebKitWebFrame *frame;
1370 const gchar *uri;
1372 frame = webkit_web_view_get_main_frame(wv);
1373 uri = webkit_web_frame_get_uri(frame);
1375 if (uri && strlen(uri) > 0)
1376 return (uri);
1377 else
1378 return (NULL);
1382 add_alias(struct settings *s, char *line)
1384 char *l, *alias;
1385 struct alias *a = NULL;
1387 if (s == NULL || line == NULL) {
1388 show_oops_s("add_alias invalid parameters");
1389 return (1);
1392 l = line;
1393 a = g_malloc(sizeof(*a));
1395 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1396 show_oops_s("add_alias: incomplete alias definition");
1397 goto bad;
1399 if (strlen(alias) == 0 || strlen(l) == 0) {
1400 show_oops_s("add_alias: invalid alias definition");
1401 goto bad;
1404 a->a_name = g_strdup(alias);
1405 a->a_uri = g_strdup(l);
1407 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1409 TAILQ_INSERT_TAIL(&aliases, a, entry);
1411 return (0);
1412 bad:
1413 if (a)
1414 g_free(a);
1415 return (1);
1419 add_mime_type(struct settings *s, char *line)
1421 char *mime_type;
1422 char *l;
1423 struct mime_type *m = NULL;
1424 int downloadfirst = 0;
1426 /* XXX this could be smarter */
1428 if (line == NULL && strlen(line) == 0) {
1429 show_oops_s("add_mime_type invalid parameters");
1430 return (1);
1433 l = line;
1434 if (*l == '@') {
1435 downloadfirst = 1;
1436 l++;
1438 m = g_malloc(sizeof(*m));
1440 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1441 show_oops_s("add_mime_type: invalid mime_type");
1442 goto bad;
1444 if (mime_type[strlen(mime_type) - 1] == '*') {
1445 mime_type[strlen(mime_type) - 1] = '\0';
1446 m->mt_default = 1;
1447 } else
1448 m->mt_default = 0;
1450 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1451 show_oops_s("add_mime_type: invalid mime_type");
1452 goto bad;
1455 m->mt_type = g_strdup(mime_type);
1456 m->mt_action = g_strdup(l);
1457 m->mt_download = downloadfirst;
1459 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1460 m->mt_type, m->mt_action, m->mt_default);
1462 TAILQ_INSERT_TAIL(&mtl, m, entry);
1464 return (0);
1465 bad:
1466 if (m)
1467 g_free(m);
1468 return (1);
1471 struct mime_type *
1472 find_mime_type(char *mime_type)
1474 struct mime_type *m, *def = NULL, *rv = NULL;
1476 TAILQ_FOREACH(m, &mtl, entry) {
1477 if (m->mt_default &&
1478 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1479 def = m;
1481 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1482 rv = m;
1483 break;
1487 if (rv == NULL)
1488 rv = def;
1490 return (rv);
1493 void
1494 walk_mime_type(struct settings *s,
1495 void (*cb)(struct settings *, char *, void *), void *cb_args)
1497 struct mime_type *m;
1498 char *str;
1500 if (s == NULL || cb == NULL)
1501 show_oops_s("walk_mime_type invalid parameters");
1503 TAILQ_FOREACH(m, &mtl, entry) {
1504 str = g_strdup_printf("%s%s --> %s",
1505 m->mt_type,
1506 m->mt_default ? "*" : "",
1507 m->mt_action);
1508 cb(s, str, cb_args);
1509 g_free(str);
1513 void
1514 wl_add(char *str, struct domain_list *wl, int handy)
1516 struct domain *d;
1517 int add_dot = 0;
1519 if (str == NULL || wl == NULL)
1520 return;
1521 if (strlen(str) < 2)
1522 return;
1524 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1526 /* treat *.moo.com the same as .moo.com */
1527 if (str[0] == '*' && str[1] == '.')
1528 str = &str[1];
1529 else if (str[0] == '.')
1530 str = &str[0];
1531 else
1532 add_dot = 1;
1534 d = g_malloc(sizeof *d);
1535 if (add_dot)
1536 d->d = g_strdup_printf(".%s", str);
1537 else
1538 d->d = g_strdup(str);
1539 d->handy = handy;
1541 if (RB_INSERT(domain_list, wl, d))
1542 goto unwind;
1544 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1545 return;
1546 unwind:
1547 if (d) {
1548 if (d->d)
1549 g_free(d->d);
1550 g_free(d);
1555 add_cookie_wl(struct settings *s, char *entry)
1557 wl_add(entry, &c_wl, 1);
1558 return (0);
1561 void
1562 walk_cookie_wl(struct settings *s,
1563 void (*cb)(struct settings *, char *, void *), void *cb_args)
1565 struct domain *d;
1567 if (s == NULL || cb == NULL) {
1568 show_oops_s("walk_cookie_wl invalid parameters");
1569 return;
1572 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1573 cb(s, d->d, cb_args);
1576 void
1577 walk_js_wl(struct settings *s,
1578 void (*cb)(struct settings *, char *, void *), void *cb_args)
1580 struct domain *d;
1582 if (s == NULL || cb == NULL) {
1583 show_oops_s("walk_js_wl invalid parameters");
1584 return;
1587 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1588 cb(s, d->d, cb_args);
1592 add_js_wl(struct settings *s, char *entry)
1594 wl_add(entry, &js_wl, 1 /* persistent */);
1595 return (0);
1598 struct domain *
1599 wl_find(const gchar *search, struct domain_list *wl)
1601 int i;
1602 struct domain *d = NULL, dfind;
1603 gchar *s = NULL;
1605 if (search == NULL || wl == NULL)
1606 return (NULL);
1607 if (strlen(search) < 2)
1608 return (NULL);
1610 if (search[0] != '.')
1611 s = g_strdup_printf(".%s", search);
1612 else
1613 s = g_strdup(search);
1615 for (i = strlen(s) - 1; i >= 0; i--) {
1616 if (s[i] == '.') {
1617 dfind.d = &s[i];
1618 d = RB_FIND(domain_list, wl, &dfind);
1619 if (d)
1620 goto done;
1624 done:
1625 if (s)
1626 g_free(s);
1628 return (d);
1631 struct domain *
1632 wl_find_uri(const gchar *s, struct domain_list *wl)
1634 int i;
1635 char *ss;
1636 struct domain *r;
1638 if (s == NULL || wl == NULL)
1639 return (NULL);
1641 if (!strncmp(s, "http://", strlen("http://")))
1642 s = &s[strlen("http://")];
1643 else if (!strncmp(s, "https://", strlen("https://")))
1644 s = &s[strlen("https://")];
1646 if (strlen(s) < 2)
1647 return (NULL);
1649 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1650 /* chop string at first slash */
1651 if (s[i] == '/' || s[i] == '\0') {
1652 ss = g_strdup(s);
1653 ss[i] = '\0';
1654 r = wl_find(ss, wl);
1655 g_free(ss);
1656 return (r);
1659 return (NULL);
1662 char *
1663 get_toplevel_domain(char *domain)
1665 char *s;
1666 int found = 0;
1668 if (domain == NULL)
1669 return (NULL);
1670 if (strlen(domain) < 2)
1671 return (NULL);
1673 s = &domain[strlen(domain) - 1];
1674 while (s != domain) {
1675 if (*s == '.') {
1676 found++;
1677 if (found == 2)
1678 return (s);
1680 s--;
1683 if (found)
1684 return (domain);
1686 return (NULL);
1690 settings_add(char *var, char *val)
1692 int i, rv, *p;
1693 gfloat *f;
1694 char **s;
1696 /* get settings */
1697 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1698 if (strcmp(var, rs[i].name))
1699 continue;
1701 if (rs[i].s) {
1702 if (rs[i].s->set(&rs[i], val))
1703 errx(1, "invalid value for %s: %s", var, val);
1704 rv = 1;
1705 break;
1706 } else
1707 switch (rs[i].type) {
1708 case XT_S_INT:
1709 p = rs[i].ival;
1710 *p = atoi(val);
1711 rv = 1;
1712 break;
1713 case XT_S_STR:
1714 s = rs[i].sval;
1715 if (s == NULL)
1716 errx(1, "invalid sval for %s",
1717 rs[i].name);
1718 if (*s)
1719 g_free(*s);
1720 *s = g_strdup(val);
1721 rv = 1;
1722 break;
1723 case XT_S_FLOAT:
1724 f = rs[i].fval;
1725 *f = atof(val);
1726 rv = 1;
1727 break;
1728 case XT_S_INVALID:
1729 default:
1730 errx(1, "invalid type for %s", var);
1732 break;
1734 return (rv);
1737 #define WS "\n= \t"
1738 void
1739 config_parse(char *filename, int runtime)
1741 FILE *config, *f;
1742 char *line, *cp, *var, *val;
1743 size_t len, lineno = 0;
1744 int handled;
1745 char file[PATH_MAX];
1746 struct stat sb;
1748 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1750 if (filename == NULL)
1751 return;
1753 if (runtime && runtime_settings[0] != '\0') {
1754 snprintf(file, sizeof file, "%s/%s",
1755 work_dir, runtime_settings);
1756 if (stat(file, &sb)) {
1757 warnx("runtime file doesn't exist, creating it");
1758 if ((f = fopen(file, "w")) == NULL)
1759 err(1, "runtime");
1760 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1761 fclose(f);
1763 } else
1764 strlcpy(file, filename, sizeof file);
1766 if ((config = fopen(file, "r")) == NULL) {
1767 warn("config_parse: cannot open %s", filename);
1768 return;
1771 for (;;) {
1772 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1773 if (feof(config) || ferror(config))
1774 break;
1776 cp = line;
1777 cp += (long)strspn(cp, WS);
1778 if (cp[0] == '\0') {
1779 /* empty line */
1780 free(line);
1781 continue;
1784 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1785 errx(1, "invalid config file entry: %s", line);
1787 cp += (long)strspn(cp, WS);
1789 if ((val = strsep(&cp, "\0")) == NULL)
1790 break;
1792 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1793 handled = settings_add(var, val);
1794 if (handled == 0)
1795 errx(1, "invalid conf file entry: %s=%s", var, val);
1797 free(line);
1800 fclose(config);
1803 char *
1804 js_ref_to_string(JSContextRef context, JSValueRef ref)
1806 char *s = NULL;
1807 size_t l;
1808 JSStringRef jsref;
1810 jsref = JSValueToStringCopy(context, ref, NULL);
1811 if (jsref == NULL)
1812 return (NULL);
1814 l = JSStringGetMaximumUTF8CStringSize(jsref);
1815 s = g_malloc(l);
1816 if (s)
1817 JSStringGetUTF8CString(jsref, s, l);
1818 JSStringRelease(jsref);
1820 return (s);
1823 void
1824 disable_hints(struct tab *t)
1826 bzero(t->hint_buf, sizeof t->hint_buf);
1827 bzero(t->hint_num, sizeof t->hint_num);
1828 run_script(t, "vimprobable_clear()");
1829 t->hints_on = 0;
1830 t->hint_mode = XT_HINT_NONE;
1833 void
1834 enable_hints(struct tab *t)
1836 bzero(t->hint_buf, sizeof t->hint_buf);
1837 run_script(t, "vimprobable_show_hints()");
1838 t->hints_on = 1;
1839 t->hint_mode = XT_HINT_NONE;
1842 #define XT_JS_OPEN ("open;")
1843 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1844 #define XT_JS_FIRE ("fire;")
1845 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1846 #define XT_JS_FOUND ("found;")
1847 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1850 run_script(struct tab *t, char *s)
1852 JSGlobalContextRef ctx;
1853 WebKitWebFrame *frame;
1854 JSStringRef str;
1855 JSValueRef val, exception;
1856 char *es, buf[128];
1858 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1859 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1861 frame = webkit_web_view_get_main_frame(t->wv);
1862 ctx = webkit_web_frame_get_global_context(frame);
1864 str = JSStringCreateWithUTF8CString(s);
1865 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1866 NULL, 0, &exception);
1867 JSStringRelease(str);
1869 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1870 if (val == NULL) {
1871 es = js_ref_to_string(ctx, exception);
1872 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1873 g_free(es);
1874 return (1);
1875 } else {
1876 es = js_ref_to_string(ctx, val);
1877 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1879 /* handle return value right here */
1880 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1881 disable_hints(t);
1882 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1885 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1886 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1887 &es[XT_JS_FIRE_LEN]);
1888 run_script(t, buf);
1889 disable_hints(t);
1892 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1893 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1894 disable_hints(t);
1897 g_free(es);
1900 return (0);
1904 hint(struct tab *t, struct karg *args)
1907 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1909 if (t->hints_on == 0)
1910 enable_hints(t);
1911 else
1912 disable_hints(t);
1914 return (0);
1917 void
1918 apply_style(struct tab *t)
1920 g_object_set(G_OBJECT(t->settings),
1921 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1925 userstyle(struct tab *t, struct karg *args)
1927 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1929 if (t->styled) {
1930 t->styled = 0;
1931 g_object_set(G_OBJECT(t->settings),
1932 "user-stylesheet-uri", NULL, (char *)NULL);
1933 } else {
1934 t->styled = 1;
1935 apply_style(t);
1937 return (0);
1941 * Doesn't work fully, due to the following bug:
1942 * https://bugs.webkit.org/show_bug.cgi?id=51747
1945 restore_global_history(void)
1947 char file[PATH_MAX];
1948 FILE *f;
1949 struct history *h;
1950 gchar *uri;
1951 gchar *title;
1953 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1955 if ((f = fopen(file, "r")) == NULL) {
1956 warnx("%s: fopen", __func__);
1957 return (1);
1960 for (;;) {
1961 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1962 if (feof(f) || ferror(f))
1963 break;
1965 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1966 if (feof(f) || ferror(f)) {
1967 free(uri);
1968 warnx("%s: broken history file\n", __func__);
1969 return (1);
1972 if (uri && strlen(uri) && title && strlen(title)) {
1973 webkit_web_history_item_new_with_data(uri, title);
1974 h = g_malloc(sizeof(struct history));
1975 h->uri = g_strdup(uri);
1976 h->title = g_strdup(title);
1977 RB_INSERT(history_list, &hl, h);
1978 completion_add_uri(h->uri);
1979 } else {
1980 warnx("%s: failed to restore history\n", __func__);
1981 free(uri);
1982 free(title);
1983 return (1);
1986 free(uri);
1987 free(title);
1988 uri = NULL;
1989 title = NULL;
1992 return (0);
1996 save_global_history_to_disk(struct tab *t)
1998 char file[PATH_MAX];
1999 FILE *f;
2000 struct history *h;
2002 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2004 if ((f = fopen(file, "w")) == NULL) {
2005 show_oops(t, "%s: global history file: %s",
2006 __func__, strerror(errno));
2007 return (1);
2010 RB_FOREACH_REVERSE(h, history_list, &hl) {
2011 if (h->uri && h->title)
2012 fprintf(f, "%s\n%s\n", h->uri, h->title);
2015 fclose(f);
2017 return (0);
2021 quit(struct tab *t, struct karg *args)
2023 if (save_global_history)
2024 save_global_history_to_disk(t);
2026 gtk_main_quit();
2028 return (1);
2032 open_tabs(struct tab *t, struct karg *a)
2034 char file[PATH_MAX];
2035 FILE *f = NULL;
2036 char *uri = NULL;
2037 int rv = 1;
2038 struct tab *ti, *tt;
2040 if (a == NULL)
2041 goto done;
2043 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2044 if ((f = fopen(file, "r")) == NULL)
2045 goto done;
2047 ti = TAILQ_LAST(&tabs, tab_list);
2049 for (;;) {
2050 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2051 if (feof(f) || ferror(f))
2052 break;
2054 /* retrieve session name */
2055 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2056 strlcpy(named_session,
2057 &uri[strlen(XT_SAVE_SESSION_ID)],
2058 sizeof named_session);
2059 continue;
2062 if (uri && strlen(uri))
2063 create_new_tab(uri, NULL, 1);
2065 free(uri);
2066 uri = NULL;
2069 /* close open tabs */
2070 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2071 for (;;) {
2072 tt = TAILQ_FIRST(&tabs);
2073 if (tt != ti) {
2074 delete_tab(tt);
2075 continue;
2077 delete_tab(tt);
2078 break;
2082 rv = 0;
2083 done:
2084 if (f)
2085 fclose(f);
2087 return (rv);
2091 restore_saved_tabs(void)
2093 char file[PATH_MAX];
2094 int unlink_file = 0;
2095 struct stat sb;
2096 struct karg a;
2097 int rv = 0;
2099 snprintf(file, sizeof file, "%s/%s",
2100 sessions_dir, XT_RESTART_TABS_FILE);
2101 if (stat(file, &sb) == -1)
2102 a.s = XT_SAVED_TABS_FILE;
2103 else {
2104 unlink_file = 1;
2105 a.s = XT_RESTART_TABS_FILE;
2108 a.i = XT_SES_DONOTHING;
2109 rv = open_tabs(NULL, &a);
2111 if (unlink_file)
2112 unlink(file);
2114 return (rv);
2118 save_tabs(struct tab *t, struct karg *a)
2120 char file[PATH_MAX];
2121 FILE *f;
2122 struct tab *ti;
2123 const gchar *uri;
2124 int len = 0, i;
2125 const gchar **arr = NULL;
2127 if (a == NULL)
2128 return (1);
2129 if (a->s == NULL)
2130 snprintf(file, sizeof file, "%s/%s",
2131 sessions_dir, named_session);
2132 else
2133 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2135 if ((f = fopen(file, "w")) == NULL) {
2136 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2137 return (1);
2140 /* save session name */
2141 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2143 /* save tabs, in the order they are arranged in the notebook */
2144 TAILQ_FOREACH(ti, &tabs, entry)
2145 len++;
2147 arr = g_malloc0(len * sizeof(gchar *));
2149 TAILQ_FOREACH(ti, &tabs, entry) {
2150 if ((uri = get_uri(ti->wv)) != NULL)
2151 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2154 for (i = 0; i < len; i++)
2155 if (arr[i])
2156 fprintf(f, "%s\n", arr[i]);
2158 g_free(arr);
2159 fclose(f);
2161 return (0);
2165 save_tabs_and_quit(struct tab *t, struct karg *args)
2167 struct karg a;
2169 a.s = NULL;
2170 save_tabs(t, &a);
2171 quit(t, NULL);
2173 return (1);
2177 yank_uri(struct tab *t, struct karg *args)
2179 const gchar *uri;
2180 GtkClipboard *clipboard;
2182 if ((uri = get_uri(t->wv)) == NULL)
2183 return (1);
2185 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2186 gtk_clipboard_set_text(clipboard, uri, -1);
2188 return (0);
2191 struct paste_args {
2192 struct tab *t;
2193 int i;
2196 void
2197 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2199 struct paste_args *pap;
2201 if (data == NULL || text == NULL || !strlen(text))
2202 return;
2204 pap = (struct paste_args *)data;
2206 switch(pap->i) {
2207 case XT_PASTE_CURRENT_TAB:
2208 load_uri(pap->t, (gchar *)text);
2209 break;
2210 case XT_PASTE_NEW_TAB:
2211 create_new_tab((gchar *)text, NULL, 1);
2212 break;
2215 g_free(pap);
2219 paste_uri(struct tab *t, struct karg *args)
2221 GtkClipboard *clipboard;
2222 struct paste_args *pap;
2224 pap = g_malloc(sizeof(struct paste_args));
2226 pap->t = t;
2227 pap->i = args->i;
2229 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2230 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2232 return (0);
2235 char *
2236 find_domain(const gchar *s, int add_dot)
2238 int i;
2239 char *r = NULL, *ss = NULL;
2241 if (s == NULL)
2242 return (NULL);
2244 if (!strncmp(s, "http://", strlen("http://")))
2245 s = &s[strlen("http://")];
2246 else if (!strncmp(s, "https://", strlen("https://")))
2247 s = &s[strlen("https://")];
2249 if (strlen(s) < 2)
2250 return (NULL);
2252 ss = g_strdup(s);
2253 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2254 /* chop string at first slash */
2255 if (ss[i] == '/' || ss[i] == '\0') {
2256 ss[i] = '\0';
2257 if (add_dot)
2258 r = g_strdup_printf(".%s", ss);
2259 else
2260 r = g_strdup(ss);
2261 break;
2263 g_free(ss);
2265 return (r);
2269 toggle_cwl(struct tab *t, struct karg *args)
2271 struct domain *d;
2272 const gchar *uri;
2273 char *dom = NULL, *dom_toggle = NULL;
2274 int es;
2276 if (args == NULL)
2277 return (1);
2279 uri = get_uri(t->wv);
2280 dom = find_domain(uri, 1);
2281 d = wl_find(dom, &c_wl);
2283 if (d == NULL)
2284 es = 0;
2285 else
2286 es = 1;
2288 if (args->i & XT_WL_TOGGLE)
2289 es = !es;
2290 else if ((args->i & XT_WL_ENABLE) && es != 1)
2291 es = 1;
2292 else if ((args->i & XT_WL_DISABLE) && es != 0)
2293 es = 0;
2295 if (args->i & XT_WL_TOPLEVEL)
2296 dom_toggle = get_toplevel_domain(dom);
2297 else
2298 dom_toggle = dom;
2300 if (es)
2301 /* enable cookies for domain */
2302 wl_add(dom_toggle, &c_wl, 0);
2303 else
2304 /* disable cookies for domain */
2305 RB_REMOVE(domain_list, &c_wl, d);
2307 webkit_web_view_reload(t->wv);
2309 g_free(dom);
2310 return (0);
2314 toggle_js(struct tab *t, struct karg *args)
2316 int es;
2317 const gchar *uri;
2318 struct domain *d;
2319 char *dom = NULL, *dom_toggle = NULL;
2321 if (args == NULL)
2322 return (1);
2324 g_object_get(G_OBJECT(t->settings),
2325 "enable-scripts", &es, (char *)NULL);
2326 if (args->i & XT_WL_TOGGLE)
2327 es = !es;
2328 else if ((args->i & XT_WL_ENABLE) && es != 1)
2329 es = 1;
2330 else if ((args->i & XT_WL_DISABLE) && es != 0)
2331 es = 0;
2332 else
2333 return (1);
2335 uri = get_uri(t->wv);
2336 dom = find_domain(uri, 1);
2338 if (uri == NULL || dom == NULL) {
2339 show_oops(t, "Can't toggle domain in JavaScript white list");
2340 goto done;
2343 if (args->i & XT_WL_TOPLEVEL)
2344 dom_toggle = get_toplevel_domain(dom);
2345 else
2346 dom_toggle = dom;
2348 if (es) {
2349 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2350 wl_add(dom_toggle, &js_wl, 0 /* session */);
2351 } else {
2352 d = wl_find(dom_toggle, &js_wl);
2353 if (d)
2354 RB_REMOVE(domain_list, &js_wl, d);
2355 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2357 g_object_set(G_OBJECT(t->settings),
2358 "enable-scripts", es, (char *)NULL);
2359 g_object_set(G_OBJECT(t->settings),
2360 "javascript-can-open-windows-automatically", es, (char *)NULL);
2361 webkit_web_view_set_settings(t->wv, t->settings);
2362 webkit_web_view_reload(t->wv);
2363 done:
2364 if (dom)
2365 g_free(dom);
2366 return (0);
2369 void
2370 js_toggle_cb(GtkWidget *w, struct tab *t)
2372 struct karg a;
2374 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2375 toggle_js(t, &a);
2379 toggle_src(struct tab *t, struct karg *args)
2381 gboolean mode;
2383 if (t == NULL)
2384 return (0);
2386 mode = webkit_web_view_get_view_source_mode(t->wv);
2387 webkit_web_view_set_view_source_mode(t->wv, !mode);
2388 webkit_web_view_reload(t->wv);
2390 return (0);
2393 void
2394 focus_webview(struct tab *t)
2396 if (t == NULL)
2397 return;
2399 /* only grab focus if we are visible */
2400 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2401 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2405 focus(struct tab *t, struct karg *args)
2407 if (t == NULL || args == NULL)
2408 return (1);
2410 if (show_url == 0)
2411 return (0);
2413 if (args->i == XT_FOCUS_URI)
2414 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2415 else if (args->i == XT_FOCUS_SEARCH)
2416 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2418 return (0);
2422 stats(struct tab *t, struct karg *args)
2424 char *stats, *s, line[64 * 1024];
2425 uint64_t line_count = 0;
2426 FILE *r_cookie_f;
2428 if (t == NULL)
2429 show_oops_s("stats invalid parameters");
2431 line[0] = '\0';
2432 if (save_rejected_cookies) {
2433 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2434 for (;;) {
2435 s = fgets(line, sizeof line, r_cookie_f);
2436 if (s == NULL || feof(r_cookie_f) ||
2437 ferror(r_cookie_f))
2438 break;
2439 line_count++;
2441 fclose(r_cookie_f);
2442 snprintf(line, sizeof line,
2443 "<br>Cookies blocked(*) total: %llu", line_count);
2444 } else
2445 show_oops(t, "Can't open blocked cookies file: %s",
2446 strerror(errno));
2449 stats = g_strdup_printf(XT_DOCTYPE
2450 "<html>"
2451 "<head>"
2452 "<title>Statistics</title>"
2453 "</head>"
2454 "<h1>Statistics</h1>"
2455 "<body>"
2456 "Cookies blocked(*) this session: %llu"
2457 "%s"
2458 "<p><small><b>*</b> results vary based on settings"
2459 "</body>"
2460 "</html>",
2461 blocked_cookies,
2462 line);
2464 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2465 g_free(stats);
2467 return (0);
2471 marco(struct tab *t, struct karg *args)
2473 char *message, line[64 * 1024];
2474 int len;
2476 if (t == NULL)
2477 show_oops_s("marco invalid parameters");
2479 line[0] = '\0';
2480 snprintf(line, sizeof line, "<br>%s", marco_message(&len));
2482 message = g_strdup_printf(XT_DOCTYPE
2483 "<html>"
2484 "<head>"
2485 "<title>Marco Sez...</title>"
2486 "</head>"
2487 "<h1>Moo</h1>"
2488 "<body>"
2489 "%s"
2490 "</body>"
2491 "</html>",
2492 line);
2494 load_webkit_string(t, message, XT_URI_ABOUT_MARCO);
2495 g_free(message);
2497 return (0);
2501 blank(struct tab *t, struct karg *args)
2503 if (t == NULL)
2504 show_oops_s("about invalid parameters");
2506 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2508 return (0);
2511 about(struct tab *t, struct karg *args)
2513 char *about;
2515 if (t == NULL)
2516 show_oops_s("about invalid parameters");
2518 about = g_strdup_printf(XT_DOCTYPE
2519 "<html>"
2520 "<head>"
2521 "<title>About</title>"
2522 "</head>"
2523 "<h1>About</h1>"
2524 "<body>"
2525 "<b>Version: %s</b><p>"
2526 "Authors:"
2527 "<ul>"
2528 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2529 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2530 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2531 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2532 "</ul>"
2533 "Copyrights and licenses can be found on the XXXterm "
2534 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2535 "</body>"
2536 "</html>",
2537 version
2540 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2541 g_free(about);
2543 return (0);
2547 help(struct tab *t, struct karg *args)
2549 char *help;
2551 if (t == NULL)
2552 show_oops_s("help invalid parameters");
2554 help = XT_DOCTYPE
2555 "<html>"
2556 "<head>"
2557 "<title>XXXterm</title>"
2558 "<meta http-equiv=\"REFRESH\" content=\"0;"
2559 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2560 "</head>"
2561 "<body>"
2562 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2563 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2564 "cgi-bin/man-cgi?xxxterm</a>"
2565 "</body>"
2566 "</html>"
2569 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2571 return (0);
2575 * update all favorite tabs apart from one. Pass NULL if
2576 * you want to update all.
2578 void
2579 update_favorite_tabs(struct tab *apart_from)
2581 struct tab *t;
2582 if (!updating_fl_tabs) {
2583 updating_fl_tabs = 1; /* stop infinite recursion */
2584 TAILQ_FOREACH(t, &tabs, entry)
2585 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2586 && (t != apart_from))
2587 xtp_page_fl(t, NULL);
2588 updating_fl_tabs = 0;
2592 /* show a list of favorites (bookmarks) */
2594 xtp_page_fl(struct tab *t, struct karg *args)
2596 char file[PATH_MAX];
2597 FILE *f;
2598 char *uri = NULL, *title = NULL;
2599 size_t len, lineno = 0;
2600 int i, failed = 0;
2601 char *header, *body, *tmp, *html = NULL;
2603 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2605 if (t == NULL)
2606 warn("%s: bad param", __func__);
2608 /* mark tab as favorite list */
2609 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2611 /* new session key */
2612 if (!updating_fl_tabs)
2613 generate_xtp_session_key(&fl_session_key);
2615 /* open favorites */
2616 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2617 if ((f = fopen(file, "r")) == NULL) {
2618 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2619 return (1);
2622 /* header */
2623 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2624 "<title>Favorites</title>\n"
2625 "%s"
2626 "</head>"
2627 "<h1>Favorites</h1>\n",
2628 XT_PAGE_STYLE);
2630 /* body */
2631 body = g_strdup_printf("<div align='center'><table><tr>"
2632 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2633 "<th style='width: 15%%'>Remove</th></tr>\n");
2635 for (i = 1;;) {
2636 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2637 if (feof(f) || ferror(f))
2638 break;
2639 if (len == 0) {
2640 free(title);
2641 title = NULL;
2642 continue;
2645 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2646 if (feof(f) || ferror(f)) {
2647 show_oops(t, "favorites file corrupt");
2648 failed = 1;
2649 break;
2652 tmp = body;
2653 body = g_strdup_printf("%s<tr>"
2654 "<td>%d</td>"
2655 "<td><a href='%s'>%s</a></td>"
2656 "<td style='text-align: center'>"
2657 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2658 "</tr>\n",
2659 body, i, uri, title,
2660 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2662 g_free(tmp);
2664 free(uri);
2665 uri = NULL;
2666 free(title);
2667 title = NULL;
2668 i++;
2670 fclose(f);
2672 /* if none, say so */
2673 if (i == 1) {
2674 tmp = body;
2675 body = g_strdup_printf("%s<tr>"
2676 "<td colspan='3' style='text-align: center'>"
2677 "No favorites - To add one use the 'favadd' command."
2678 "</td></tr>", body);
2679 g_free(tmp);
2682 if (uri)
2683 free(uri);
2684 if (title)
2685 free(title);
2687 /* render */
2688 if (!failed) {
2689 html = g_strdup_printf("%s%s</table></div></html>",
2690 header, body);
2691 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2694 update_favorite_tabs(t);
2696 if (header)
2697 g_free(header);
2698 if (body)
2699 g_free(body);
2700 if (html)
2701 g_free(html);
2703 return (failed);
2706 void
2707 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2708 size_t cert_count, char *title)
2710 gnutls_datum_t cinfo;
2711 char *tmp, *header, *body, *footer;
2712 int i;
2714 header = g_strdup_printf("<html><head><title>%s</title></head><body>", title);
2715 footer = g_strdup("</body></html>");
2716 body = g_strdup("");
2718 for (i = 0; i < cert_count; i++) {
2719 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2720 &cinfo))
2721 return;
2723 tmp = body;
2724 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2725 body, i, cinfo.data);
2726 gnutls_free(cinfo.data);
2727 g_free(tmp);
2730 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2731 g_free(header);
2732 g_free(body);
2733 g_free(footer);
2734 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2735 g_free(tmp);
2739 ca_cmd(struct tab *t, struct karg *args)
2741 FILE *f = NULL;
2742 int rv = 1, certs = 0, certs_read;
2743 struct stat sb;
2744 gnutls_datum dt;
2745 gnutls_x509_crt_t *c = NULL;
2746 char *certs_buf = NULL, *s;
2748 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2749 show_oops(t, "Can't open CA file: %s", strerror(errno));
2750 return (1);
2753 if (fstat(fileno(f), &sb) == -1) {
2754 show_oops(t, "Can't stat CA file: %s", strerror(errno));
2755 goto done;
2758 certs_buf = g_malloc(sb.st_size + 1);
2759 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2760 show_oops(t, "Can't read CA file: %s", strerror(errno));
2761 goto done;
2763 certs_buf[sb.st_size] = '\0';
2765 s = certs_buf;
2766 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2767 certs++;
2768 s += strlen("BEGIN CERTIFICATE");
2771 bzero(&dt, sizeof dt);
2772 dt.data = certs_buf;
2773 dt.size = sb.st_size;
2774 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2775 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2776 GNUTLS_X509_FMT_PEM, 0);
2777 if (certs_read <= 0) {
2778 show_oops(t, "No cert(s) available");
2779 goto done;
2781 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2782 done:
2783 if (c)
2784 g_free(c);
2785 if (certs_buf)
2786 g_free(certs_buf);
2787 if (f)
2788 fclose(f);
2790 return (rv);
2794 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2796 SoupURI *su = NULL;
2797 struct addrinfo hints, *res = NULL, *ai;
2798 int s = -1, on;
2799 char port[8];
2801 if (uri && !g_str_has_prefix(uri, "https://"))
2802 goto done;
2804 su = soup_uri_new(uri);
2805 if (su == NULL)
2806 goto done;
2807 if (!SOUP_URI_VALID_FOR_HTTP(su))
2808 goto done;
2810 snprintf(port, sizeof port, "%d", su->port);
2811 bzero(&hints, sizeof(struct addrinfo));
2812 hints.ai_flags = AI_CANONNAME;
2813 hints.ai_family = AF_UNSPEC;
2814 hints.ai_socktype = SOCK_STREAM;
2816 if (getaddrinfo(su->host, port, &hints, &res))
2817 goto done;
2819 for (ai = res; ai; ai = ai->ai_next) {
2820 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2821 continue;
2823 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2824 if (s < 0)
2825 goto done;
2826 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2827 sizeof(on)) == -1)
2828 goto done;
2830 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2831 goto done;
2834 if (domain)
2835 strlcpy(domain, su->host, domain_sz);
2836 done:
2837 if (su)
2838 soup_uri_free(su);
2839 if (res)
2840 freeaddrinfo(res);
2842 return (s);
2846 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2848 if (gsession)
2849 gnutls_deinit(gsession);
2850 if (xcred)
2851 gnutls_certificate_free_credentials(xcred);
2853 return (0);
2857 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2858 gnutls_certificate_credentials_t *xc)
2860 gnutls_certificate_credentials_t xcred;
2861 gnutls_session_t gsession;
2862 int rv = 1;
2864 if (gs == NULL || xc == NULL)
2865 goto done;
2867 *gs = NULL;
2868 *xc = NULL;
2870 gnutls_certificate_allocate_credentials(&xcred);
2871 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2872 GNUTLS_X509_FMT_PEM);
2873 gnutls_init(&gsession, GNUTLS_CLIENT);
2874 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2875 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2876 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2877 if ((rv = gnutls_handshake(gsession)) < 0) {
2878 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2880 gnutls_error_is_fatal(rv),
2881 gnutls_strerror_name(rv));
2882 stop_tls(gsession, xcred);
2883 goto done;
2886 gnutls_credentials_type_t cred;
2887 cred = gnutls_auth_get_type(gsession);
2888 if (cred != GNUTLS_CRD_CERTIFICATE) {
2889 stop_tls(gsession, xcred);
2890 goto done;
2893 *gs = gsession;
2894 *xc = xcred;
2895 rv = 0;
2896 done:
2897 return (rv);
2901 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2902 size_t *cert_count)
2904 unsigned int len;
2905 const gnutls_datum_t *cl;
2906 gnutls_x509_crt_t *all_certs;
2907 int i, rv = 1;
2909 if (certs == NULL || cert_count == NULL)
2910 goto done;
2911 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2912 goto done;
2913 cl = gnutls_certificate_get_peers(gsession, &len);
2914 if (len == 0)
2915 goto done;
2917 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2918 for (i = 0; i < len; i++) {
2919 gnutls_x509_crt_init(&all_certs[i]);
2920 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2921 GNUTLS_X509_FMT_PEM < 0)) {
2922 g_free(all_certs);
2923 goto done;
2927 *certs = all_certs;
2928 *cert_count = len;
2929 rv = 0;
2930 done:
2931 return (rv);
2934 void
2935 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2937 int i;
2939 for (i = 0; i < cert_count; i++)
2940 gnutls_x509_crt_deinit(certs[i]);
2941 g_free(certs);
2944 void
2945 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2946 size_t cert_count, char *domain)
2948 size_t cert_buf_sz;
2949 char cert_buf[64 * 1024], file[PATH_MAX];
2950 int i;
2951 FILE *f;
2952 GdkColor color;
2954 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2955 return;
2957 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2958 if ((f = fopen(file, "w")) == NULL) {
2959 show_oops(t, "Can't create cert file %s %s",
2960 file, strerror(errno));
2961 return;
2964 for (i = 0; i < cert_count; i++) {
2965 cert_buf_sz = sizeof cert_buf;
2966 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2967 cert_buf, &cert_buf_sz)) {
2968 show_oops(t, "gnutls_x509_crt_export failed");
2969 goto done;
2971 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2972 show_oops(t, "Can't write certs: %s", strerror(errno));
2973 goto done;
2977 /* not the best spot but oh well */
2978 gdk_color_parse("lightblue", &color);
2979 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2980 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2981 gdk_color_parse(XT_COLOR_BLACK, &color);
2982 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2983 done:
2984 fclose(f);
2988 load_compare_cert(struct tab *t, struct karg *args)
2990 const gchar *uri;
2991 char domain[8182], file[PATH_MAX];
2992 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2993 int s = -1, rv = 1, i;
2994 size_t cert_count;
2995 FILE *f = NULL;
2996 size_t cert_buf_sz;
2997 gnutls_session_t gsession;
2998 gnutls_x509_crt_t *certs;
2999 gnutls_certificate_credentials_t xcred;
3001 if (t == NULL)
3002 return (1);
3004 if ((uri = get_uri(t->wv)) == NULL)
3005 return (1);
3007 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3008 return (1);
3010 /* go ssl/tls */
3011 if (start_tls(t, s, &gsession, &xcred)) {
3012 show_oops(t, "Start TLS failed");
3013 goto done;
3016 /* get certs */
3017 if (get_connection_certs(gsession, &certs, &cert_count)) {
3018 show_oops(t, "Can't get connection certificates");
3019 goto done;
3022 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3023 if ((f = fopen(file, "r")) == NULL)
3024 goto freeit;
3026 for (i = 0; i < cert_count; i++) {
3027 cert_buf_sz = sizeof cert_buf;
3028 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3029 cert_buf, &cert_buf_sz)) {
3030 goto freeit;
3032 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3033 rv = -1; /* critical */
3034 goto freeit;
3036 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3037 rv = -1; /* critical */
3038 goto freeit;
3042 rv = 0;
3043 freeit:
3044 if (f)
3045 fclose(f);
3046 free_connection_certs(certs, cert_count);
3047 done:
3048 /* we close the socket first for speed */
3049 if (s != -1)
3050 close(s);
3051 stop_tls(gsession, xcred);
3053 return (rv);
3057 cert_cmd(struct tab *t, struct karg *args)
3059 const gchar *uri;
3060 char domain[8182];
3061 int s = -1;
3062 size_t cert_count;
3063 gnutls_session_t gsession;
3064 gnutls_x509_crt_t *certs;
3065 gnutls_certificate_credentials_t xcred;
3067 if (t == NULL)
3068 return (1);
3070 if ((uri = get_uri(t->wv)) == NULL) {
3071 show_oops(t, "Invalid URI");
3072 return (1);
3075 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3076 show_oops(t, "Invalid certidicate URI: %s", uri);
3077 return (1);
3080 /* go ssl/tls */
3081 if (start_tls(t, s, &gsession, &xcred)) {
3082 show_oops(t, "Start TLS failed");
3083 goto done;
3086 /* get certs */
3087 if (get_connection_certs(gsession, &certs, &cert_count)) {
3088 show_oops(t, "get_connection_certs failed");
3089 goto done;
3092 if (args->i & XT_SHOW)
3093 show_certs(t, certs, cert_count, "Certificate Chain");
3094 else if (args->i & XT_SAVE)
3095 save_certs(t, certs, cert_count, domain);
3097 free_connection_certs(certs, cert_count);
3098 done:
3099 /* we close the socket first for speed */
3100 if (s != -1)
3101 close(s);
3102 stop_tls(gsession, xcred);
3104 return (0);
3108 remove_cookie(int index)
3110 int i, rv = 1;
3111 GSList *cf;
3112 SoupCookie *c;
3114 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3116 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3118 for (i = 1; cf; cf = cf->next, i++) {
3119 if (i != index)
3120 continue;
3121 c = cf->data;
3122 print_cookie("remove cookie", c);
3123 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3124 rv = 0;
3125 break;
3128 soup_cookies_free(cf);
3130 return (rv);
3134 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3136 struct domain *d;
3137 char *tmp, *header, *body, *footer;
3139 /* we set this to indicate we want to manually do navaction */
3140 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3142 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3143 title, title);
3144 footer = g_strdup("</body></html>");
3145 body = g_strdup("");
3147 /* p list */
3148 if (args->i & XT_WL_PERSISTENT) {
3149 tmp = body;
3150 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3151 g_free(tmp);
3152 RB_FOREACH(d, domain_list, wl) {
3153 if (d->handy == 0)
3154 continue;
3155 tmp = body;
3156 body = g_strdup_printf("%s%s<br>", body, d->d);
3157 g_free(tmp);
3161 /* s list */
3162 if (args->i & XT_WL_SESSION) {
3163 tmp = body;
3164 body = g_strdup_printf("%s<h2>Session</h2>", body);
3165 g_free(tmp);
3166 RB_FOREACH(d, domain_list, wl) {
3167 if (d->handy == 1)
3168 continue;
3169 tmp = body;
3170 body = g_strdup_printf("%s%s<br>", body, d->d);
3171 g_free(tmp);
3175 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3176 g_free(header);
3177 g_free(body);
3178 g_free(footer);
3179 if (wl == &js_wl)
3180 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3181 else
3182 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3183 g_free(tmp);
3184 return (0);
3188 wl_save(struct tab *t, struct karg *args, int js)
3190 char file[PATH_MAX];
3191 FILE *f;
3192 char *line = NULL, *lt = NULL;
3193 size_t linelen;
3194 const gchar *uri;
3195 char *dom = NULL, *dom_save = NULL;
3196 struct karg a;
3197 struct domain *d;
3198 GSList *cf;
3199 SoupCookie *ci, *c;
3201 if (t == NULL || args == NULL)
3202 return (1);
3204 if (runtime_settings[0] == '\0')
3205 return (1);
3207 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3208 if ((f = fopen(file, "r+")) == NULL)
3209 return (1);
3211 uri = get_uri(t->wv);
3212 dom = find_domain(uri, 1);
3213 if (uri == NULL || dom == NULL) {
3214 show_oops(t, "Can't add domain to %s white list",
3215 js ? "JavaScript" : "cookie");
3216 goto done;
3219 if (args->i & XT_WL_TOPLEVEL) {
3220 /* save domain */
3221 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3222 show_oops(t, "invalid domain: %s", dom);
3223 goto done;
3225 } else if (args->i & XT_WL_FQDN) {
3226 /* save fqdn */
3227 dom_save = dom;
3228 } else
3229 goto done;
3231 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3233 while (!feof(f)) {
3234 line = fparseln(f, &linelen, NULL, NULL, 0);
3235 if (line == NULL)
3236 continue;
3237 if (!strcmp(line, lt))
3238 goto done;
3239 free(line);
3240 line = NULL;
3243 fprintf(f, "%s\n", lt);
3245 a.i = XT_WL_ENABLE;
3246 a.i |= args->i;
3247 if (js) {
3248 d = wl_find(dom_save, &js_wl);
3249 if (!d) {
3250 settings_add("js_wl", dom_save);
3251 d = wl_find(dom_save, &js_wl);
3253 toggle_js(t, &a);
3254 } else {
3255 d = wl_find(dom_save, &c_wl);
3256 if (!d) {
3257 settings_add("cookie_wl", dom_save);
3258 d = wl_find(dom_save, &c_wl);
3260 toggle_cwl(t, &a);
3262 /* find and add to persistent jar */
3263 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3264 for (;cf; cf = cf->next) {
3265 ci = cf->data;
3266 if (!strcmp(dom_save, ci->domain) ||
3267 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3268 c = soup_cookie_copy(ci);
3269 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3272 soup_cookies_free(cf);
3274 if (d)
3275 d->handy = 1;
3277 done:
3278 if (line)
3279 free(line);
3280 if (dom)
3281 g_free(dom);
3282 if (lt)
3283 g_free(lt);
3284 fclose(f);
3286 return (0);
3290 js_show_wl(struct tab *t, struct karg *args)
3292 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3293 wl_show(t, args, "JavaScript White List", &js_wl);
3295 return (0);
3299 cookie_show_wl(struct tab *t, struct karg *args)
3301 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3302 wl_show(t, args, "Cookie White List", &c_wl);
3304 return (0);
3308 cookie_cmd(struct tab *t, struct karg *args)
3310 if (args->i & XT_SHOW)
3311 wl_show(t, args, "Cookie White List", &c_wl);
3312 else if (args->i & XT_WL_TOGGLE)
3313 toggle_cwl(t, args);
3314 else if (args->i & XT_SAVE)
3315 wl_save(t, args, 0);
3316 else if (args->i & XT_DELETE)
3317 show_oops(t, "'cookie delete' currently unimplemented");
3319 return (0);
3323 js_cmd(struct tab *t, struct karg *args)
3325 if (args->i & XT_SHOW)
3326 wl_show(t, args, "JavaScript White List", &js_wl);
3327 else if (args->i & XT_SAVE)
3328 wl_save(t, args, 1);
3329 else if (args->i & XT_WL_TOGGLE)
3330 toggle_js(t, args);
3331 else if (args->i & XT_DELETE)
3332 show_oops(t, "'js delete' currently unimplemented");
3334 return (0);
3338 add_favorite(struct tab *t, struct karg *args)
3340 char file[PATH_MAX];
3341 FILE *f;
3342 char *line = NULL;
3343 size_t urilen, linelen;
3344 const gchar *uri, *title;
3346 if (t == NULL)
3347 return (1);
3349 /* don't allow adding of xtp pages to favorites */
3350 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3351 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3352 return (1);
3355 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3356 if ((f = fopen(file, "r+")) == NULL) {
3357 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3358 return (1);
3361 title = webkit_web_view_get_title(t->wv);
3362 uri = get_uri(t->wv);
3364 if (title == NULL)
3365 title = uri;
3367 if (title == NULL || uri == NULL) {
3368 show_oops(t, "can't add page to favorites");
3369 goto done;
3372 urilen = strlen(uri);
3374 for (;;) {
3375 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3376 if (feof(f) || ferror(f))
3377 break;
3379 if (linelen == urilen && !strcmp(line, uri))
3380 goto done;
3382 free(line);
3383 line = NULL;
3386 fprintf(f, "\n%s\n%s", title, uri);
3387 done:
3388 if (line)
3389 free(line);
3390 fclose(f);
3392 update_favorite_tabs(NULL);
3394 return (0);
3398 navaction(struct tab *t, struct karg *args)
3400 WebKitWebHistoryItem *item;
3402 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3403 t->tab_id, args->i);
3405 if (t->item) {
3406 if (args->i == XT_NAV_BACK)
3407 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3408 else
3409 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3410 if (item == NULL)
3411 return (XT_CB_PASSTHROUGH);
3412 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3413 t->item = NULL;
3414 return (XT_CB_PASSTHROUGH);
3417 switch (args->i) {
3418 case XT_NAV_BACK:
3419 webkit_web_view_go_back(t->wv);
3420 break;
3421 case XT_NAV_FORWARD:
3422 webkit_web_view_go_forward(t->wv);
3423 break;
3424 case XT_NAV_RELOAD:
3425 webkit_web_view_reload(t->wv);
3426 break;
3427 case XT_NAV_RELOAD_CACHE:
3428 webkit_web_view_reload_bypass_cache(t->wv);
3429 break;
3431 return (XT_CB_PASSTHROUGH);
3435 move(struct tab *t, struct karg *args)
3437 GtkAdjustment *adjust;
3438 double pi, si, pos, ps, upper, lower, max;
3440 switch (args->i) {
3441 case XT_MOVE_DOWN:
3442 case XT_MOVE_UP:
3443 case XT_MOVE_BOTTOM:
3444 case XT_MOVE_TOP:
3445 case XT_MOVE_PAGEDOWN:
3446 case XT_MOVE_PAGEUP:
3447 case XT_MOVE_HALFDOWN:
3448 case XT_MOVE_HALFUP:
3449 adjust = t->adjust_v;
3450 break;
3451 default:
3452 adjust = t->adjust_h;
3453 break;
3456 pos = gtk_adjustment_get_value(adjust);
3457 ps = gtk_adjustment_get_page_size(adjust);
3458 upper = gtk_adjustment_get_upper(adjust);
3459 lower = gtk_adjustment_get_lower(adjust);
3460 si = gtk_adjustment_get_step_increment(adjust);
3461 pi = gtk_adjustment_get_page_increment(adjust);
3462 max = upper - ps;
3464 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3465 "max %f si %f pi %f\n",
3466 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3467 pos, ps, upper, lower, max, si, pi);
3469 switch (args->i) {
3470 case XT_MOVE_DOWN:
3471 case XT_MOVE_RIGHT:
3472 pos += si;
3473 gtk_adjustment_set_value(adjust, MIN(pos, max));
3474 break;
3475 case XT_MOVE_UP:
3476 case XT_MOVE_LEFT:
3477 pos -= si;
3478 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3479 break;
3480 case XT_MOVE_BOTTOM:
3481 case XT_MOVE_FARRIGHT:
3482 gtk_adjustment_set_value(adjust, max);
3483 break;
3484 case XT_MOVE_TOP:
3485 case XT_MOVE_FARLEFT:
3486 gtk_adjustment_set_value(adjust, lower);
3487 break;
3488 case XT_MOVE_PAGEDOWN:
3489 pos += pi;
3490 gtk_adjustment_set_value(adjust, MIN(pos, max));
3491 break;
3492 case XT_MOVE_PAGEUP:
3493 pos -= pi;
3494 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3495 break;
3496 case XT_MOVE_HALFDOWN:
3497 pos += pi / 2;
3498 gtk_adjustment_set_value(adjust, MIN(pos, max));
3499 break;
3500 case XT_MOVE_HALFUP:
3501 pos -= pi / 2;
3502 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3503 break;
3504 default:
3505 return (XT_CB_PASSTHROUGH);
3508 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3510 return (XT_CB_HANDLED);
3513 void
3514 url_set_visibility(void)
3516 struct tab *t;
3518 TAILQ_FOREACH(t, &tabs, entry) {
3519 if (show_url == 0) {
3520 gtk_widget_hide(t->toolbar);
3521 focus_webview(t);
3522 } else
3523 gtk_widget_show(t->toolbar);
3527 void
3528 notebook_tab_set_visibility(GtkNotebook *notebook)
3530 if (show_tabs == 0)
3531 gtk_notebook_set_show_tabs(notebook, FALSE);
3532 else
3533 gtk_notebook_set_show_tabs(notebook, TRUE);
3536 void
3537 statusbar_set_visibility(void)
3539 struct tab *t;
3541 TAILQ_FOREACH(t, &tabs, entry) {
3542 if (show_statusbar == 0) {
3543 gtk_widget_hide(t->statusbar);
3544 focus_webview(t);
3545 } else
3546 gtk_widget_show(t->statusbar);
3550 void
3551 url_set(struct tab *t, int enable_url_entry)
3553 GdkPixbuf *pixbuf;
3554 int progress;
3556 show_url = enable_url_entry;
3558 if (enable_url_entry) {
3559 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3560 GTK_ENTRY_ICON_PRIMARY, NULL);
3561 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3562 } else {
3563 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3564 GTK_ENTRY_ICON_PRIMARY);
3565 progress =
3566 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3567 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3568 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3569 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3570 progress);
3575 fullscreen(struct tab *t, struct karg *args)
3577 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3579 if (t == NULL)
3580 return (XT_CB_PASSTHROUGH);
3582 if (show_url == 0) {
3583 url_set(t, 1);
3584 show_tabs = 1;
3585 } else {
3586 url_set(t, 0);
3587 show_tabs = 0;
3590 url_set_visibility();
3591 notebook_tab_set_visibility(notebook);
3593 return (XT_CB_HANDLED);
3597 statusaction(struct tab *t, struct karg *args)
3599 int rv = XT_CB_HANDLED;
3601 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3603 if (t == NULL)
3604 return (XT_CB_PASSTHROUGH);
3606 switch (args->i) {
3607 case XT_STATUSBAR_SHOW:
3608 if (show_statusbar == 0) {
3609 show_statusbar = 1;
3610 statusbar_set_visibility();
3612 break;
3613 case XT_STATUSBAR_HIDE:
3614 if (show_statusbar == 1) {
3615 show_statusbar = 0;
3616 statusbar_set_visibility();
3618 break;
3620 return (rv);
3624 urlaction(struct tab *t, struct karg *args)
3626 int rv = XT_CB_HANDLED;
3628 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3630 if (t == NULL)
3631 return (XT_CB_PASSTHROUGH);
3633 switch (args->i) {
3634 case XT_URL_SHOW:
3635 if (show_url == 0) {
3636 url_set(t, 1);
3637 url_set_visibility();
3639 break;
3640 case XT_URL_HIDE:
3641 if (show_url == 1) {
3642 url_set(t, 0);
3643 url_set_visibility();
3645 break;
3647 return (rv);
3651 tabaction(struct tab *t, struct karg *args)
3653 int rv = XT_CB_HANDLED;
3654 char *url = args->s;
3655 struct undo *u;
3657 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3659 if (t == NULL)
3660 return (XT_CB_PASSTHROUGH);
3662 switch (args->i) {
3663 case XT_TAB_NEW:
3664 if (strlen(url) > 0)
3665 create_new_tab(url, NULL, 1);
3666 else
3667 create_new_tab(NULL, NULL, 1);
3668 break;
3669 case XT_TAB_DELETE:
3670 delete_tab(t);
3671 break;
3672 case XT_TAB_DELQUIT:
3673 if (gtk_notebook_get_n_pages(notebook) > 1)
3674 delete_tab(t);
3675 else
3676 quit(t, args);
3677 break;
3678 case XT_TAB_OPEN:
3679 if (strlen(url) > 0)
3681 else {
3682 rv = XT_CB_PASSTHROUGH;
3683 goto done;
3685 load_uri(t, url);
3686 break;
3687 case XT_TAB_SHOW:
3688 if (show_tabs == 0) {
3689 show_tabs = 1;
3690 notebook_tab_set_visibility(notebook);
3692 break;
3693 case XT_TAB_HIDE:
3694 if (show_tabs == 1) {
3695 show_tabs = 0;
3696 notebook_tab_set_visibility(notebook);
3698 break;
3699 case XT_TAB_UNDO_CLOSE:
3700 if (undo_count == 0) {
3701 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3702 goto done;
3703 } else {
3704 undo_count--;
3705 u = TAILQ_FIRST(&undos);
3706 create_new_tab(u->uri, u, 1);
3708 TAILQ_REMOVE(&undos, u, entry);
3709 g_free(u->uri);
3710 /* u->history is freed in create_new_tab() */
3711 g_free(u);
3713 break;
3714 default:
3715 rv = XT_CB_PASSTHROUGH;
3716 goto done;
3719 done:
3720 if (args->s) {
3721 g_free(args->s);
3722 args->s = NULL;
3725 return (rv);
3729 resizetab(struct tab *t, struct karg *args)
3731 if (t == NULL || args == NULL) {
3732 show_oops_s("resizetab invalid parameters");
3733 return (XT_CB_PASSTHROUGH);
3736 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3737 t->tab_id, args->i);
3739 adjustfont_webkit(t, args->i);
3741 return (XT_CB_HANDLED);
3745 movetab(struct tab *t, struct karg *args)
3747 struct tab *tt;
3748 int x;
3750 if (t == NULL || args == NULL) {
3751 show_oops_s("movetab invalid parameters");
3752 return (XT_CB_PASSTHROUGH);
3755 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3756 t->tab_id, args->i);
3758 if (args->i == XT_TAB_INVALID)
3759 return (XT_CB_PASSTHROUGH);
3761 if (args->i < XT_TAB_INVALID) {
3762 /* next or previous tab */
3763 if (TAILQ_EMPTY(&tabs))
3764 return (XT_CB_PASSTHROUGH);
3766 switch (args->i) {
3767 case XT_TAB_NEXT:
3768 /* if at the last page, loop around to the first */
3769 if (gtk_notebook_get_current_page(notebook) ==
3770 gtk_notebook_get_n_pages(notebook) - 1)
3771 gtk_notebook_set_current_page(notebook, 0);
3772 else
3773 gtk_notebook_next_page(notebook);
3774 break;
3775 case XT_TAB_PREV:
3776 /* if at the first page, loop around to the last */
3777 if (gtk_notebook_current_page(notebook) == 0)
3778 gtk_notebook_set_current_page(notebook,
3779 gtk_notebook_get_n_pages(notebook) - 1);
3780 else
3781 gtk_notebook_prev_page(notebook);
3782 break;
3783 case XT_TAB_FIRST:
3784 gtk_notebook_set_current_page(notebook, 0);
3785 break;
3786 case XT_TAB_LAST:
3787 gtk_notebook_set_current_page(notebook, -1);
3788 break;
3789 default:
3790 return (XT_CB_PASSTHROUGH);
3793 return (XT_CB_HANDLED);
3796 /* jump to tab */
3797 x = args->i - 1;
3798 if (t->tab_id == x) {
3799 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3800 return (XT_CB_HANDLED);
3803 TAILQ_FOREACH(tt, &tabs, entry) {
3804 if (tt->tab_id == x) {
3805 gtk_notebook_set_current_page(notebook, x);
3806 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3807 if (tt->focus_wv)
3808 focus_webview(tt);
3812 return (XT_CB_HANDLED);
3816 command(struct tab *t, struct karg *args)
3818 char *s = NULL, *ss = NULL;
3819 GdkColor color;
3820 const gchar *uri;
3822 if (t == NULL || args == NULL) {
3823 show_oops_s("command invalid parameters");
3824 return (XT_CB_PASSTHROUGH);
3827 switch (args->i) {
3828 case '/':
3829 s = "/";
3830 break;
3831 case '?':
3832 s = "?";
3833 break;
3834 case ':':
3835 s = ":";
3836 break;
3837 case XT_CMD_OPEN:
3838 s = ":open ";
3839 break;
3840 case XT_CMD_TABNEW:
3841 s = ":tabnew ";
3842 break;
3843 case XT_CMD_OPEN_CURRENT:
3844 s = ":open ";
3845 /* FALL THROUGH */
3846 case XT_CMD_TABNEW_CURRENT:
3847 if (!s) /* FALL THROUGH? */
3848 s = ":tabnew ";
3849 if ((uri = get_uri(t->wv)) != NULL) {
3850 ss = g_strdup_printf("%s%s", s, uri);
3851 s = ss;
3853 break;
3854 default:
3855 show_oops(t, "command: invalid opcode %d", args->i);
3856 return (XT_CB_PASSTHROUGH);
3859 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3861 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3862 gdk_color_parse(XT_COLOR_WHITE, &color);
3863 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3864 show_cmd(t);
3865 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3866 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3868 if (ss)
3869 g_free(ss);
3871 return (XT_CB_HANDLED);
3875 * Return a new string with a download row (in html)
3876 * appended. Old string is freed.
3878 char *
3879 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3882 WebKitDownloadStatus stat;
3883 char *status_html = NULL, *cmd_html = NULL, *new_html;
3884 gdouble progress;
3885 char cur_sz[FMT_SCALED_STRSIZE];
3886 char tot_sz[FMT_SCALED_STRSIZE];
3887 char *xtp_prefix;
3889 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3891 /* All actions wil take this form:
3892 * xxxt://class/seskey
3894 xtp_prefix = g_strdup_printf("%s%d/%s/",
3895 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3897 stat = webkit_download_get_status(dl->download);
3899 switch (stat) {
3900 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3901 status_html = g_strdup_printf("Finished");
3902 cmd_html = g_strdup_printf(
3903 "<a href='%s%d/%d'>Remove</a>",
3904 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3905 break;
3906 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3907 /* gather size info */
3908 progress = 100 * webkit_download_get_progress(dl->download);
3910 fmt_scaled(
3911 webkit_download_get_current_size(dl->download), cur_sz);
3912 fmt_scaled(
3913 webkit_download_get_total_size(dl->download), tot_sz);
3915 status_html = g_strdup_printf(
3916 "<div style='width: 100%%' align='center'>"
3917 "<div class='progress-outer'>"
3918 "<div class='progress-inner' style='width: %.2f%%'>"
3919 "</div></div></div>"
3920 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3921 progress, cur_sz, tot_sz, progress);
3923 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3924 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3926 break;
3927 /* LLL */
3928 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3929 status_html = g_strdup_printf("Cancelled");
3930 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3931 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3932 break;
3933 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3934 status_html = g_strdup_printf("Error!");
3935 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3936 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3937 break;
3938 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3939 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3940 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3941 status_html = g_strdup_printf("Starting");
3942 break;
3943 default:
3944 show_oops(t, "%s: unknown download status", __func__);
3947 new_html = g_strdup_printf(
3948 "%s\n<tr><td>%s</td><td>%s</td>"
3949 "<td style='text-align:center'>%s</td></tr>\n",
3950 html, basename(webkit_download_get_destination_uri(dl->download)),
3951 status_html, cmd_html);
3952 g_free(html);
3954 if (status_html)
3955 g_free(status_html);
3957 if (cmd_html)
3958 g_free(cmd_html);
3960 g_free(xtp_prefix);
3962 return new_html;
3966 * update all download tabs apart from one. Pass NULL if
3967 * you want to update all.
3969 void
3970 update_download_tabs(struct tab *apart_from)
3972 struct tab *t;
3973 if (!updating_dl_tabs) {
3974 updating_dl_tabs = 1; /* stop infinite recursion */
3975 TAILQ_FOREACH(t, &tabs, entry)
3976 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3977 && (t != apart_from))
3978 xtp_page_dl(t, NULL);
3979 updating_dl_tabs = 0;
3984 * update all cookie tabs apart from one. Pass NULL if
3985 * you want to update all.
3987 void
3988 update_cookie_tabs(struct tab *apart_from)
3990 struct tab *t;
3991 if (!updating_cl_tabs) {
3992 updating_cl_tabs = 1; /* stop infinite recursion */
3993 TAILQ_FOREACH(t, &tabs, entry)
3994 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3995 && (t != apart_from))
3996 xtp_page_cl(t, NULL);
3997 updating_cl_tabs = 0;
4002 * update all history tabs apart from one. Pass NULL if
4003 * you want to update all.
4005 void
4006 update_history_tabs(struct tab *apart_from)
4008 struct tab *t;
4010 if (!updating_hl_tabs) {
4011 updating_hl_tabs = 1; /* stop infinite recursion */
4012 TAILQ_FOREACH(t, &tabs, entry)
4013 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4014 && (t != apart_from))
4015 xtp_page_hl(t, NULL);
4016 updating_hl_tabs = 0;
4020 /* cookie management XTP page */
4022 xtp_page_cl(struct tab *t, struct karg *args)
4024 char *header, *body, *footer, *page, *tmp;
4025 int i = 1; /* all ids start 1 */
4026 GSList *sc, *pc, *pc_start;
4027 SoupCookie *c;
4028 char *type, *table_headers;
4029 char *last_domain = strdup("");
4031 DNPRINTF(XT_D_CMD, "%s", __func__);
4033 if (t == NULL) {
4034 show_oops_s("%s invalid parameters", __func__);
4035 return (1);
4037 /* mark this tab as cookie jar */
4038 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4040 /* Generate a new session key */
4041 if (!updating_cl_tabs)
4042 generate_xtp_session_key(&cl_session_key);
4044 /* header */
4045 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4046 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4047 "</head><body><h1>Cookie Jar</h1>\n");
4049 /* table headers */
4050 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4051 "<th>Type</th>"
4052 "<th>Name</th>"
4053 "<th>Value</th>"
4054 "<th>Path</th>"
4055 "<th>Expires</th>"
4056 "<th>Secure</th>"
4057 "<th>HTTP<br />only</th>"
4058 "<th>Rm</th></tr>\n");
4060 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4061 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4062 pc_start = pc;
4064 body = NULL;
4065 for (; sc; sc = sc->next) {
4066 c = sc->data;
4068 if (strcmp(last_domain, c->domain) != 0) {
4069 /* new domain */
4070 free(last_domain);
4071 last_domain = strdup(c->domain);
4073 if (body != NULL) {
4074 tmp = body;
4075 body = g_strdup_printf("%s</table></div>"
4076 "<h2>%s</h2>%s\n",
4077 body, c->domain, table_headers);
4078 g_free(tmp);
4079 } else {
4080 /* first domain */
4081 body = g_strdup_printf("<h2>%s</h2>%s\n",
4082 c->domain, table_headers);
4086 type = "Session";
4087 for (pc = pc_start; pc; pc = pc->next)
4088 if (soup_cookie_equal(pc->data, c)) {
4089 type = "Session + Persistent";
4090 break;
4093 tmp = body;
4094 body = g_strdup_printf(
4095 "%s\n<tr>"
4096 "<td style='width: text-align: center'>%s</td>"
4097 "<td style='width: 1px'>%s</td>"
4098 "<td style='width=70%%;overflow: visible'>"
4099 " <textarea rows='4'>%s</textarea>"
4100 "</td>"
4101 "<td>%s</td>"
4102 "<td>%s</td>"
4103 "<td style='width: 1px; text-align: center'>%d</td>"
4104 "<td style='width: 1px; text-align: center'>%d</td>"
4105 "<td style='width: 1px; text-align: center'>"
4106 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4107 body,
4108 type,
4109 c->name,
4110 c->value,
4111 c->path,
4112 c->expires ?
4113 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4114 c->secure,
4115 c->http_only,
4117 XT_XTP_STR,
4118 XT_XTP_CL,
4119 cl_session_key,
4120 XT_XTP_CL_REMOVE,
4124 g_free(tmp);
4125 i++;
4128 soup_cookies_free(sc);
4129 soup_cookies_free(pc);
4131 /* small message if there are none */
4132 if (i == 1) {
4133 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4134 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4137 /* footer */
4138 footer = g_strdup_printf("</table></div></body></html>");
4140 page = g_strdup_printf("%s%s%s", header, body, footer);
4142 g_free(header);
4143 g_free(body);
4144 g_free(footer);
4145 g_free(table_headers);
4146 g_free(last_domain);
4148 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4149 update_cookie_tabs(t);
4151 g_free(page);
4153 return (0);
4157 xtp_page_hl(struct tab *t, struct karg *args)
4159 char *header, *body, *footer, *page, *tmp;
4160 struct history *h;
4161 int i = 1; /* all ids start 1 */
4163 DNPRINTF(XT_D_CMD, "%s", __func__);
4165 if (t == NULL) {
4166 show_oops_s("%s invalid parameters", __func__);
4167 return (1);
4170 /* mark this tab as history manager */
4171 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4173 /* Generate a new session key */
4174 if (!updating_hl_tabs)
4175 generate_xtp_session_key(&hl_session_key);
4177 /* header */
4178 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4179 "<title>History</title>\n"
4180 "%s"
4181 "</head>"
4182 "<h1>History</h1>\n",
4183 XT_PAGE_STYLE);
4185 /* body */
4186 body = g_strdup_printf("<div align='center'><table><tr>"
4187 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4189 RB_FOREACH_REVERSE(h, history_list, &hl) {
4190 tmp = body;
4191 body = g_strdup_printf(
4192 "%s\n<tr>"
4193 "<td><a href='%s'>%s</a></td>"
4194 "<td>%s</td>"
4195 "<td style='text-align: center'>"
4196 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4197 body, h->uri, h->uri, h->title,
4198 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4199 XT_XTP_HL_REMOVE, i);
4201 g_free(tmp);
4202 i++;
4205 /* small message if there are none */
4206 if (i == 1) {
4207 tmp = body;
4208 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4209 "colspan='3'>No History</td></tr>\n", body);
4210 g_free(tmp);
4213 /* footer */
4214 footer = g_strdup_printf("</table></div></body></html>");
4216 page = g_strdup_printf("%s%s%s", header, body, footer);
4219 * update all history manager tabs as the xtp session
4220 * key has now changed. No need to update the current tab.
4221 * Already did that above.
4223 update_history_tabs(t);
4225 g_free(header);
4226 g_free(body);
4227 g_free(footer);
4229 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4230 g_free(page);
4232 return (0);
4236 * Generate a web page detailing the status of any downloads
4239 xtp_page_dl(struct tab *t, struct karg *args)
4241 struct download *dl;
4242 char *header, *body, *footer, *page, *tmp;
4243 char *ref;
4244 int n_dl = 1;
4246 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4248 if (t == NULL) {
4249 show_oops_s("%s invalid parameters", __func__);
4250 return (1);
4252 /* mark as a download manager tab */
4253 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4256 * Generate a new session key for next page instance.
4257 * This only happens for the top level call to xtp_page_dl()
4258 * in which case updating_dl_tabs is 0.
4260 if (!updating_dl_tabs)
4261 generate_xtp_session_key(&dl_session_key);
4263 /* header - with refresh so as to update */
4264 if (refresh_interval >= 1)
4265 ref = g_strdup_printf(
4266 "<meta http-equiv='refresh' content='%u"
4267 ";url=%s%d/%s/%d' />\n",
4268 refresh_interval,
4269 XT_XTP_STR,
4270 XT_XTP_DL,
4271 dl_session_key,
4272 XT_XTP_DL_LIST);
4273 else
4274 ref = g_strdup("");
4277 header = g_strdup_printf(
4278 "%s\n<head>"
4279 "<title>Downloads</title>\n%s%s</head>\n",
4280 XT_DOCTYPE XT_HTML_TAG,
4281 ref,
4282 XT_PAGE_STYLE);
4284 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4285 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4286 "</p><table><tr><th style='width: 60%%'>"
4287 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4288 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4290 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4291 body = xtp_page_dl_row(t, body, dl);
4292 n_dl++;
4295 /* message if no downloads in list */
4296 if (n_dl == 1) {
4297 tmp = body;
4298 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4299 " style='text-align: center'>"
4300 "No downloads</td></tr>\n", body);
4301 g_free(tmp);
4304 /* footer */
4305 footer = g_strdup_printf("</table></div></body></html>");
4307 page = g_strdup_printf("%s%s%s", header, body, footer);
4311 * update all download manager tabs as the xtp session
4312 * key has now changed. No need to update the current tab.
4313 * Already did that above.
4315 update_download_tabs(t);
4317 g_free(ref);
4318 g_free(header);
4319 g_free(body);
4320 g_free(footer);
4322 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4323 g_free(page);
4325 return (0);
4329 search(struct tab *t, struct karg *args)
4331 gboolean d;
4333 if (t == NULL || args == NULL) {
4334 show_oops_s("search invalid parameters");
4335 return (1);
4337 if (t->search_text == NULL) {
4338 if (global_search == NULL)
4339 return (XT_CB_PASSTHROUGH);
4340 else {
4341 t->search_text = g_strdup(global_search);
4342 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4343 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4347 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4348 t->tab_id, args->i, t->search_forward, t->search_text);
4350 switch (args->i) {
4351 case XT_SEARCH_NEXT:
4352 d = t->search_forward;
4353 break;
4354 case XT_SEARCH_PREV:
4355 d = !t->search_forward;
4356 break;
4357 default:
4358 return (XT_CB_PASSTHROUGH);
4361 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4363 return (XT_CB_HANDLED);
4366 struct settings_args {
4367 char **body;
4368 int i;
4371 void
4372 print_setting(struct settings *s, char *val, void *cb_args)
4374 char *tmp, *color;
4375 struct settings_args *sa = cb_args;
4377 if (sa == NULL)
4378 return;
4380 if (s->flags & XT_SF_RUNTIME)
4381 color = "#22cc22";
4382 else
4383 color = "#cccccc";
4385 tmp = *sa->body;
4386 *sa->body = g_strdup_printf(
4387 "%s\n<tr>"
4388 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4389 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4390 *sa->body,
4391 color,
4392 s->name,
4393 color,
4396 g_free(tmp);
4397 sa->i++;
4401 set(struct tab *t, struct karg *args)
4403 char *header, *body, *footer, *page, *tmp;
4404 int i = 1;
4405 struct settings_args sa;
4407 bzero(&sa, sizeof sa);
4408 sa.body = &body;
4410 /* header */
4411 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4412 "\n<head><title>Settings</title>\n"
4413 "</head><body><h1>Settings</h1>\n");
4415 /* body */
4416 body = g_strdup_printf("<div align='center'><table><tr>"
4417 "<th align='left'>Setting</th>"
4418 "<th align='left'>Value</th></tr>\n");
4420 settings_walk(print_setting, &sa);
4421 i = sa.i;
4423 /* small message if there are none */
4424 if (i == 1) {
4425 tmp = body;
4426 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4427 "colspan='2'>No settings</td></tr>\n", body);
4428 g_free(tmp);
4431 /* footer */
4432 footer = g_strdup_printf("</table></div></body></html>");
4434 page = g_strdup_printf("%s%s%s", header, body, footer);
4436 g_free(header);
4437 g_free(body);
4438 g_free(footer);
4440 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4442 return (XT_CB_PASSTHROUGH);
4446 session_save(struct tab *t, char *filename)
4448 struct karg a;
4449 int rv = 1;
4451 if (strlen(filename) == 0)
4452 goto done;
4454 if (filename[0] == '.' || filename[0] == '/')
4455 goto done;
4457 a.s = filename;
4458 if (save_tabs(t, &a))
4459 goto done;
4460 strlcpy(named_session, filename, sizeof named_session);
4462 rv = 0;
4463 done:
4464 return (rv);
4468 session_open(struct tab *t, char *filename)
4470 struct karg a;
4471 int rv = 1;
4473 if (strlen(filename) == 0)
4474 goto done;
4476 if (filename[0] == '.' || filename[0] == '/')
4477 goto done;
4479 a.s = filename;
4480 a.i = XT_SES_CLOSETABS;
4481 if (open_tabs(t, &a))
4482 goto done;
4484 strlcpy(named_session, filename, sizeof named_session);
4486 rv = 0;
4487 done:
4488 return (rv);
4492 session_delete(struct tab *t, char *filename)
4494 char file[PATH_MAX];
4495 int rv = 1;
4497 if (strlen(filename) == 0)
4498 goto done;
4500 if (filename[0] == '.' || filename[0] == '/')
4501 goto done;
4503 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4504 if (unlink(file))
4505 goto done;
4507 if (!strcmp(filename, named_session))
4508 strlcpy(named_session, XT_SAVED_TABS_FILE,
4509 sizeof named_session);
4511 rv = 0;
4512 done:
4513 return (rv);
4517 session_cmd(struct tab *t, struct karg *args)
4519 char *filename = args->s;
4521 if (t == NULL)
4522 return (1);
4524 if (args->i & XT_SHOW)
4525 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4526 XT_SAVED_TABS_FILE : named_session);
4527 else if (args->i & XT_SAVE) {
4528 if (session_save(t, filename)) {
4529 show_oops(t, "Can't save session: %s",
4530 filename ? filename : "INVALID");
4531 goto done;
4533 } else if (args->i & XT_OPEN) {
4534 if (session_open(t, filename)) {
4535 show_oops(t, "Can't open session: %s",
4536 filename ? filename : "INVALID");
4537 goto done;
4539 } else if (args->i & XT_DELETE) {
4540 if (session_delete(t, filename)) {
4541 show_oops(t, "Can't delete session: %s",
4542 filename ? filename : "INVALID");
4543 goto done;
4546 done:
4547 return (XT_CB_PASSTHROUGH);
4551 * Make a hardcopy of the page
4554 print_page(struct tab *t, struct karg *args)
4556 WebKitWebFrame *frame;
4557 GtkPageSetup *ps;
4558 GtkPrintOperation *op;
4559 GtkPrintOperationAction action;
4560 GtkPrintOperationResult print_res;
4561 GError *g_err = NULL;
4562 int marg_l, marg_r, marg_t, marg_b;
4564 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4566 ps = gtk_page_setup_new();
4567 op = gtk_print_operation_new();
4568 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4569 frame = webkit_web_view_get_main_frame(t->wv);
4571 /* the default margins are too small, so we will bump them */
4572 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4573 XT_PRINT_EXTRA_MARGIN;
4574 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4575 XT_PRINT_EXTRA_MARGIN;
4576 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4577 XT_PRINT_EXTRA_MARGIN;
4578 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4579 XT_PRINT_EXTRA_MARGIN;
4581 /* set margins */
4582 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4583 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4584 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4585 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4587 gtk_print_operation_set_default_page_setup(op, ps);
4589 /* this appears to free 'op' and 'ps' */
4590 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4592 /* check it worked */
4593 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4594 show_oops_s("can't print: %s", g_err->message);
4595 g_error_free (g_err);
4596 return (1);
4599 return (0);
4603 go_home(struct tab *t, struct karg *args)
4605 load_uri(t, home);
4606 return (0);
4610 restart(struct tab *t, struct karg *args)
4612 struct karg a;
4614 a.s = XT_RESTART_TABS_FILE;
4615 save_tabs(t, &a);
4616 execvp(start_argv[0], start_argv);
4617 /* NOTREACHED */
4619 return (0);
4622 #define CTRL GDK_CONTROL_MASK
4623 #define MOD1 GDK_MOD1_MASK
4624 #define SHFT GDK_SHIFT_MASK
4626 /* inherent to GTK not all keys will be caught at all times */
4627 /* XXX sort key bindings */
4628 struct key_binding {
4629 char *cmd;
4630 guint mask;
4631 guint use_in_entry;
4632 guint key;
4633 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4634 } keys[] = {
4635 { "cookiejar", MOD1, 0, GDK_j },
4636 { "downloadmgr", MOD1, 0, GDK_d },
4637 { "history", MOD1, 0, GDK_h },
4638 { "print", CTRL, 0, GDK_p },
4639 { "search", 0, 0, GDK_slash },
4640 { "searchb", 0, 0, GDK_question },
4641 { "command", 0, 0, GDK_colon },
4642 { "quit", CTRL, 0, GDK_q },
4643 { "restart", MOD1, 0, GDK_q },
4644 { "js toggle", CTRL, 0, GDK_j },
4645 { "cookie toggle", MOD1, 0, GDK_c },
4646 { "togglesrc", CTRL, 0, GDK_s },
4647 { "yankuri", 0, 0, GDK_y },
4648 { "pasteuricur", 0, 0, GDK_p },
4649 { "pasteurinew", 0, 0, GDK_P },
4651 /* search */
4652 { "searchnext", 0, 0, GDK_n },
4653 { "searchprevious", 0, 0, GDK_N },
4655 /* focus */
4656 { "focusaddress", 0, 0, GDK_F6 },
4657 { "focussearch", 0, 0, GDK_F7 },
4659 /* hinting */
4660 { "hinting", 0, 0, GDK_f },
4662 /* custom stylesheet */
4663 { "userstyle", 0, 0, GDK_i },
4665 /* navigation */
4666 { "goback", 0, 0, GDK_BackSpace },
4667 { "goback", MOD1, 0, GDK_Left },
4668 { "goforward", SHFT, 0, GDK_BackSpace },
4669 { "goforward", MOD1, 0, GDK_Right },
4670 { "reload", 0, 0, GDK_F5 },
4671 { "reload", CTRL, 0, GDK_r },
4672 { "reloadforce", CTRL, 0, GDK_R },
4673 { "reload", CTRL, 0, GDK_l },
4674 { "favorites", MOD1, 1, GDK_f },
4676 /* vertical movement */
4677 { "scrolldown", 0, 0, GDK_j },
4678 { "scrolldown", 0, 0, GDK_Down },
4679 { "scrollup", 0, 0, GDK_Up },
4680 { "scrollup", 0, 0, GDK_k },
4681 { "scrollbottom", 0, 0, GDK_G },
4682 { "scrollbottom", 0, 0, GDK_End },
4683 { "scrolltop", 0, 0, GDK_Home },
4684 { "scrolltop", 0, 0, GDK_g },
4685 { "scrollpagedown", 0, 0, GDK_space },
4686 { "scrollpagedown", CTRL, 0, GDK_f },
4687 { "scrollhalfdown", CTRL, 0, GDK_d },
4688 { "scrollpagedown", 0, 0, GDK_Page_Down },
4689 { "scrollpageup", 0, 0, GDK_Page_Up },
4690 { "scrollpageup", CTRL, 0, GDK_b },
4691 { "scrollhalfup", CTRL, 0, GDK_u },
4692 /* horizontal movement */
4693 { "scrollright", 0, 0, GDK_l },
4694 { "scrollright", 0, 0, GDK_Right },
4695 { "scrollleft", 0, 0, GDK_Left },
4696 { "scrollleft", 0, 0, GDK_h },
4697 { "scrollfarright", 0, 0, GDK_dollar },
4698 { "scrollfarleft", 0, 0, GDK_0 },
4700 /* tabs */
4701 { "tabnew", CTRL, 0, GDK_t },
4702 { "tabclose", CTRL, 1, GDK_w },
4703 { "tabundoclose", 0, 0, GDK_U },
4704 { "tabgoto1", CTRL, 0, GDK_1 },
4705 { "tabgoto2", CTRL, 0, GDK_2 },
4706 { "tabgoto3", CTRL, 0, GDK_3 },
4707 { "tabgoto4", CTRL, 0, GDK_4 },
4708 { "tabgoto5", CTRL, 0, GDK_5 },
4709 { "tabgoto6", CTRL, 0, GDK_6 },
4710 { "tabgoto7", CTRL, 0, GDK_7 },
4711 { "tabgoto8", CTRL, 0, GDK_8 },
4712 { "tabgoto9", CTRL, 0, GDK_9 },
4713 { "tabgoto10", CTRL, 0, GDK_0 },
4714 { "tabfirst", CTRL, 0, GDK_less },
4715 { "tablast", CTRL, 0, GDK_greater },
4716 { "tabprevious", CTRL, 0, GDK_Left },
4717 { "tabnext", CTRL, 0, GDK_Right },
4718 { "focusout", CTRL, 0, GDK_minus },
4719 { "focusin", CTRL, 0, GDK_plus },
4720 { "focusin", CTRL, 0, GDK_equal },
4722 /* command aliases (handy when -S flag is used) */
4723 { "promptopen", 0, 0, GDK_F9 },
4724 { "promptopencurrent", 0, 0, GDK_F10 },
4725 { "prompttabnew", 0, 0, GDK_F11 },
4726 { "prompttabnewcurrent",0, 0, GDK_F12 },
4728 TAILQ_HEAD(keybinding_list, key_binding);
4730 void
4731 walk_kb(struct settings *s,
4732 void (*cb)(struct settings *, char *, void *), void *cb_args)
4734 struct key_binding *k;
4735 char str[1024];
4737 if (s == NULL || cb == NULL) {
4738 show_oops_s("walk_kb invalid parameters");
4739 return;
4742 TAILQ_FOREACH(k, &kbl, entry) {
4743 if (k->cmd == NULL)
4744 continue;
4745 str[0] = '\0';
4747 /* sanity */
4748 if (gdk_keyval_name(k->key) == NULL)
4749 continue;
4751 strlcat(str, k->cmd, sizeof str);
4752 strlcat(str, ",", sizeof str);
4754 if (k->mask & GDK_SHIFT_MASK)
4755 strlcat(str, "S-", sizeof str);
4756 if (k->mask & GDK_CONTROL_MASK)
4757 strlcat(str, "C-", sizeof str);
4758 if (k->mask & GDK_MOD1_MASK)
4759 strlcat(str, "M1-", sizeof str);
4760 if (k->mask & GDK_MOD2_MASK)
4761 strlcat(str, "M2-", sizeof str);
4762 if (k->mask & GDK_MOD3_MASK)
4763 strlcat(str, "M3-", sizeof str);
4764 if (k->mask & GDK_MOD4_MASK)
4765 strlcat(str, "M4-", sizeof str);
4766 if (k->mask & GDK_MOD5_MASK)
4767 strlcat(str, "M5-", sizeof str);
4769 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4770 cb(s, str, cb_args);
4773 void
4774 init_keybindings(void)
4776 int i;
4777 struct key_binding *k;
4779 for (i = 0; i < LENGTH(keys); i++) {
4780 k = g_malloc0(sizeof *k);
4781 k->cmd = keys[i].cmd;
4782 k->mask = keys[i].mask;
4783 k->use_in_entry = keys[i].use_in_entry;
4784 k->key = keys[i].key;
4785 TAILQ_INSERT_HEAD(&kbl, k, entry);
4787 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4788 k->cmd ? k->cmd : "unnamed key");
4792 void
4793 keybinding_clearall(void)
4795 struct key_binding *k, *next;
4797 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4798 next = TAILQ_NEXT(k, entry);
4799 if (k->cmd == NULL)
4800 continue;
4802 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4803 k->cmd ? k->cmd : "unnamed key");
4804 TAILQ_REMOVE(&kbl, k, entry);
4805 g_free(k);
4810 keybinding_add(char *kb, char *value, struct key_binding *orig)
4812 struct key_binding *k;
4813 guint keyval, mask = 0;
4814 int i;
4816 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->cmd);
4818 if (orig == NULL)
4819 return (1);
4820 if (strcmp(kb, orig->cmd))
4821 return (1);
4823 /* find modifier keys */
4824 if (strstr(value, "S-"))
4825 mask |= GDK_SHIFT_MASK;
4826 if (strstr(value, "C-"))
4827 mask |= GDK_CONTROL_MASK;
4828 if (strstr(value, "M1-"))
4829 mask |= GDK_MOD1_MASK;
4830 if (strstr(value, "M2-"))
4831 mask |= GDK_MOD2_MASK;
4832 if (strstr(value, "M3-"))
4833 mask |= GDK_MOD3_MASK;
4834 if (strstr(value, "M4-"))
4835 mask |= GDK_MOD4_MASK;
4836 if (strstr(value, "M5-"))
4837 mask |= GDK_MOD5_MASK;
4839 /* find keyname */
4840 for (i = strlen(value) - 1; i > 0; i--)
4841 if (value[i] == '-')
4842 value = &value[i + 1];
4844 /* validate keyname */
4845 keyval = gdk_keyval_from_name(value);
4846 if (keyval == GDK_VoidSymbol) {
4847 warnx("invalid keybinding name %s", value);
4848 return (1);
4850 /* must run this test too, gtk+ doesn't handle 10 for example */
4851 if (gdk_keyval_name(keyval) == NULL) {
4852 warnx("invalid keybinding name %s", value);
4853 return (1);
4856 /* make sure it isn't a dupe */
4857 TAILQ_FOREACH(k, &kbl, entry)
4858 if (k->key == keyval && k->mask == mask) {
4859 warnx("duplicate keybinding for %s", value);
4860 return (1);
4863 /* add keyname */
4864 k = g_malloc0(sizeof *k);
4865 k->cmd = orig->cmd;
4866 k->mask = mask;
4867 k->use_in_entry = orig->use_in_entry;
4868 k->key = keyval;
4870 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4871 k->name,
4872 k->mask,
4873 k->use_in_entry,
4874 k->key);
4875 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4876 k->name, gdk_keyval_name(keyval));
4878 TAILQ_INSERT_HEAD(&kbl, k, entry);
4880 return (0);
4884 add_kb(struct settings *s, char *entry)
4886 int i;
4887 char *kb, *value;
4889 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4891 /* clearall is special */
4892 if (!strcmp(entry, "clearall")) {
4893 keybinding_clearall();
4894 return (0);
4897 kb = strstr(entry, ",");
4898 if (kb == NULL)
4899 return (1);
4900 *kb = '\0';
4901 value = kb + 1;
4903 /* make sure it is a valid keybinding */
4904 for (i = 0; i < LENGTH(keys); i++)
4905 if (keys[i].cmd && !strcmp(entry, keys[i].cmd)) {
4906 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4907 keys[i].cmd,
4908 keys[i].mask,
4909 keys[i].use_in_entry,
4910 keys[i].key);
4912 return (keybinding_add(entry, value, &keys[i]));
4915 return (1);
4918 struct cmd {
4919 char *cmd;
4920 int level;
4921 int (*func)(struct tab *, struct karg *);
4922 struct karg arg;
4923 bool userarg; /* allow free text arg */
4924 } cmds[] = {
4925 { "command", 0, command, {.i = ':'}, FALSE },
4926 { "search", 0, command, {.i = '/'}, FALSE },
4927 { "searchb", 0, command, {.i = '?'}, FALSE },
4928 { "togglesrc", 0, toggle_src, {0}, FALSE },
4930 /* yanking and pasting */
4931 { "yankuri", 0, yank_uri, {0}, FALSE },
4932 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4933 { "pasteuricur", 0, paste_uri, {.i = XT_PASTE_CURRENT_TAB}, FALSE },
4934 { "pasteurinew", 0, paste_uri, {.i = XT_PASTE_NEW_TAB}, FALSE },
4936 /* search */
4937 { "searchnext", 0, search, {.i = XT_SEARCH_NEXT}, FALSE },
4938 { "searchprevious", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
4939 { "searchprev", 0, search, {.i = XT_SEARCH_PREV}, FALSE },
4941 /* focus */
4942 { "focusaddress", 0, focus, {.i = XT_FOCUS_URI}, FALSE },
4943 { "focussearch", 0, focus, {.i = XT_FOCUS_SEARCH}, FALSE },
4945 /* hinting */
4946 { "hinting", 0, hint, {.i = 0}, FALSE },
4948 /* custom stylesheet */
4949 { "userstyle", 0, userstyle, {.i = 0 }, FALSE },
4951 /* navigation */
4952 { "goback", 0, navaction, {.i = XT_NAV_BACK}, FALSE },
4953 { "goforward", 0, navaction, {.i = XT_NAV_FORWARD}, FALSE },
4954 { "reload", 0, navaction, {.i = XT_NAV_RELOAD}, FALSE },
4955 { "reloadforce", 0, navaction, {.i = XT_NAV_RELOAD_CACHE}, FALSE },
4957 /* vertical movement */
4958 { "scrolldown", 0, move, {.i = XT_MOVE_DOWN}, FALSE },
4959 { "scrollup", 0, move, {.i = XT_MOVE_UP}, FALSE },
4960 { "scrollbottom", 0, move, {.i = XT_MOVE_BOTTOM}, FALSE },
4961 { "scrolltop", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4962 { "1", 0, move, {.i = XT_MOVE_TOP}, FALSE },
4963 { "scrollhalfdown", 0, move, {.i = XT_MOVE_HALFDOWN},FALSE },
4964 { "scrollhalfup", 0, move, {.i = XT_MOVE_HALFUP}, FALSE },
4965 { "scrollpagedown", 0, move, {.i = XT_MOVE_PAGEDOWN},FALSE },
4966 { "scrollpageup", 0, move, {.i = XT_MOVE_PAGEUP}, FALSE },
4967 /* horizontal movement */
4968 { "scrollright", 0, move, {.i = XT_MOVE_RIGHT}, FALSE },
4969 { "scrollleft", 0, move, {.i = XT_MOVE_LEFT}, FALSE },
4970 { "scrollfarright", 0, move, {.i = XT_MOVE_FARRIGHT},FALSE },
4971 { "scrollfarleft", 0, move, {.i = XT_MOVE_FARLEFT}, FALSE },
4974 { "favorites", 0, xtp_page_fl, {0}, FALSE },
4975 { "fav", 0, xtp_page_fl, {0}, FALSE },
4976 { "favadd", 0, add_favorite, {0}, FALSE },
4978 { "quit", 0, quit, {0}, FALSE },
4979 { "q!", 0, quit, {0}, FALSE },
4980 { "qa", 0, quit, {0}, FALSE },
4981 { "qa!", 0, quit, {0}, FALSE },
4982 { "w", 0, save_tabs, {0}, FALSE },
4983 { "wq", 0, save_tabs_and_quit, {0}, FALSE },
4984 { "wq!", 0, save_tabs_and_quit, {0}, FALSE },
4985 { "help", 0, help, {0}, FALSE },
4986 { "about", 0, about, {0}, FALSE },
4987 { "stats", 0, stats, {0}, FALSE },
4988 { "version", 0, about, {0}, FALSE },
4989 { "cookiejar", 0, xtp_page_cl, {0}, FALSE },
4991 /* js command */
4992 { "js", 0, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4993 { "save", 1, js_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
4994 { "domain", 2, js_cmd, {.i = XT_SAVE | XT_WL_TOPLEVEL}, FALSE },
4995 { "fqdn", 2, js_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
4996 { "show", 1, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4997 { "all", 2, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
4998 { "persistent", 2, js_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT}, FALSE },
4999 { "session", 2, js_cmd, {.i = XT_SHOW | XT_WL_SESSION}, FALSE },
5000 { "toggle", 1, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5001 { "domain", 2, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL}, FALSE },
5002 { "fqdn", 2, js_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5004 /* cookie command */
5005 { "cookie", 0, cookie_cmd, {0}, FALSE },
5006 { "save", 1, cookie_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
5007 { "domain", 2, cookie_cmd, {.i = XT_SAVE | XT_WL_TOPLEVEL}, FALSE },
5008 { "fqdn", 2, cookie_cmd, {.i = XT_SAVE | XT_WL_FQDN}, FALSE },
5009 { "show", 1, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
5010 { "all", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION}, FALSE },
5011 { "persistent", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_PERSISTENT}, FALSE },
5012 { "session", 2, cookie_cmd, {.i = XT_SHOW | XT_WL_SESSION}, FALSE },
5013 { "toggle", 1, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5014 { "domain", 2, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL}, FALSE },
5015 { "fqdn", 2, cookie_cmd, {.i = XT_WL_TOGGLE | XT_WL_FQDN}, FALSE },
5017 /* cert command */
5018 { "cert", 0, cert_cmd, {.i = XT_SHOW}, FALSE },
5019 { "save", 1, cert_cmd, {.i = XT_SAVE}, FALSE },
5020 { "show", 1, cert_cmd, {.i = XT_SHOW}, FALSE },
5022 { "ca", 0, ca_cmd, {0}, FALSE },
5023 { "downloadmgr", 0, xtp_page_dl, {0}, FALSE },
5024 { "dl", 0, xtp_page_dl, {0}, FALSE },
5025 { "h", 0, xtp_page_hl, {0}, FALSE },
5026 { "hist", 0, xtp_page_hl, {0}, FALSE },
5027 { "history", 0, xtp_page_hl, {0}, FALSE },
5028 { "home", 0, go_home, {0}, FALSE },
5029 { "restart", 0, restart, {0}, FALSE },
5030 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5031 { "urlh", 0, urlaction, {.i = XT_URL_HIDE}, FALSE },
5032 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5033 { "urls", 0, urlaction, {.i = XT_URL_SHOW}, FALSE },
5034 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5035 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE}, FALSE },
5036 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5037 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW}, FALSE },
5039 { "print", 0, print_page, {0}, FALSE },
5041 /* tabs */
5042 { "o", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5043 { "op", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5044 { "open", 0, tabaction, {.i = XT_TAB_OPEN}, TRUE },
5045 { "tabnew", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5046 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5047 { "tabe", 0, tabaction, {.i = XT_TAB_NEW}, TRUE },
5048 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5049 { "tabundoclose", 0, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
5050 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE}, FALSE },
5051 { "tabshow", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5052 { "tabs", 0, tabaction, {.i = XT_TAB_SHOW}, FALSE },
5053 { "tabhide", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5054 { "tabh", 0, tabaction, {.i = XT_TAB_HIDE}, FALSE },
5055 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5056 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT}, FALSE },
5057 /* XXX add count to these commands */
5058 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5059 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5060 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5061 { "tabr", 0, movetab, {.i = XT_TAB_FIRST}, FALSE },
5062 { "tablast", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5063 { "tabl", 0, movetab, {.i = XT_TAB_LAST}, FALSE },
5064 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5065 { "tabprev", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5066 { "tabp", 0, movetab, {.i = XT_TAB_PREV}, FALSE },
5067 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT}, FALSE },
5068 { "tabn", 0, movetab, {.i = XT_TAB_NEXT}, FALSE },
5069 { "tabgoto1", 0, movetab, {.i = 1}, FALSE },
5070 { "tabgoto2", 0, movetab, {.i = 2}, FALSE },
5071 { "tabgoto3", 0, movetab, {.i = 3}, FALSE },
5072 { "tabgoto4", 0, movetab, {.i = 4}, FALSE },
5073 { "tabgoto5", 0, movetab, {.i = 5}, FALSE },
5074 { "tabgoto6", 0, movetab, {.i = 6}, FALSE },
5075 { "tabgoto7", 0, movetab, {.i = 7}, FALSE },
5076 { "tabgoto8", 0, movetab, {.i = 8}, FALSE },
5077 { "tabgoto9", 0, movetab, {.i = 9}, FALSE },
5078 { "tabgoto10", 0, movetab, {.i = 10}, FALSE },
5079 { "focusout", 0, resizetab, {.i = -1}, FALSE },
5080 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5081 { "focusin", 0, resizetab, {.i = 1}, FALSE },
5083 /* command aliases (handy when -S flag is used) */
5084 { "promptopen", 0, command, {.i = XT_CMD_OPEN}, FALSE },
5085 { "promptopencurrent", 0, command, {.i = XT_CMD_OPEN_CURRENT}, FALSE },
5086 { "prompttabnew", 0, command, {.i = XT_CMD_TABNEW}, FALSE },
5087 { "prompttabnewcurrent",0, command, {.i = XT_CMD_TABNEW_CURRENT}, FALSE },
5089 /* settings */
5090 { "set", 0, set, {0}, FALSE },
5091 { "fullscreen", 0, fullscreen, {0}, FALSE },
5092 { "f", 0, fullscreen, {0}, FALSE },
5094 /* sessions */
5095 { "session", 0, session_cmd, {.i = XT_SHOW}, FALSE },
5096 { "delete", 1, session_cmd, {.i = XT_DELETE}, TRUE },
5097 { "open", 1, session_cmd, {.i = XT_OPEN}, TRUE },
5098 { "save", 1, session_cmd, {.i = XT_SAVE}, TRUE },
5099 { "show", 1, session_cmd, {.i = XT_SHOW}, FALSE },
5102 struct {
5103 int index;
5104 int len;
5105 gchar *list[256];
5106 } cmd_status = {-1, 0};
5108 gboolean
5109 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5111 struct karg a;
5113 hide_oops(t);
5115 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5116 /* go backward */
5117 a.i = XT_NAV_BACK;
5118 navaction(t, &a);
5120 return (TRUE);
5121 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5122 /* go forward */
5123 a.i = XT_NAV_FORWARD;
5124 navaction(t, &a);
5126 return (TRUE);
5129 return (FALSE);
5132 gboolean
5133 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5135 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5137 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5138 delete_tab(t);
5140 return (FALSE);
5144 * cancel, remove, etc. downloads
5146 void
5147 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5149 struct download find, *d = NULL;
5151 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5153 /* some commands require a valid download id */
5154 if (cmd != XT_XTP_DL_LIST) {
5155 /* lookup download in question */
5156 find.id = id;
5157 d = RB_FIND(download_list, &downloads, &find);
5159 if (d == NULL) {
5160 show_oops(t, "%s: no such download", __func__);
5161 return;
5165 /* decide what to do */
5166 switch (cmd) {
5167 case XT_XTP_DL_CANCEL:
5168 webkit_download_cancel(d->download);
5169 break;
5170 case XT_XTP_DL_REMOVE:
5171 webkit_download_cancel(d->download); /* just incase */
5172 g_object_unref(d->download);
5173 RB_REMOVE(download_list, &downloads, d);
5174 break;
5175 case XT_XTP_DL_LIST:
5176 /* Nothing */
5177 break;
5178 default:
5179 show_oops(t, "%s: unknown command", __func__);
5180 break;
5182 xtp_page_dl(t, NULL);
5186 * Actions on history, only does one thing for now, but
5187 * we provide the function for future actions
5189 void
5190 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5192 struct history *h, *next;
5193 int i = 1;
5195 switch (cmd) {
5196 case XT_XTP_HL_REMOVE:
5197 /* walk backwards, as listed in reverse */
5198 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5199 next = RB_PREV(history_list, &hl, h);
5200 if (id == i) {
5201 RB_REMOVE(history_list, &hl, h);
5202 g_free((gpointer) h->title);
5203 g_free((gpointer) h->uri);
5204 g_free(h);
5205 break;
5207 i++;
5209 break;
5210 case XT_XTP_HL_LIST:
5211 /* Nothing - just xtp_page_hl() below */
5212 break;
5213 default:
5214 show_oops(t, "%s: unknown command", __func__);
5215 break;
5218 xtp_page_hl(t, NULL);
5221 /* remove a favorite */
5222 void
5223 remove_favorite(struct tab *t, int index)
5225 char file[PATH_MAX], *title, *uri = NULL;
5226 char *new_favs, *tmp;
5227 FILE *f;
5228 int i;
5229 size_t len, lineno;
5231 /* open favorites */
5232 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5234 if ((f = fopen(file, "r")) == NULL) {
5235 show_oops(t, "%s: can't open favorites: %s",
5236 __func__, strerror(errno));
5237 return;
5240 /* build a string which will become the new favroites file */
5241 new_favs = g_strdup_printf("%s", "");
5243 for (i = 1;;) {
5244 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5245 if (feof(f) || ferror(f))
5246 break;
5247 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5248 if (len == 0) {
5249 free(title);
5250 title = NULL;
5251 continue;
5254 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5255 if (feof(f) || ferror(f)) {
5256 show_oops(t, "%s: can't parse favorites %s",
5257 __func__, strerror(errno));
5258 goto clean;
5262 /* as long as this isn't the one we are deleting add to file */
5263 if (i != index) {
5264 tmp = new_favs;
5265 new_favs = g_strdup_printf("%s%s\n%s\n",
5266 new_favs, title, uri);
5267 g_free(tmp);
5270 free(uri);
5271 uri = NULL;
5272 free(title);
5273 title = NULL;
5274 i++;
5276 fclose(f);
5278 /* write back new favorites file */
5279 if ((f = fopen(file, "w")) == NULL) {
5280 show_oops(t, "%s: can't open favorites: %s",
5281 __func__, strerror(errno));
5282 goto clean;
5285 fwrite(new_favs, strlen(new_favs), 1, f);
5286 fclose(f);
5288 clean:
5289 if (uri)
5290 free(uri);
5291 if (title)
5292 free(title);
5294 g_free(new_favs);
5297 void
5298 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5300 switch (cmd) {
5301 case XT_XTP_FL_LIST:
5302 /* nothing, just the below call to xtp_page_fl() */
5303 break;
5304 case XT_XTP_FL_REMOVE:
5305 remove_favorite(t, arg);
5306 break;
5307 default:
5308 show_oops(t, "%s: invalid favorites command", __func__);
5309 break;
5312 xtp_page_fl(t, NULL);
5315 void
5316 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5318 switch (cmd) {
5319 case XT_XTP_CL_LIST:
5320 /* nothing, just xtp_page_cl() */
5321 break;
5322 case XT_XTP_CL_REMOVE:
5323 remove_cookie(arg);
5324 break;
5325 default:
5326 show_oops(t, "%s: unknown cookie xtp command", __func__);
5327 break;
5330 xtp_page_cl(t, NULL);
5333 /* link an XTP class to it's session key and handler function */
5334 struct xtp_despatch {
5335 uint8_t xtp_class;
5336 char **session_key;
5337 void (*handle_func)(struct tab *, uint8_t, int);
5340 struct xtp_despatch xtp_despatches[] = {
5341 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5342 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5343 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5344 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5345 { NULL, NULL, NULL }
5349 * is the url xtp protocol? (xxxt://)
5350 * if so, parse and despatch correct bahvior
5353 parse_xtp_url(struct tab *t, const char *url)
5355 char *dup = NULL, *p, *last;
5356 uint8_t n_tokens = 0;
5357 char *tokens[4] = {NULL, NULL, NULL, ""};
5358 struct xtp_despatch *dsp, *dsp_match = NULL;
5359 uint8_t req_class;
5362 * tokens array meaning:
5363 * tokens[0] = class
5364 * tokens[1] = session key
5365 * tokens[2] = action
5366 * tokens[3] = optional argument
5369 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5371 /*xtp tab meaning is normal unless proven special */
5372 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5374 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5375 return 0;
5377 dup = g_strdup(url + strlen(XT_XTP_STR));
5379 /* split out the url */
5380 for ((p = strtok_r(dup, "/", &last)); p;
5381 (p = strtok_r(NULL, "/", &last))) {
5382 if (n_tokens < 4)
5383 tokens[n_tokens++] = p;
5386 /* should be atleast three fields 'class/seskey/command/arg' */
5387 if (n_tokens < 3)
5388 goto clean;
5390 dsp = xtp_despatches;
5391 req_class = atoi(tokens[0]);
5392 while (dsp->xtp_class != NULL) {
5393 if (dsp->xtp_class == req_class) {
5394 dsp_match = dsp;
5395 break;
5397 dsp++;
5400 /* did we find one atall? */
5401 if (dsp_match == NULL) {
5402 show_oops(t, "%s: no matching xtp despatch found", __func__);
5403 goto clean;
5406 /* check session key and call despatch function */
5407 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5408 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5411 clean:
5412 if (dup)
5413 g_free(dup);
5415 return 1;
5420 void
5421 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5423 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5425 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5427 if (t == NULL) {
5428 show_oops_s("activate_uri_entry_cb invalid parameters");
5429 return;
5432 if (uri == NULL) {
5433 show_oops(t, "activate_uri_entry_cb no uri");
5434 return;
5437 uri += strspn(uri, "\t ");
5439 /* if xxxt:// treat specially */
5440 if (!parse_xtp_url(t, uri)) {
5441 load_uri(t, (gchar *)uri);
5442 focus_webview(t);
5446 void
5447 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5449 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5450 char *newuri = NULL;
5451 gchar *enc_search;
5453 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5455 if (t == NULL) {
5456 show_oops_s("activate_search_entry_cb invalid parameters");
5457 return;
5460 if (search_string == NULL) {
5461 show_oops(t, "no search_string");
5462 return;
5465 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5466 newuri = g_strdup_printf(search_string, enc_search);
5467 g_free(enc_search);
5469 webkit_web_view_load_uri(t->wv, newuri);
5470 focus_webview(t);
5472 if (newuri)
5473 g_free(newuri);
5476 void
5477 check_and_set_js(const gchar *uri, struct tab *t)
5479 struct domain *d = NULL;
5480 int es = 0;
5482 if (uri == NULL || t == NULL)
5483 return;
5485 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5486 es = 0;
5487 else
5488 es = 1;
5490 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5491 es ? "enable" : "disable", uri);
5493 g_object_set(G_OBJECT(t->settings),
5494 "enable-scripts", es, (char *)NULL);
5495 g_object_set(G_OBJECT(t->settings),
5496 "javascript-can-open-windows-automatically", es, (char *)NULL);
5497 webkit_web_view_set_settings(t->wv, t->settings);
5499 button_set_stockid(t->js_toggle,
5500 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5503 void
5504 show_ca_status(struct tab *t, const char *uri)
5506 WebKitWebFrame *frame;
5507 WebKitWebDataSource *source;
5508 WebKitNetworkRequest *request;
5509 SoupMessage *message;
5510 GdkColor color;
5511 gchar *col_str = XT_COLOR_WHITE;
5512 int r;
5514 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5515 ssl_strict_certs, ssl_ca_file, uri);
5517 if (uri == NULL)
5518 goto done;
5519 if (ssl_ca_file == NULL) {
5520 if (g_str_has_prefix(uri, "http://"))
5521 goto done;
5522 if (g_str_has_prefix(uri, "https://")) {
5523 col_str = XT_COLOR_RED;
5524 goto done;
5526 return;
5528 if (g_str_has_prefix(uri, "http://") ||
5529 !g_str_has_prefix(uri, "https://"))
5530 goto done;
5532 frame = webkit_web_view_get_main_frame(t->wv);
5533 source = webkit_web_frame_get_data_source(frame);
5534 request = webkit_web_data_source_get_request(source);
5535 message = webkit_network_request_get_message(request);
5537 if (message && (soup_message_get_flags(message) &
5538 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5539 col_str = XT_COLOR_GREEN;
5540 goto done;
5541 } else {
5542 r = load_compare_cert(t, NULL);
5543 if (r == 0)
5544 col_str = XT_COLOR_BLUE;
5545 else if (r == 1)
5546 col_str = XT_COLOR_YELLOW;
5547 else
5548 col_str = XT_COLOR_RED;
5549 goto done;
5551 done:
5552 if (col_str) {
5553 gdk_color_parse(col_str, &color);
5554 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5556 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5557 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5558 &color);
5559 gdk_color_parse(XT_COLOR_BLACK, &color);
5560 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5561 &color);
5562 } else {
5563 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5564 &color);
5565 gdk_color_parse(XT_COLOR_BLACK, &color);
5566 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5567 &color);
5572 void
5573 free_favicon(struct tab *t)
5575 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5576 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5578 if (t->icon_request)
5579 g_object_unref(t->icon_request);
5580 if (t->icon_pixbuf)
5581 g_object_unref(t->icon_pixbuf);
5582 if (t->icon_dest_uri)
5583 g_free(t->icon_dest_uri);
5585 t->icon_pixbuf = NULL;
5586 t->icon_request = NULL;
5587 t->icon_dest_uri = NULL;
5590 void
5591 xt_icon_from_name(struct tab *t, gchar *name)
5593 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5594 GTK_ENTRY_ICON_PRIMARY, "text-html");
5595 if (show_url == 0)
5596 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5597 GTK_ENTRY_ICON_PRIMARY, "text-html");
5598 else
5599 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5600 GTK_ENTRY_ICON_PRIMARY, NULL);
5603 void
5604 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5606 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5607 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5608 if (show_url == 0)
5609 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5610 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5611 else
5612 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5613 GTK_ENTRY_ICON_PRIMARY, NULL);
5616 gboolean
5617 is_valid_icon(char *file)
5619 gboolean valid = 0;
5620 const char *mime_type;
5621 GFileInfo *fi;
5622 GFile *gf;
5624 gf = g_file_new_for_path(file);
5625 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5626 NULL, NULL);
5627 mime_type = g_file_info_get_content_type(fi);
5628 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5629 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5630 g_strcmp0(mime_type, "image/png") == 0 ||
5631 g_strcmp0(mime_type, "image/gif") == 0 ||
5632 g_strcmp0(mime_type, "application/octet-stream") == 0;
5633 g_object_unref(fi);
5634 g_object_unref(gf);
5636 return (valid);
5639 void
5640 set_favicon_from_file(struct tab *t, char *file)
5642 gint width, height;
5643 GdkPixbuf *pixbuf, *scaled;
5644 struct stat sb;
5646 if (t == NULL || file == NULL)
5647 return;
5648 if (t->icon_pixbuf) {
5649 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5650 return;
5653 if (g_str_has_prefix(file, "file://"))
5654 file += strlen("file://");
5655 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5657 if (!stat(file, &sb)) {
5658 if (sb.st_size == 0 || !is_valid_icon(file)) {
5659 /* corrupt icon so trash it */
5660 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5661 __func__, file);
5662 unlink(file);
5663 /* no need to set icon to default here */
5664 return;
5668 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5669 if (pixbuf == NULL) {
5670 xt_icon_from_name(t, "text-html");
5671 return;
5674 g_object_get(pixbuf, "width", &width, "height", &height,
5675 (char *)NULL);
5676 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5677 __func__, t->tab_id, width, height);
5679 if (width > 16 || height > 16) {
5680 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5681 GDK_INTERP_BILINEAR);
5682 g_object_unref(pixbuf);
5683 } else
5684 scaled = pixbuf;
5686 if (scaled == NULL) {
5687 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5688 GDK_INTERP_BILINEAR);
5689 return;
5692 t->icon_pixbuf = scaled;
5693 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5696 void
5697 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5698 WebKitWebView *wv)
5700 WebKitDownloadStatus status = webkit_download_get_status(download);
5701 struct tab *tt = NULL, *t = NULL;
5704 * find the webview instead of passing in the tab as it could have been
5705 * deleted from underneath us.
5707 TAILQ_FOREACH(tt, &tabs, entry) {
5708 if (tt->wv == wv) {
5709 t = tt;
5710 break;
5713 if (t == NULL)
5714 return;
5716 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5717 __func__, t->tab_id, status);
5719 switch (status) {
5720 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5721 /* -1 */
5722 t->icon_download = NULL;
5723 free_favicon(t);
5724 break;
5725 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5726 /* 0 */
5727 break;
5728 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5729 /* 1 */
5730 break;
5731 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5732 /* 2 */
5733 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5734 __func__, t->tab_id);
5735 t->icon_download = NULL;
5736 free_favicon(t);
5737 break;
5738 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5739 /* 3 */
5741 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5742 __func__, t->icon_dest_uri);
5743 set_favicon_from_file(t, t->icon_dest_uri);
5744 /* these will be freed post callback */
5745 t->icon_request = NULL;
5746 t->icon_download = NULL;
5747 break;
5748 default:
5749 break;
5753 void
5754 abort_favicon_download(struct tab *t)
5756 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5758 if (t->icon_download) {
5759 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5760 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5761 webkit_download_cancel(t->icon_download);
5762 t->icon_download = NULL;
5763 } else
5764 free_favicon(t);
5766 xt_icon_from_name(t, "text-html");
5769 void
5770 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5772 gchar *name_hash, file[PATH_MAX];
5773 struct stat sb;
5775 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5777 if (uri == NULL || t == NULL)
5778 return;
5780 if (t->icon_request) {
5781 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5782 return;
5785 /* check to see if we got the icon in cache */
5786 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5787 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5788 g_free(name_hash);
5790 if (!stat(file, &sb)) {
5791 if (sb.st_size > 0) {
5792 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5793 __func__, file);
5794 set_favicon_from_file(t, file);
5795 return;
5798 /* corrupt icon so trash it */
5799 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5800 __func__, file);
5801 unlink(file);
5804 /* create download for icon */
5805 t->icon_request = webkit_network_request_new(uri);
5806 if (t->icon_request == NULL) {
5807 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5808 __func__, uri);
5809 return;
5812 t->icon_download = webkit_download_new(t->icon_request);
5813 if (t->icon_download == NULL) {
5814 fprintf(stderr, "%s: icon_download", __func__);
5815 return;
5818 /* we have to free icon_dest_uri later */
5819 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5820 webkit_download_set_destination_uri(t->icon_download,
5821 t->icon_dest_uri);
5823 if (webkit_download_get_status(t->icon_download) ==
5824 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5825 fprintf(stderr, "%s: download failed to start", __func__);
5826 g_object_unref(t->icon_request);
5827 g_free(t->icon_dest_uri);
5828 t->icon_request = NULL;
5829 t->icon_dest_uri = NULL;
5830 return;
5833 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5834 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5836 webkit_download_start(t->icon_download);
5839 void
5840 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5842 const gchar *set = NULL, *uri = NULL, *title = NULL;
5843 struct history *h, find;
5844 const gchar *s_loading;
5845 struct karg a;
5847 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5848 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5850 if (t == NULL) {
5851 show_oops_s("notify_load_status_cb invalid paramters");
5852 return;
5855 switch (webkit_web_view_get_load_status(wview)) {
5856 case WEBKIT_LOAD_PROVISIONAL:
5857 /* 0 */
5858 abort_favicon_download(t);
5859 #if GTK_CHECK_VERSION(2, 20, 0)
5860 gtk_widget_show(t->spinner);
5861 gtk_spinner_start(GTK_SPINNER(t->spinner));
5862 #endif
5863 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5865 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5867 t->focus_wv = 1;
5869 break;
5871 case WEBKIT_LOAD_COMMITTED:
5872 /* 1 */
5873 if ((uri = get_uri(wview)) != NULL) {
5874 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5875 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5877 if (t->status) {
5878 g_free(t->status);
5879 t->status = NULL;
5881 set_status(t, (char *)uri, XT_STATUS_LOADING);
5884 /* check if js white listing is enabled */
5885 if (enable_js_whitelist) {
5886 uri = get_uri(wview);
5887 check_and_set_js(uri, t);
5890 if (t->styled)
5891 apply_style(t);
5893 show_ca_status(t, uri);
5895 /* we know enough to autosave the session */
5896 if (session_autosave) {
5897 a.s = NULL;
5898 save_tabs(t, &a);
5900 break;
5902 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5903 /* 3 */
5904 break;
5906 case WEBKIT_LOAD_FINISHED:
5907 /* 2 */
5908 uri = get_uri(wview);
5909 if (uri == NULL)
5910 return;
5912 if (!strncmp(uri, "http://", strlen("http://")) ||
5913 !strncmp(uri, "https://", strlen("https://")) ||
5914 !strncmp(uri, "file://", strlen("file://"))) {
5915 find.uri = uri;
5916 h = RB_FIND(history_list, &hl, &find);
5917 if (!h) {
5918 title = webkit_web_view_get_title(wview);
5919 set = title ? title: uri;
5920 h = g_malloc(sizeof *h);
5921 h->uri = g_strdup(uri);
5922 h->title = g_strdup(set);
5923 RB_INSERT(history_list, &hl, h);
5924 completion_add_uri(h->uri);
5925 update_history_tabs(NULL);
5929 set_status(t, (char *)uri, XT_STATUS_URI);
5930 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5931 case WEBKIT_LOAD_FAILED:
5932 /* 4 */
5933 #endif
5934 #if GTK_CHECK_VERSION(2, 20, 0)
5935 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5936 gtk_widget_hide(t->spinner);
5937 #endif
5938 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5939 if (s_loading && !strcmp(s_loading, "Loading"))
5940 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5941 default:
5942 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5943 break;
5946 if (t->item)
5947 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5948 else
5949 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5950 webkit_web_view_can_go_back(wview));
5952 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5953 webkit_web_view_can_go_forward(wview));
5955 /* take focus if we are visible */
5956 focus_webview(t);
5959 void
5960 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5962 const gchar *set = NULL, *title = NULL;
5964 title = webkit_web_view_get_title(wview);
5965 set = title ? title: get_uri(wview);
5966 gtk_label_set_text(GTK_LABEL(t->label), set);
5967 gtk_window_set_title(GTK_WINDOW(main_window), set);
5970 void
5971 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5973 run_script(t, JS_HINTING);
5976 void
5977 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5979 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5980 progress == 100 ? 0 : (double)progress / 100);
5981 if (show_url == 0) {
5982 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5983 progress == 100 ? 0 : (double)progress / 100);
5988 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5989 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5990 WebKitWebPolicyDecision *pd, struct tab *t)
5992 char *uri;
5993 WebKitWebNavigationReason reason;
5994 struct domain *d = NULL;
5996 if (t == NULL) {
5997 show_oops_s("webview_npd_cb invalid parameters");
5998 return (FALSE);
6001 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6002 t->ctrl_click,
6003 webkit_network_request_get_uri(request));
6005 uri = (char *)webkit_network_request_get_uri(request);
6007 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
6008 t->ctrl_click = 0;
6009 create_new_tab(uri, NULL, ctrl_click_focus);
6010 webkit_web_policy_decision_ignore(pd);
6011 return (TRUE); /* we made the decission */
6015 * This is a little hairy but it comes down to this:
6016 * when we run in whitelist mode we have to assist the browser in
6017 * opening the URL that it would have opened in a new tab.
6019 reason = webkit_web_navigation_action_get_reason(na);
6020 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6021 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6022 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6023 load_uri(t, uri);
6025 webkit_web_policy_decision_use(pd);
6026 return (TRUE); /* we made the decission */
6029 return (FALSE);
6032 WebKitWebView *
6033 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6035 struct tab *tt;
6036 struct domain *d = NULL;
6037 const gchar *uri;
6038 WebKitWebView *webview = NULL;
6040 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6041 webkit_web_view_get_uri(wv));
6043 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6044 uri = webkit_web_view_get_uri(wv);
6045 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6046 return (NULL);
6048 tt = create_new_tab(NULL, NULL, 1);
6049 webview = tt->wv;
6050 } else if (enable_scripts == 1) {
6051 tt = create_new_tab(NULL, NULL, 1);
6052 webview = tt->wv;
6055 return (webview);
6058 gboolean
6059 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6061 const gchar *uri;
6062 struct domain *d = NULL;
6064 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6066 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6067 uri = webkit_web_view_get_uri(wv);
6068 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6069 return (FALSE);
6071 delete_tab(t);
6072 } else if (enable_scripts == 1)
6073 delete_tab(t);
6075 return (TRUE);
6079 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6081 /* we can not eat the event without throwing gtk off so defer it */
6083 /* catch middle click */
6084 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6085 t->ctrl_click = 1;
6086 goto done;
6089 /* catch ctrl click */
6090 if (e->type == GDK_BUTTON_RELEASE &&
6091 CLEAN(e->state) == GDK_CONTROL_MASK)
6092 t->ctrl_click = 1;
6093 else
6094 t->ctrl_click = 0;
6095 done:
6096 return (XT_CB_PASSTHROUGH);
6100 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6102 struct mime_type *m;
6104 m = find_mime_type(mime_type);
6105 if (m == NULL)
6106 return (1);
6107 if (m->mt_download)
6108 return (1);
6110 switch (fork()) {
6111 case -1:
6112 show_oops(t, "can't fork mime handler");
6113 /* NOTREACHED */
6114 case 0:
6115 break;
6116 default:
6117 return (0);
6120 /* child */
6121 execlp(m->mt_action, m->mt_action,
6122 webkit_network_request_get_uri(request), (void *)NULL);
6124 _exit(0);
6126 /* NOTREACHED */
6127 return (0);
6130 const gchar *
6131 get_mime_type(char *file)
6133 const char *mime_type;
6134 GFileInfo *fi;
6135 GFile *gf;
6137 if (g_str_has_prefix(file, "file://"))
6138 file += strlen("file://");
6140 gf = g_file_new_for_path(file);
6141 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6142 NULL, NULL);
6143 mime_type = g_file_info_get_content_type(fi);
6144 g_object_unref(fi);
6145 g_object_unref(gf);
6147 return (mime_type);
6151 run_download_mimehandler(char *mime_type, char *file)
6153 struct mime_type *m;
6155 m = find_mime_type(mime_type);
6156 if (m == NULL)
6157 return (1);
6159 switch (fork()) {
6160 case -1:
6161 show_oops_s("can't fork download mime handler");
6162 /* NOTREACHED */
6163 case 0:
6164 break;
6165 default:
6166 return (0);
6169 /* child */
6170 if (g_str_has_prefix(file, "file://"))
6171 file += strlen("file://");
6172 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6174 _exit(0);
6176 /* NOTREACHED */
6177 return (0);
6180 void
6181 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6182 WebKitWebView *wv)
6184 WebKitDownloadStatus status;
6185 const gchar *file = NULL, *mime = NULL;
6187 if (download == NULL)
6188 return;
6189 status = webkit_download_get_status(download);
6190 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6191 return;
6193 file = webkit_download_get_destination_uri(download);
6194 if (file == NULL)
6195 return;
6196 mime = get_mime_type((char *)file);
6197 if (mime == NULL)
6198 return;
6200 run_download_mimehandler((char *)mime, (char *)file);
6204 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6205 WebKitNetworkRequest *request, char *mime_type,
6206 WebKitWebPolicyDecision *decision, struct tab *t)
6208 if (t == NULL) {
6209 show_oops_s("webview_mimetype_cb invalid parameters");
6210 return (FALSE);
6213 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6214 t->tab_id, mime_type);
6216 if (run_mimehandler(t, mime_type, request) == 0) {
6217 webkit_web_policy_decision_ignore(decision);
6218 focus_webview(t);
6219 return (TRUE);
6222 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6223 webkit_web_policy_decision_download(decision);
6224 return (TRUE);
6227 return (FALSE);
6231 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6232 struct tab *t)
6234 const gchar *filename;
6235 char *uri = NULL;
6236 struct download *download_entry;
6237 int ret = TRUE;
6239 if (wk_download == NULL || t == NULL) {
6240 show_oops_s("%s invalid parameters", __func__);
6241 return (FALSE);
6244 filename = webkit_download_get_suggested_filename(wk_download);
6245 if (filename == NULL)
6246 return (FALSE); /* abort download */
6248 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6250 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6251 "local %s\n", __func__, t->tab_id, filename, uri);
6253 webkit_download_set_destination_uri(wk_download, uri);
6255 if (webkit_download_get_status(wk_download) ==
6256 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6257 show_oops(t, "%s: download failed to start", __func__);
6258 ret = FALSE;
6259 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6260 } else {
6261 /* connect "download first" mime handler */
6262 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6263 G_CALLBACK(download_status_changed_cb), NULL);
6265 download_entry = g_malloc(sizeof(struct download));
6266 download_entry->download = wk_download;
6267 download_entry->tab = t;
6268 download_entry->id = next_download_id++;
6269 RB_INSERT(download_list, &downloads, download_entry);
6270 /* get from history */
6271 g_object_ref(wk_download);
6272 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6273 show_oops(t, "Download of '%s' started...",
6274 basename(webkit_download_get_destination_uri(wk_download)));
6277 if (uri)
6278 g_free(uri);
6280 /* sync other download manager tabs */
6281 update_download_tabs(NULL);
6284 * NOTE: never redirect/render the current tab before this
6285 * function returns. This will cause the download to never start.
6287 return (ret); /* start download */
6290 void
6291 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6293 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6295 if (t == NULL) {
6296 show_oops_s("webview_hover_cb");
6297 return;
6300 if (uri)
6301 set_status(t, uri, XT_STATUS_LINK);
6302 else {
6303 if (t->status)
6304 set_status(t, t->status, XT_STATUS_NOTHING);
6308 gboolean
6309 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6311 struct key_binding *k;
6313 TAILQ_FOREACH(k, &kbl, entry)
6314 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6315 if (k->mask == 0) {
6316 if ((e->state & (CTRL | MOD1)) == 0)
6317 return (cmd_execute(t, k->cmd));
6318 } else if ((e->state & k->mask) == k->mask) {
6319 return (cmd_execute(t, k->cmd));
6323 return (XT_CB_PASSTHROUGH);
6327 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6329 char s[2], buf[128];
6330 const char *errstr = NULL;
6331 long long link;
6333 /* don't use w directly; use t->whatever instead */
6335 if (t == NULL) {
6336 show_oops_s("wv_keypress_after_cb");
6337 return (XT_CB_PASSTHROUGH);
6340 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6341 e->keyval, e->state, t);
6343 if (t->hints_on) {
6344 /* ESC */
6345 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6346 disable_hints(t);
6347 return (XT_CB_HANDLED);
6350 /* RETURN */
6351 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6352 link = strtonum(t->hint_num, 1, 1000, &errstr);
6353 if (errstr) {
6354 /* we have a string */
6355 } else {
6356 /* we have a number */
6357 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6358 t->hint_num);
6359 run_script(t, buf);
6361 disable_hints(t);
6364 /* BACKSPACE */
6365 /* XXX unfuck this */
6366 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6367 if (t->hint_mode == XT_HINT_NUMERICAL) {
6368 /* last input was numerical */
6369 int l;
6370 l = strlen(t->hint_num);
6371 if (l > 0) {
6372 l--;
6373 if (l == 0) {
6374 disable_hints(t);
6375 enable_hints(t);
6376 } else {
6377 t->hint_num[l] = '\0';
6378 goto num;
6381 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6382 /* last input was alphanumerical */
6383 int l;
6384 l = strlen(t->hint_buf);
6385 if (l > 0) {
6386 l--;
6387 if (l == 0) {
6388 disable_hints(t);
6389 enable_hints(t);
6390 } else {
6391 t->hint_buf[l] = '\0';
6392 goto anum;
6395 } else {
6396 /* bogus */
6397 disable_hints(t);
6401 /* numerical input */
6402 if (CLEAN(e->state) == 0 &&
6403 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6404 snprintf(s, sizeof s, "%c", e->keyval);
6405 strlcat(t->hint_num, s, sizeof t->hint_num);
6406 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6407 t->hint_num);
6408 num:
6409 link = strtonum(t->hint_num, 1, 1000, &errstr);
6410 if (errstr) {
6411 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6412 disable_hints(t);
6413 } else {
6414 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6415 t->hint_num);
6416 t->hint_mode = XT_HINT_NUMERICAL;
6417 run_script(t, buf);
6420 /* empty the counter buffer */
6421 bzero(t->hint_buf, sizeof t->hint_buf);
6422 return (XT_CB_HANDLED);
6425 /* alphanumerical input */
6426 if (
6427 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6428 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6429 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6430 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6431 snprintf(s, sizeof s, "%c", e->keyval);
6432 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6433 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6434 t->hint_buf);
6435 anum:
6436 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6437 run_script(t, buf);
6439 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6440 t->hint_buf);
6441 t->hint_mode = XT_HINT_ALPHANUM;
6442 run_script(t, buf);
6444 /* empty the counter buffer */
6445 bzero(t->hint_num, sizeof t->hint_num);
6446 return (XT_CB_HANDLED);
6449 return (XT_CB_HANDLED);
6452 return (handle_keypress(t, e, 0));
6456 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6458 hide_oops(t);
6460 return (XT_CB_PASSTHROUGH);
6464 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6466 const gchar *c = gtk_entry_get_text(w);
6467 GdkColor color;
6468 int forward = TRUE;
6470 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6471 e->keyval, e->state, t);
6473 if (t == NULL) {
6474 show_oops_s("cmd_keyrelease_cb invalid parameters");
6475 return (XT_CB_PASSTHROUGH);
6478 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6479 e->keyval, e->state, t);
6481 if (c[0] == ':')
6482 goto done;
6483 if (strlen(c) == 1) {
6484 webkit_web_view_unmark_text_matches(t->wv);
6485 goto done;
6488 if (c[0] == '/')
6489 forward = TRUE;
6490 else if (c[0] == '?')
6491 forward = FALSE;
6492 else
6493 goto done;
6495 /* search */
6496 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6497 FALSE) {
6498 /* not found, mark red */
6499 gdk_color_parse(XT_COLOR_RED, &color);
6500 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6501 /* unmark and remove selection */
6502 webkit_web_view_unmark_text_matches(t->wv);
6503 /* my kingdom for a way to unselect text in webview */
6504 } else {
6505 /* found, highlight all */
6506 webkit_web_view_unmark_text_matches(t->wv);
6507 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6508 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6509 gdk_color_parse(XT_COLOR_WHITE, &color);
6510 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6512 done:
6513 return (XT_CB_PASSTHROUGH);
6516 gboolean
6517 match_uri(const gchar *uri, const gchar *key) {
6518 gchar *voffset;
6519 size_t len;
6520 gboolean match = FALSE;
6522 len = strlen(key);
6524 if (!strncmp(key, uri, len))
6525 match = TRUE;
6526 else {
6527 voffset = strstr(uri, "/") + 2;
6528 if (!strncmp(key, voffset, len))
6529 match = TRUE;
6530 else if (g_str_has_prefix(voffset, "www.")) {
6531 voffset = voffset + strlen("www.");
6532 if (!strncmp(key, voffset, len))
6533 match = TRUE;
6537 return (match);
6540 void
6541 cmd_getlist(int id, char *key)
6543 int i, dep, c = 0;
6544 struct history *h;
6546 if (id >= 0 && (cmds[id].userarg && (cmds[id].arg.i == XT_TAB_OPEN || cmds[id].arg.i == XT_TAB_NEW))) {
6547 RB_FOREACH_REVERSE(h, history_list, &hl)
6548 if (match_uri(h->uri, key)) {
6549 cmd_status.list[c] = (char *)h->uri;
6550 if (++c > 255)
6551 break;
6554 cmd_status.len = c;
6555 return;
6558 dep = (id == -1) ? 0 : cmds[id].level + 1;
6560 for (i = id + 1; i < LENGTH(cmds); i++) {
6561 if(cmds[i].level < dep)
6562 break;
6563 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6564 cmd_status.list[c++] = cmds[i].cmd;
6568 cmd_status.len = c;
6571 char *
6572 cmd_getnext(int dir)
6574 cmd_status.index += dir;
6576 if (cmd_status.index<0)
6577 cmd_status.index = cmd_status.len-1;
6578 else if (cmd_status.index >= cmd_status.len)
6579 cmd_status.index = 0;
6581 return cmd_status.list[cmd_status.index];
6585 cmd_tokenize(char *s, char *tokens[])
6587 int i = 0;
6588 char *tok, *last;
6589 size_t len = strlen(s);
6590 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6592 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6593 tokens[i] = tok;
6595 if (blank && i < 3)
6596 tokens[i++] = "";
6598 return (i);
6601 void
6602 cmd_complete(struct tab *t, char *str, int dir)
6604 GtkEntry *w = GTK_ENTRY(t->cmd);
6605 int i, j, levels, c = 0, dep = 0, parent = -1;
6606 char *tok, *match, *s = strdup(str);
6607 char *tokens[3];
6608 char res[XT_MAX_URL_LENGTH + 32] = ":";
6610 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", str);
6612 levels = cmd_tokenize(s, tokens);
6614 for (i = 0; i < levels - 1; i++) {
6615 tok = tokens[i];
6616 for (j = c; j < LENGTH(cmds); j++)
6617 if (cmds[j].level == dep && !strcmp(tok, cmds[j].cmd)) {
6618 strlcat(res, tok, sizeof res);
6619 strlcat(res, " ", sizeof res);
6620 dep++;
6621 c = j;
6622 break;
6625 parent = c;
6628 if (cmd_status.index == -1)
6629 cmd_getlist(parent, tokens[i]);
6631 if (cmd_status.len > 0) {
6632 match = cmd_getnext(dir);
6633 strlcat(res, match, sizeof res);
6634 gtk_entry_set_text(w, res);
6635 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6639 gboolean
6640 cmd_valid(char *str)
6642 char *tok, *last, *s = g_strdup(str);
6643 int i, c = 0, dep = 0;
6645 for (tok = strtok_r(s, " ", &last); tok;
6646 tok = strtok_r(NULL, " ", &last)) {
6647 for (i = c; i < LENGTH(cmds); i++) {
6648 if (cmds[i].level < dep) {
6649 return (XT_CB_PASSTHROUGH);
6651 if (cmds[i].level == dep && !strcmp(tok, cmds[i].cmd)) {
6652 if (cmds[i].userarg) {
6653 return (XT_CB_HANDLED);
6655 c = i + 1;
6656 dep++;
6657 break;
6660 if (i == LENGTH(cmds)) {
6661 return (XT_CB_PASSTHROUGH);
6664 return (XT_CB_HANDLED);
6667 gboolean
6668 cmd_execute(struct tab *t, char *str)
6670 struct cmd *cmd = NULL;
6671 char *tok, *last, *s = g_strdup(str);
6672 int j, c = 0, dep = 0;
6674 for (tok = strtok_r(s, " ", &last); tok;
6675 tok = strtok_r(NULL, " ", &last)) {
6676 for (j = c; j < LENGTH(cmds); j++) {
6677 if (cmds[j].level < dep) {
6678 show_oops(t, "Invalid command: %s", str);
6679 return (XT_CB_PASSTHROUGH);
6681 if (cmds[j].level == dep && !strcmp(tok, cmds[j].cmd)) {
6682 cmd = &cmds[j];
6683 if (cmd->userarg) {
6684 cmd->arg.s = last ? g_strdup(last) : g_strdup("");
6685 goto execute_cmd;
6687 c = j + 1;
6688 dep++;
6689 break;
6692 if (j == LENGTH(cmds)) {
6693 show_oops(t, "Invalid command: %s", str);
6694 return (XT_CB_PASSTHROUGH);
6697 cmd->arg.s = g_strdup(tok);
6698 execute_cmd:
6699 /* arg->s contains last token */
6700 cmd->func(t, &cmd->arg);
6701 if (cmd->arg.s)
6702 g_free(cmd->arg.s);
6704 return (XT_CB_HANDLED);
6708 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6710 if (t == NULL) {
6711 show_oops_s("entry_key_cb invalid parameters");
6712 return (XT_CB_PASSTHROUGH);
6715 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6716 e->keyval, e->state, t);
6718 hide_oops(t);
6720 if (e->keyval == GDK_Escape) {
6721 /* don't use focus_webview(t) because we want to type :cmds */
6722 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6725 return (handle_keypress(t, e, 1));
6729 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6731 int rv = XT_CB_HANDLED;
6732 const gchar *c = gtk_entry_get_text(w);
6734 if (t == NULL) {
6735 show_oops_s("cmd_keypress_cb parameters");
6736 return (XT_CB_PASSTHROUGH);
6739 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6740 e->keyval, e->state, t);
6742 /* sanity */
6743 if (c == NULL)
6744 e->keyval = GDK_Escape;
6745 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6746 e->keyval = GDK_Escape;
6748 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6749 cmd_status.index = -1;
6751 switch (e->keyval) {
6752 case GDK_Tab:
6753 if (c[0] == ':')
6754 cmd_complete(t, (char *)&c[1], 1);
6755 goto done;
6756 case GDK_ISO_Left_Tab:
6757 if (c[0] == ':')
6758 cmd_complete(t, (char *)&c[1], -1);
6760 goto done;
6761 case GDK_BackSpace:
6762 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6763 break;
6764 /* FALLTHROUGH */
6765 case GDK_Escape:
6766 hide_cmd(t);
6767 focus_webview(t);
6769 /* cancel search */
6770 if (c[0] == '/' || c[0] == '?')
6771 webkit_web_view_unmark_text_matches(t->wv);
6772 goto done;
6775 rv = XT_CB_PASSTHROUGH;
6776 done:
6777 return (rv);
6781 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6783 if (t == NULL) {
6784 show_oops_s("cmd_focusout_cb invalid parameters");
6785 return (XT_CB_PASSTHROUGH);
6787 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6789 hide_cmd(t);
6790 hide_oops(t);
6792 if (show_url == 0 || t->focus_wv)
6793 focus_webview(t);
6794 else
6795 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6797 return (XT_CB_PASSTHROUGH);
6800 void
6801 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6803 char *s;
6804 const gchar *c = gtk_entry_get_text(entry);
6806 if (t == NULL) {
6807 show_oops_s("cmd_activate_cb invalid parameters");
6808 return;
6811 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6813 /* sanity */
6814 if (c == NULL)
6815 goto done;
6816 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6817 goto done;
6818 if (strlen(c) < 2)
6819 goto done;
6820 s = (char *)&c[1];
6822 if (c[0] == '/' || c[0] == '?') {
6823 if (t->search_text) {
6824 g_free(t->search_text);
6825 t->search_text = NULL;
6828 t->search_text = g_strdup(s);
6829 if (global_search)
6830 g_free(global_search);
6831 global_search = g_strdup(s);
6832 t->search_forward = c[0] == '/';
6834 goto done;
6837 cmd_execute(t, s);
6839 done:
6840 hide_cmd(t);
6843 void
6844 backward_cb(GtkWidget *w, struct tab *t)
6846 struct karg a;
6848 if (t == NULL) {
6849 show_oops_s("backward_cb invalid parameters");
6850 return;
6853 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6855 a.i = XT_NAV_BACK;
6856 navaction(t, &a);
6859 void
6860 forward_cb(GtkWidget *w, struct tab *t)
6862 struct karg a;
6864 if (t == NULL) {
6865 show_oops_s("forward_cb invalid parameters");
6866 return;
6869 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6871 a.i = XT_NAV_FORWARD;
6872 navaction(t, &a);
6875 void
6876 home_cb(GtkWidget *w, struct tab *t)
6878 if (t == NULL) {
6879 show_oops_s("home_cb invalid parameters");
6880 return;
6883 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6885 load_uri(t, home);
6888 void
6889 stop_cb(GtkWidget *w, struct tab *t)
6891 WebKitWebFrame *frame;
6893 if (t == NULL) {
6894 show_oops_s("stop_cb invalid parameters");
6895 return;
6898 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6900 frame = webkit_web_view_get_main_frame(t->wv);
6901 if (frame == NULL) {
6902 show_oops(t, "stop_cb: no frame");
6903 return;
6906 webkit_web_frame_stop_loading(frame);
6907 abort_favicon_download(t);
6910 void
6911 setup_webkit(struct tab *t)
6913 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6914 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6915 FALSE, (char *)NULL);
6916 else
6917 warnx("webkit does not have \"enable-dns-prefetching\" property");
6918 g_object_set(G_OBJECT(t->settings),
6919 "user-agent", t->user_agent, (char *)NULL);
6920 g_object_set(G_OBJECT(t->settings),
6921 "enable-scripts", enable_scripts, (char *)NULL);
6922 g_object_set(G_OBJECT(t->settings),
6923 "enable-plugins", enable_plugins, (char *)NULL);
6924 g_object_set(G_OBJECT(t->settings),
6925 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6926 g_object_set(G_OBJECT(t->wv),
6927 "full-content-zoom", TRUE, (char *)NULL);
6928 adjustfont_webkit(t, XT_FONT_SET);
6930 webkit_web_view_set_settings(t->wv, t->settings);
6933 GtkWidget *
6934 create_browser(struct tab *t)
6936 GtkWidget *w;
6937 gchar *strval;
6939 if (t == NULL) {
6940 show_oops_s("create_browser invalid parameters");
6941 return (NULL);
6944 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6945 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6946 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6947 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6949 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6950 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6951 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6953 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6954 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6956 /* set defaults */
6957 t->settings = webkit_web_settings_new();
6959 if (user_agent == NULL) {
6960 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6961 (char *)NULL);
6962 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6963 g_free(strval);
6964 } else
6965 t->user_agent = g_strdup(user_agent);
6967 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
6969 setup_webkit(t);
6971 return (w);
6974 GtkWidget *
6975 create_window(void)
6977 GtkWidget *w;
6979 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6980 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6981 gtk_widget_set_name(w, "xxxterm");
6982 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6983 g_signal_connect(G_OBJECT(w), "delete_event",
6984 G_CALLBACK (gtk_main_quit), NULL);
6986 return (w);
6989 GtkWidget *
6990 create_kiosk_toolbar(struct tab *t)
6992 GtkWidget *toolbar = NULL, *b;
6994 b = gtk_hbox_new(FALSE, 0);
6995 toolbar = b;
6996 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6998 /* backward button */
6999 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7000 gtk_widget_set_sensitive(t->backward, FALSE);
7001 g_signal_connect(G_OBJECT(t->backward), "clicked",
7002 G_CALLBACK(backward_cb), t);
7003 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7005 /* forward button */
7006 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7007 gtk_widget_set_sensitive(t->forward, FALSE);
7008 g_signal_connect(G_OBJECT(t->forward), "clicked",
7009 G_CALLBACK(forward_cb), t);
7010 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7012 /* home button */
7013 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7014 gtk_widget_set_sensitive(t->gohome, true);
7015 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7016 G_CALLBACK(home_cb), t);
7017 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7019 /* create widgets but don't use them */
7020 t->uri_entry = gtk_entry_new();
7021 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7022 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7023 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7025 return (toolbar);
7028 GtkWidget *
7029 create_toolbar(struct tab *t)
7031 GtkWidget *toolbar = NULL, *b, *eb1;
7033 b = gtk_hbox_new(FALSE, 0);
7034 toolbar = b;
7035 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7037 if (fancy_bar) {
7038 /* backward button */
7039 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7040 gtk_widget_set_sensitive(t->backward, FALSE);
7041 g_signal_connect(G_OBJECT(t->backward), "clicked",
7042 G_CALLBACK(backward_cb), t);
7043 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7045 /* forward button */
7046 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7047 gtk_widget_set_sensitive(t->forward, FALSE);
7048 g_signal_connect(G_OBJECT(t->forward), "clicked",
7049 G_CALLBACK(forward_cb), t);
7050 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7051 FALSE, 0);
7053 /* stop button */
7054 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7055 gtk_widget_set_sensitive(t->stop, FALSE);
7056 g_signal_connect(G_OBJECT(t->stop), "clicked",
7057 G_CALLBACK(stop_cb), t);
7058 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7059 FALSE, 0);
7061 /* JS button */
7062 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7063 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7064 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7065 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7066 G_CALLBACK(js_toggle_cb), t);
7067 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7070 t->uri_entry = gtk_entry_new();
7071 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7072 G_CALLBACK(activate_uri_entry_cb), t);
7073 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7074 G_CALLBACK(entry_key_cb), t);
7075 completion_add(t);
7076 eb1 = gtk_hbox_new(FALSE, 0);
7077 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7078 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7079 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7081 /* search entry */
7082 if (fancy_bar && search_string) {
7083 GtkWidget *eb2;
7084 t->search_entry = gtk_entry_new();
7085 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7086 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7087 G_CALLBACK(activate_search_entry_cb), t);
7088 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7089 G_CALLBACK(entry_key_cb), t);
7090 gtk_widget_set_size_request(t->search_entry, -1, -1);
7091 eb2 = gtk_hbox_new(FALSE, 0);
7092 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7093 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7095 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7097 return (toolbar);
7100 void
7101 recalc_tabs(void)
7103 struct tab *t;
7105 TAILQ_FOREACH(t, &tabs, entry)
7106 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7110 undo_close_tab_save(struct tab *t)
7112 int m, n;
7113 const gchar *uri;
7114 struct undo *u1, *u2;
7115 GList *items;
7116 WebKitWebHistoryItem *item;
7118 if ((uri = get_uri(t->wv)) == NULL)
7119 return (1);
7121 u1 = g_malloc0(sizeof(struct undo));
7122 u1->uri = g_strdup(uri);
7124 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7126 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7127 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7128 u1->back = n;
7130 /* forward history */
7131 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7133 while (items) {
7134 item = items->data;
7135 u1->history = g_list_prepend(u1->history,
7136 webkit_web_history_item_copy(item));
7137 items = g_list_next(items);
7140 /* current item */
7141 if (m) {
7142 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7143 u1->history = g_list_prepend(u1->history,
7144 webkit_web_history_item_copy(item));
7147 /* back history */
7148 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7150 while (items) {
7151 item = items->data;
7152 u1->history = g_list_prepend(u1->history,
7153 webkit_web_history_item_copy(item));
7154 items = g_list_next(items);
7157 TAILQ_INSERT_HEAD(&undos, u1, entry);
7159 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7160 u2 = TAILQ_LAST(&undos, undo_tailq);
7161 TAILQ_REMOVE(&undos, u2, entry);
7162 g_free(u2->uri);
7163 g_list_free(u2->history);
7164 g_free(u2);
7165 } else
7166 undo_count++;
7168 return (0);
7171 void
7172 delete_tab(struct tab *t)
7174 struct karg a;
7176 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7178 if (t == NULL)
7179 return;
7181 TAILQ_REMOVE(&tabs, t, entry);
7183 /* halt all webkit activity */
7184 abort_favicon_download(t);
7185 webkit_web_view_stop_loading(t->wv);
7186 undo_close_tab_save(t);
7188 if (browser_mode == XT_BM_KIOSK) {
7189 gtk_widget_destroy(t->uri_entry);
7190 gtk_widget_destroy(t->stop);
7191 gtk_widget_destroy(t->js_toggle);
7195 gtk_widget_destroy(t->vbox);
7196 g_free(t->user_agent);
7197 g_free(t->stylesheet);
7198 g_free(t);
7200 recalc_tabs();
7201 if (TAILQ_EMPTY(&tabs)) {
7202 if (browser_mode == XT_BM_KIOSK)
7203 create_new_tab(home, NULL, 1);
7204 else
7205 create_new_tab(NULL, NULL, 1);
7208 /* recreate session */
7209 if (session_autosave) {
7210 a.s = NULL;
7211 save_tabs(t, &a);
7215 void
7216 adjustfont_webkit(struct tab *t, int adjust)
7218 gfloat zoom;
7220 if (t == NULL) {
7221 show_oops_s("adjustfont_webkit invalid parameters");
7222 return;
7225 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7226 if (adjust == XT_FONT_SET) {
7227 t->font_size = default_font_size;
7228 zoom = default_zoom_level;
7229 t->font_size += adjust;
7230 g_object_set(G_OBJECT(t->settings), "default-font-size",
7231 t->font_size, (char *)NULL);
7232 g_object_get(G_OBJECT(t->settings), "default-font-size",
7233 &t->font_size, (char *)NULL);
7234 } else {
7235 t->font_size += adjust;
7236 zoom += adjust/25.0;
7237 if (zoom < 0.0) {
7238 zoom = 0.04;
7241 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7242 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7245 void
7246 append_tab(struct tab *t)
7248 if (t == NULL)
7249 return;
7251 TAILQ_INSERT_TAIL(&tabs, t, entry);
7252 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7255 struct tab *
7256 create_new_tab(char *title, struct undo *u, int focus)
7258 struct tab *t, *tt;
7259 int load = 1, id, notfound;
7260 GtkWidget *b, *bb;
7261 WebKitWebHistoryItem *item;
7262 GList *items;
7263 GdkColor color;
7265 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7267 if (tabless && !TAILQ_EMPTY(&tabs)) {
7268 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7269 return (NULL);
7272 t = g_malloc0(sizeof *t);
7274 if (title == NULL) {
7275 title = "(untitled)";
7276 load = 0;
7279 t->vbox = gtk_vbox_new(FALSE, 0);
7281 /* label + button for tab */
7282 b = gtk_hbox_new(FALSE, 0);
7283 t->tab_content = b;
7285 #if GTK_CHECK_VERSION(2, 20, 0)
7286 t->spinner = gtk_spinner_new ();
7287 #endif
7288 t->label = gtk_label_new(title);
7289 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7290 gtk_widget_set_size_request(t->label, 100, 0);
7291 gtk_widget_set_size_request(b, 130, 0);
7293 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7294 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7295 #if GTK_CHECK_VERSION(2, 20, 0)
7296 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7297 #endif
7299 /* toolbar */
7300 if (browser_mode == XT_BM_KIOSK)
7301 t->toolbar = create_kiosk_toolbar(t);
7302 else
7303 t->toolbar = create_toolbar(t);
7305 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7307 /* browser */
7308 t->browser_win = create_browser(t);
7309 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7311 /* oops message for user feedback */
7312 t->oops = gtk_entry_new();
7313 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7314 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7315 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7316 gdk_color_parse(XT_COLOR_RED, &color);
7317 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7318 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7320 /* command entry */
7321 t->cmd = gtk_entry_new();
7322 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7323 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7324 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7326 /* status bar */
7327 t->statusbar = gtk_entry_new();
7328 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7329 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7330 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7331 gdk_color_parse(XT_COLOR_BLACK, &color);
7332 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7333 gdk_color_parse(XT_COLOR_WHITE, &color);
7334 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7335 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7337 /* xtp meaning is normal by default */
7338 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7340 /* set empty favicon */
7341 xt_icon_from_name(t, "text-html");
7343 /* and show it all */
7344 gtk_widget_show_all(b);
7345 gtk_widget_show_all(t->vbox);
7347 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7348 append_tab(t);
7349 else {
7350 notfound = 1;
7351 id = gtk_notebook_get_current_page(notebook);
7352 TAILQ_FOREACH(tt, &tabs, entry) {
7353 if (tt->tab_id == id) {
7354 notfound = 0;
7355 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
7356 gtk_notebook_insert_page(notebook, t->vbox, b,
7357 id + 1);
7358 recalc_tabs();
7359 break;
7362 if (notfound)
7363 append_tab(t);
7366 #if GTK_CHECK_VERSION(2, 20, 0)
7367 /* turn spinner off if we are a new tab without uri */
7368 if (!load) {
7369 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7370 gtk_widget_hide(t->spinner);
7372 #endif
7373 /* make notebook tabs reorderable */
7374 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7376 g_object_connect(G_OBJECT(t->cmd),
7377 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7378 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7379 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7380 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7381 (char *)NULL);
7383 /* reuse wv_button_cb to hide oops */
7384 g_object_connect(G_OBJECT(t->oops),
7385 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7386 (char *)NULL);
7388 g_object_connect(G_OBJECT(t->wv),
7389 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7390 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7391 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7392 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7393 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7394 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7395 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7396 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7397 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7398 "signal::event", G_CALLBACK(webview_event_cb), t,
7399 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7400 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7401 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7402 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7403 #endif
7404 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7405 (char *)NULL);
7406 g_signal_connect(t->wv,
7407 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7408 g_signal_connect(t->wv,
7409 "notify::title", G_CALLBACK(notify_title_cb), t);
7411 /* hijack the unused keys as if we were the browser */
7412 g_object_connect(G_OBJECT(t->toolbar),
7413 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7414 (char *)NULL);
7416 g_signal_connect(G_OBJECT(bb), "button_press_event",
7417 G_CALLBACK(tab_close_cb), t);
7419 /* hide stuff */
7420 hide_cmd(t);
7421 hide_oops(t);
7422 url_set_visibility();
7423 statusbar_set_visibility();
7425 if (focus) {
7426 gtk_notebook_set_current_page(notebook, t->tab_id);
7427 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7428 t->tab_id);
7431 if (load) {
7432 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7433 load_uri(t, title);
7434 } else {
7435 if (show_url == 1)
7436 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7437 else
7438 focus_webview(t);
7441 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7442 /* restore the tab's history */
7443 if (u && u->history) {
7444 items = u->history;
7445 while (items) {
7446 item = items->data;
7447 webkit_web_back_forward_list_add_item(t->bfl, item);
7448 items = g_list_next(items);
7451 item = g_list_nth_data(u->history, u->back);
7452 if (item)
7453 webkit_web_view_go_to_back_forward_item(t->wv, item);
7455 g_list_free(items);
7456 g_list_free(u->history);
7457 } else
7458 webkit_web_back_forward_list_clear(t->bfl);
7460 return (t);
7463 void
7464 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7465 gpointer *udata)
7467 struct tab *t;
7468 const gchar *uri;
7470 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7472 TAILQ_FOREACH(t, &tabs, entry) {
7473 if (t->tab_id == pn) {
7474 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7475 "%d\n", pn);
7477 uri = webkit_web_view_get_title(t->wv);
7478 if (uri == NULL)
7479 uri = XT_NAME;
7480 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7482 hide_cmd(t);
7483 hide_oops(t);
7485 if (t->focus_wv)
7486 focus_webview(t);
7491 void
7492 menuitem_response(struct tab *t)
7494 gtk_notebook_set_current_page(notebook, t->tab_id);
7497 gboolean
7498 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7500 GtkWidget *menu, *menu_items;
7501 GdkEventButton *bevent;
7502 const gchar *uri;
7503 struct tab *ti;
7505 if (event->type == GDK_BUTTON_PRESS) {
7506 bevent = (GdkEventButton *) event;
7507 menu = gtk_menu_new();
7509 TAILQ_FOREACH(ti, &tabs, entry) {
7510 if ((uri = get_uri(ti->wv)) == NULL)
7511 /* XXX make sure there is something to print */
7512 /* XXX add gui pages in here to look purdy */
7513 uri = "(untitled)";
7514 menu_items = gtk_menu_item_new_with_label(uri);
7515 gtk_menu_append(GTK_MENU (menu), menu_items);
7516 gtk_widget_show(menu_items);
7518 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7519 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7520 (gpointer)ti);
7523 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7524 bevent->button, bevent->time);
7526 /* unref object so it'll free itself when popped down */
7527 g_object_ref_sink(menu);
7528 g_object_unref(menu);
7530 return (TRUE /* eat event */);
7533 return (FALSE /* propagate */);
7537 icon_size_map(int icon_size)
7539 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7540 icon_size > GTK_ICON_SIZE_DIALOG)
7541 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7543 return (icon_size);
7546 GtkWidget *
7547 create_button(char *name, char *stockid, int size)
7549 GtkWidget *button, *image;
7550 gchar *rcstring;
7551 int gtk_icon_size;
7552 rcstring = g_strdup_printf(
7553 "style \"%s-style\"\n"
7554 "{\n"
7555 " GtkWidget::focus-padding = 0\n"
7556 " GtkWidget::focus-line-width = 0\n"
7557 " xthickness = 0\n"
7558 " ythickness = 0\n"
7559 "}\n"
7560 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7561 gtk_rc_parse_string(rcstring);
7562 g_free(rcstring);
7563 button = gtk_button_new();
7564 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7565 gtk_icon_size = icon_size_map(size ? size : icon_size);
7567 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7568 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7569 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7570 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7571 gtk_widget_set_name(button, name);
7572 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7573 gtk_widget_set_tooltip_text(button, name);
7575 return button;
7578 void
7579 button_set_stockid(GtkWidget *button, char *stockid)
7581 GtkWidget *image;
7583 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7584 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7585 gtk_button_set_image(GTK_BUTTON(button), image);
7588 void
7589 create_canvas(void)
7591 GtkWidget *vbox;
7592 GList *l = NULL;
7593 GdkPixbuf *pb;
7594 char file[PATH_MAX];
7595 int i;
7597 vbox = gtk_vbox_new(FALSE, 0);
7598 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7599 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7600 gtk_notebook_set_tab_hborder(notebook, 0);
7601 gtk_notebook_set_tab_vborder(notebook, 0);
7602 gtk_notebook_set_scrollable(notebook, TRUE);
7603 notebook_tab_set_visibility(notebook);
7604 gtk_notebook_set_show_border(notebook, FALSE);
7605 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7607 abtn = gtk_button_new();
7608 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7609 gtk_widget_set_size_request(arrow, -1, -1);
7610 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7611 gtk_widget_set_size_request(abtn, -1, 20);
7612 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7614 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7615 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7616 gtk_widget_set_size_request(vbox, -1, -1);
7618 g_object_connect(G_OBJECT(notebook),
7619 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7620 (char *)NULL);
7621 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7622 G_CALLBACK(arrow_cb), NULL);
7624 main_window = create_window();
7625 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7626 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7628 /* icons */
7629 for (i = 0; i < LENGTH(icons); i++) {
7630 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7631 pb = gdk_pixbuf_new_from_file(file, NULL);
7632 l = g_list_append(l, pb);
7634 gtk_window_set_default_icon_list(l);
7636 gtk_widget_show_all(abtn);
7637 gtk_widget_show_all(main_window);
7640 void
7641 set_hook(void **hook, char *name)
7643 if (hook == NULL)
7644 errx(1, "set_hook");
7646 if (*hook == NULL) {
7647 *hook = dlsym(RTLD_NEXT, name);
7648 if (*hook == NULL)
7649 errx(1, "can't hook %s", name);
7653 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7654 gboolean
7655 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7657 g_return_val_if_fail(cookie1, FALSE);
7658 g_return_val_if_fail(cookie2, FALSE);
7660 return (!strcmp (cookie1->name, cookie2->name) &&
7661 !strcmp (cookie1->value, cookie2->value) &&
7662 !strcmp (cookie1->path, cookie2->path) &&
7663 !strcmp (cookie1->domain, cookie2->domain));
7666 void
7667 transfer_cookies(void)
7669 GSList *cf;
7670 SoupCookie *sc, *pc;
7672 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7674 for (;cf; cf = cf->next) {
7675 pc = cf->data;
7676 sc = soup_cookie_copy(pc);
7677 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7680 soup_cookies_free(cf);
7683 void
7684 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7686 GSList *cf;
7687 SoupCookie *ci;
7689 print_cookie("soup_cookie_jar_delete_cookie", c);
7691 if (cookies_enabled == 0)
7692 return;
7694 if (jar == NULL || c == NULL)
7695 return;
7697 /* find and remove from persistent jar */
7698 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7700 for (;cf; cf = cf->next) {
7701 ci = cf->data;
7702 if (soup_cookie_equal(ci, c)) {
7703 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7704 break;
7708 soup_cookies_free(cf);
7710 /* delete from session jar */
7711 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7714 void
7715 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7717 struct domain *d = NULL;
7718 SoupCookie *c;
7719 FILE *r_cookie_f;
7721 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7722 jar, p_cookiejar, s_cookiejar);
7724 if (cookies_enabled == 0)
7725 return;
7727 /* see if we are up and running */
7728 if (p_cookiejar == NULL) {
7729 _soup_cookie_jar_add_cookie(jar, cookie);
7730 return;
7732 /* disallow p_cookiejar adds, shouldn't happen */
7733 if (jar == p_cookiejar)
7734 return;
7736 /* sanity */
7737 if (jar == NULL || cookie == NULL)
7738 return;
7740 if (enable_cookie_whitelist &&
7741 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7742 blocked_cookies++;
7743 DNPRINTF(XT_D_COOKIE,
7744 "soup_cookie_jar_add_cookie: reject %s\n",
7745 cookie->domain);
7746 if (save_rejected_cookies) {
7747 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7748 show_oops_s("can't open reject cookie file");
7749 return;
7751 fseek(r_cookie_f, 0, SEEK_END);
7752 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7753 cookie->http_only ? "#HttpOnly_" : "",
7754 cookie->domain,
7755 *cookie->domain == '.' ? "TRUE" : "FALSE",
7756 cookie->path,
7757 cookie->secure ? "TRUE" : "FALSE",
7758 cookie->expires ?
7759 (gulong)soup_date_to_time_t(cookie->expires) :
7761 cookie->name,
7762 cookie->value);
7763 fflush(r_cookie_f);
7764 fclose(r_cookie_f);
7766 if (!allow_volatile_cookies)
7767 return;
7770 if (cookie->expires == NULL && session_timeout) {
7771 soup_cookie_set_expires(cookie,
7772 soup_date_new_from_now(session_timeout));
7773 print_cookie("modified add cookie", cookie);
7776 /* see if we are white listed for persistence */
7777 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7778 /* add to persistent jar */
7779 c = soup_cookie_copy(cookie);
7780 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7781 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7784 /* add to session jar */
7785 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7786 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7789 void
7790 setup_cookies(void)
7792 char file[PATH_MAX];
7794 set_hook((void *)&_soup_cookie_jar_add_cookie,
7795 "soup_cookie_jar_add_cookie");
7796 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7797 "soup_cookie_jar_delete_cookie");
7799 if (cookies_enabled == 0)
7800 return;
7803 * the following code is intricate due to overriding several libsoup
7804 * functions.
7805 * do not alter order of these operations.
7808 /* rejected cookies */
7809 if (save_rejected_cookies)
7810 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7812 /* persistent cookies */
7813 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7814 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7816 /* session cookies */
7817 s_cookiejar = soup_cookie_jar_new();
7818 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7819 cookie_policy, (void *)NULL);
7820 transfer_cookies();
7822 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7825 void
7826 setup_proxy(char *uri)
7828 if (proxy_uri) {
7829 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7830 soup_uri_free(proxy_uri);
7831 proxy_uri = NULL;
7833 if (http_proxy) {
7834 if (http_proxy != uri) {
7835 g_free(http_proxy);
7836 http_proxy = NULL;
7840 if (uri) {
7841 http_proxy = g_strdup(uri);
7842 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7843 proxy_uri = soup_uri_new(http_proxy);
7844 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7849 send_cmd_to_socket(char *cmd)
7851 int s, len, rv = 1;
7852 struct sockaddr_un sa;
7854 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7855 warnx("%s: socket", __func__);
7856 return (rv);
7859 sa.sun_family = AF_UNIX;
7860 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7861 work_dir, XT_SOCKET_FILE);
7862 len = SUN_LEN(&sa);
7864 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7865 warnx("%s: connect", __func__);
7866 goto done;
7869 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
7870 warnx("%s: send", __func__);
7871 goto done;
7874 rv = 0;
7875 done:
7876 close(s);
7877 return (rv);
7880 void
7881 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7883 int s, n;
7884 char str[XT_MAX_URL_LENGTH];
7885 socklen_t t = sizeof(struct sockaddr_un);
7886 struct sockaddr_un sa;
7887 struct passwd *p;
7888 uid_t uid;
7889 gid_t gid;
7890 struct tab *tt;
7892 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7893 warn("accept");
7894 return;
7897 if (getpeereid(s, &uid, &gid) == -1) {
7898 warn("getpeereid");
7899 return;
7901 if (uid != getuid() || gid != getgid()) {
7902 warnx("unauthorized user");
7903 return;
7906 p = getpwuid(uid);
7907 if (p == NULL) {
7908 warnx("not a valid user");
7909 return;
7912 n = recv(s, str, sizeof(str), 0);
7913 if (n <= 0)
7914 return;
7916 tt = TAILQ_LAST(&tabs, tab_list);
7917 cmd_execute(tt, str);
7921 is_running(void)
7923 int s, len, rv = 1;
7924 struct sockaddr_un sa;
7926 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7927 warn("is_running: socket");
7928 return (-1);
7931 sa.sun_family = AF_UNIX;
7932 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7933 work_dir, XT_SOCKET_FILE);
7934 len = SUN_LEN(&sa);
7936 /* connect to see if there is a listener */
7937 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7938 rv = 0; /* not running */
7939 else
7940 rv = 1; /* already running */
7942 close(s);
7944 return (rv);
7948 build_socket(void)
7950 int s, len;
7951 struct sockaddr_un sa;
7953 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7954 warn("build_socket: socket");
7955 return (-1);
7958 sa.sun_family = AF_UNIX;
7959 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7960 work_dir, XT_SOCKET_FILE);
7961 len = SUN_LEN(&sa);
7963 /* connect to see if there is a listener */
7964 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7965 /* no listener so we will */
7966 unlink(sa.sun_path);
7968 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7969 warn("build_socket: bind");
7970 goto done;
7973 if (listen(s, 1) == -1) {
7974 warn("build_socket: listen");
7975 goto done;
7978 return (s);
7981 done:
7982 close(s);
7983 return (-1);
7986 static gboolean
7987 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7988 GtkTreeIter *iter, struct tab *t)
7990 gchar *value;
7992 gtk_tree_model_get(model, iter, 0, &value, -1);
7993 load_uri(t, value);
7995 return (FALSE);
7998 void
7999 completion_add_uri(const gchar *uri)
8001 GtkTreeIter iter;
8003 /* add uri to list_store */
8004 gtk_list_store_append(completion_model, &iter);
8005 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8008 gboolean
8009 completion_match(GtkEntryCompletion *completion, const gchar *key,
8010 GtkTreeIter *iter, gpointer user_data)
8012 gchar *value;
8013 gboolean match = FALSE;
8015 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8016 -1);
8018 if (value == NULL)
8019 return FALSE;
8021 match = match_uri(value, key);
8023 g_free(value);
8024 return (match);
8027 void
8028 completion_add(struct tab *t)
8030 /* enable completion for tab */
8031 t->completion = gtk_entry_completion_new();
8032 gtk_entry_completion_set_text_column(t->completion, 0);
8033 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8034 gtk_entry_completion_set_model(t->completion,
8035 GTK_TREE_MODEL(completion_model));
8036 gtk_entry_completion_set_match_func(t->completion, completion_match,
8037 NULL, NULL);
8038 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8039 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8040 G_CALLBACK(completion_select_cb), t);
8043 void
8044 usage(void)
8046 fprintf(stderr,
8047 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8048 exit(0);
8052 main(int argc, char *argv[])
8054 struct stat sb;
8055 int c, s, optn = 0, opte = 0, focus = 1;
8056 char conf[PATH_MAX] = { '\0' };
8057 char file[PATH_MAX];
8058 char *env_proxy = NULL;
8059 FILE *f = NULL;
8060 struct karg a;
8061 struct sigaction sact;
8062 gchar *priority = g_strdup("NORMAL");
8064 start_argv = argv;
8066 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8068 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8069 switch (c) {
8070 case 'S':
8071 show_url = 0;
8072 break;
8073 case 'T':
8074 show_tabs = 0;
8075 break;
8076 case 'V':
8077 errx(0 , "Version: %s", version);
8078 break;
8079 case 'f':
8080 strlcpy(conf, optarg, sizeof(conf));
8081 break;
8082 case 's':
8083 strlcpy(named_session, optarg, sizeof(named_session));
8084 break;
8085 case 't':
8086 tabless = 1;
8087 break;
8088 case 'n':
8089 optn = 1;
8090 break;
8091 case 'e':
8092 opte = 1;
8093 break;
8094 default:
8095 usage();
8096 /* NOTREACHED */
8099 argc -= optind;
8100 argv += optind;
8102 RB_INIT(&hl);
8103 RB_INIT(&js_wl);
8104 RB_INIT(&downloads);
8106 TAILQ_INIT(&tabs);
8107 TAILQ_INIT(&mtl);
8108 TAILQ_INIT(&aliases);
8109 TAILQ_INIT(&undos);
8110 TAILQ_INIT(&kbl);
8112 init_keybindings();
8114 gnutls_global_init();
8116 /* generate session keys for xtp pages */
8117 generate_xtp_session_key(&dl_session_key);
8118 generate_xtp_session_key(&hl_session_key);
8119 generate_xtp_session_key(&cl_session_key);
8120 generate_xtp_session_key(&fl_session_key);
8122 /* prepare gtk */
8123 gtk_init(&argc, &argv);
8124 if (!g_thread_supported())
8125 g_thread_init(NULL);
8127 /* signals */
8128 bzero(&sact, sizeof(sact));
8129 sigemptyset(&sact.sa_mask);
8130 sact.sa_handler = sigchild;
8131 sact.sa_flags = SA_NOCLDSTOP;
8132 sigaction(SIGCHLD, &sact, NULL);
8134 /* set download dir */
8135 pwd = getpwuid(getuid());
8136 if (pwd == NULL)
8137 errx(1, "invalid user %d", getuid());
8138 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8140 /* set default string settings */
8141 home = g_strdup("http://www.peereboom.us");
8142 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8143 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8144 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8146 /* read config file */
8147 if (strlen(conf) == 0)
8148 snprintf(conf, sizeof conf, "%s/.%s",
8149 pwd->pw_dir, XT_CONF_FILE);
8150 config_parse(conf, 0);
8152 /* working directory */
8153 if (strlen(work_dir) == 0)
8154 snprintf(work_dir, sizeof work_dir, "%s/%s",
8155 pwd->pw_dir, XT_DIR);
8156 if (stat(work_dir, &sb)) {
8157 if (mkdir(work_dir, S_IRWXU) == -1)
8158 err(1, "mkdir work_dir");
8159 if (stat(work_dir, &sb))
8160 err(1, "stat work_dir");
8162 if (S_ISDIR(sb.st_mode) == 0)
8163 errx(1, "%s not a dir", work_dir);
8164 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8165 warnx("fixing invalid permissions on %s", work_dir);
8166 if (chmod(work_dir, S_IRWXU) == -1)
8167 err(1, "chmod");
8170 /* icon cache dir */
8171 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8172 if (stat(cache_dir, &sb)) {
8173 if (mkdir(cache_dir, S_IRWXU) == -1)
8174 err(1, "mkdir cache_dir");
8175 if (stat(cache_dir, &sb))
8176 err(1, "stat cache_dir");
8178 if (S_ISDIR(sb.st_mode) == 0)
8179 errx(1, "%s not a dir", cache_dir);
8180 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8181 warnx("fixing invalid permissions on %s", cache_dir);
8182 if (chmod(cache_dir, S_IRWXU) == -1)
8183 err(1, "chmod");
8186 /* certs dir */
8187 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8188 if (stat(certs_dir, &sb)) {
8189 if (mkdir(certs_dir, S_IRWXU) == -1)
8190 err(1, "mkdir certs_dir");
8191 if (stat(certs_dir, &sb))
8192 err(1, "stat certs_dir");
8194 if (S_ISDIR(sb.st_mode) == 0)
8195 errx(1, "%s not a dir", certs_dir);
8196 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8197 warnx("fixing invalid permissions on %s", certs_dir);
8198 if (chmod(certs_dir, S_IRWXU) == -1)
8199 err(1, "chmod");
8202 /* sessions dir */
8203 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8204 work_dir, XT_SESSIONS_DIR);
8205 if (stat(sessions_dir, &sb)) {
8206 if (mkdir(sessions_dir, S_IRWXU) == -1)
8207 err(1, "mkdir sessions_dir");
8208 if (stat(sessions_dir, &sb))
8209 err(1, "stat sessions_dir");
8211 if (S_ISDIR(sb.st_mode) == 0)
8212 errx(1, "%s not a dir", sessions_dir);
8213 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8214 warnx("fixing invalid permissions on %s", sessions_dir);
8215 if (chmod(sessions_dir, S_IRWXU) == -1)
8216 err(1, "chmod");
8218 /* runtime settings that can override config file */
8219 if (runtime_settings[0] != '\0')
8220 config_parse(runtime_settings, 1);
8222 /* download dir */
8223 if (!strcmp(download_dir, pwd->pw_dir))
8224 strlcat(download_dir, "/downloads", sizeof download_dir);
8225 if (stat(download_dir, &sb)) {
8226 if (mkdir(download_dir, S_IRWXU) == -1)
8227 err(1, "mkdir download_dir");
8228 if (stat(download_dir, &sb))
8229 err(1, "stat download_dir");
8231 if (S_ISDIR(sb.st_mode) == 0)
8232 errx(1, "%s not a dir", download_dir);
8233 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8234 warnx("fixing invalid permissions on %s", download_dir);
8235 if (chmod(download_dir, S_IRWXU) == -1)
8236 err(1, "chmod");
8239 /* favorites file */
8240 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8241 if (stat(file, &sb)) {
8242 warnx("favorites file doesn't exist, creating it");
8243 if ((f = fopen(file, "w")) == NULL)
8244 err(1, "favorites");
8245 fclose(f);
8248 /* cookies */
8249 session = webkit_get_default_session();
8250 /* XXX ssl-priority property not quite available yet */
8251 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8252 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8253 (char *)NULL);
8254 else
8255 warnx("session does not have \"ssl-priority\" property");
8256 setup_cookies();
8258 /* certs */
8259 if (ssl_ca_file) {
8260 if (stat(ssl_ca_file, &sb)) {
8261 warn("no CA file: %s", ssl_ca_file);
8262 g_free(ssl_ca_file);
8263 ssl_ca_file = NULL;
8264 } else
8265 g_object_set(session,
8266 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8267 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8268 (void *)NULL);
8271 /* proxy */
8272 env_proxy = getenv("http_proxy");
8273 if (env_proxy)
8274 setup_proxy(env_proxy);
8275 else
8276 setup_proxy(http_proxy);
8278 if (opte) {
8279 send_cmd_to_socket(argv[0]);
8280 exit(0);
8283 /* set some connection parameters */
8284 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8285 g_object_set(session, "max-conns-per-host", max_host_connections,
8286 (char *)NULL);
8288 /* see if there is already an xxxterm running */
8289 if (single_instance && is_running()) {
8290 optn = 1;
8291 warnx("already running");
8294 char *cmd = NULL;
8295 if (optn) {
8296 while (argc) {
8297 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8298 send_cmd_to_socket(cmd);
8299 if (cmd)
8300 g_free(cmd);
8302 argc--;
8303 argv++;
8305 exit(0);
8308 /* uri completion */
8309 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8311 /* go graphical */
8312 create_canvas();
8314 if (save_global_history)
8315 restore_global_history();
8317 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8318 restore_saved_tabs();
8319 else {
8320 a.s = named_session;
8321 a.i = XT_SES_DONOTHING;
8322 open_tabs(NULL, &a);
8325 while (argc) {
8326 create_new_tab(argv[0], NULL, focus);
8327 focus = 0;
8329 argc--;
8330 argv++;
8333 if (TAILQ_EMPTY(&tabs))
8334 create_new_tab(home, NULL, 1);
8336 if (enable_socket)
8337 if ((s = build_socket()) != -1)
8338 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
8340 gtk_main();
8342 gnutls_global_deinit();
8344 return (0);