this apparently is moar bettar
[xxxterm.git] / xxxterm.c
blob9d75ab5b70ef77fd9b8338dc41b4df809d64f923
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>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <err.h>
34 #include <pwd.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pthread.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <libgen.h>
42 #include <ctype.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/stat.h>
59 #include <sys/socket.h>
60 #include <sys/un.h>
61 #include <sys/time.h>
62 #include <sys/resource.h>
64 #include <gtk/gtk.h>
65 #include <gdk/gdkkeysyms.h>
67 #if GTK_CHECK_VERSION(3,0,0)
68 /* we still use GDK_* instead of GDK_KEY_* */
69 #include <gdk/gdkkeysyms-compat.h>
70 #endif
72 #include <webkit/webkit.h>
73 #include <libsoup/soup.h>
74 #include <gnutls/gnutls.h>
75 #include <JavaScriptCore/JavaScript.h>
76 #include <gnutls/x509.h>
78 #include "javascript.h"
81 javascript.h borrowed from vimprobable2 under the following license:
83 Copyright (c) 2009 Leon Winter
84 Copyright (c) 2009 Hannes Schueller
85 Copyright (c) 2009 Matto Fransen
87 Permission is hereby granted, free of charge, to any person obtaining a copy
88 of this software and associated documentation files (the "Software"), to deal
89 in the Software without restriction, including without limitation the rights
90 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
91 copies of the Software, and to permit persons to whom the Software is
92 furnished to do so, subject to the following conditions:
94 The above copyright notice and this permission notice shall be included in
95 all copies or substantial portions of the Software.
97 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
98 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
99 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
100 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
101 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
103 THE SOFTWARE.
106 static char *version = "$xxxterm$";
108 /* hooked functions */
109 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
110 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
111 SoupCookie *);
113 /*#define XT_DEBUG*/
114 #ifdef XT_DEBUG
115 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
116 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
117 #define XT_D_MOVE 0x0001
118 #define XT_D_KEY 0x0002
119 #define XT_D_TAB 0x0004
120 #define XT_D_URL 0x0008
121 #define XT_D_CMD 0x0010
122 #define XT_D_NAV 0x0020
123 #define XT_D_DOWNLOAD 0x0040
124 #define XT_D_CONFIG 0x0080
125 #define XT_D_JS 0x0100
126 #define XT_D_FAVORITE 0x0200
127 #define XT_D_PRINTING 0x0400
128 #define XT_D_COOKIE 0x0800
129 #define XT_D_KEYBINDING 0x1000
130 #define XT_D_CLIP 0x2000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
147 #else
148 #define DPRINTF(x...)
149 #define DNPRINTF(n,x...)
150 #endif
152 #define LENGTH(x) (sizeof x / sizeof x[0])
153 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
154 ~(GDK_BUTTON1_MASK) & \
155 ~(GDK_BUTTON2_MASK) & \
156 ~(GDK_BUTTON3_MASK) & \
157 ~(GDK_BUTTON4_MASK) & \
158 ~(GDK_BUTTON5_MASK))
160 char *icons[] = {
161 "xxxtermicon16.png",
162 "xxxtermicon32.png",
163 "xxxtermicon48.png",
164 "xxxtermicon64.png",
165 "xxxtermicon128.png"
168 struct tab {
169 TAILQ_ENTRY(tab) entry;
170 GtkWidget *vbox;
171 GtkWidget *tab_content;
172 GtkWidget *label;
173 GtkWidget *spinner;
174 GtkWidget *uri_entry;
175 GtkWidget *search_entry;
176 GtkWidget *toolbar;
177 GtkWidget *browser_win;
178 GtkWidget *statusbar;
179 GtkWidget *cmd;
180 GtkWidget *oops;
181 GtkWidget *backward;
182 GtkWidget *forward;
183 GtkWidget *stop;
184 GtkWidget *gohome;
185 GtkWidget *js_toggle;
186 GtkEntryCompletion *completion;
187 guint tab_id;
188 WebKitWebView *wv;
190 WebKitWebHistoryItem *item;
191 WebKitWebBackForwardList *bfl;
193 /* favicon */
194 WebKitNetworkRequest *icon_request;
195 WebKitDownload *icon_download;
196 gchar *icon_dest_uri;
198 /* adjustments for browser */
199 GtkScrollbar *sb_h;
200 GtkScrollbar *sb_v;
201 GtkAdjustment *adjust_h;
202 GtkAdjustment *adjust_v;
204 /* flags */
205 int focus_wv;
206 int ctrl_click;
207 gchar *status;
208 int xtp_meaning; /* identifies dls/favorites */
210 /* hints */
211 int hints_on;
212 int hint_mode;
213 #define XT_HINT_NONE (0)
214 #define XT_HINT_NUMERICAL (1)
215 #define XT_HINT_ALPHANUM (2)
216 char hint_buf[128];
217 char hint_num[128];
219 /* custom stylesheet */
220 int styled;
221 char *stylesheet;
223 /* search */
224 char *search_text;
225 int search_forward;
227 /* settings */
228 WebKitWebSettings *settings;
229 int font_size;
230 gchar *user_agent;
232 TAILQ_HEAD(tab_list, tab);
234 struct history {
235 RB_ENTRY(history) entry;
236 const gchar *uri;
237 const gchar *title;
239 RB_HEAD(history_list, history);
241 struct download {
242 RB_ENTRY(download) entry;
243 int id;
244 WebKitDownload *download;
245 struct tab *tab;
247 RB_HEAD(download_list, download);
249 struct domain {
250 RB_ENTRY(domain) entry;
251 gchar *d;
252 int handy; /* app use */
254 RB_HEAD(domain_list, domain);
256 struct undo {
257 TAILQ_ENTRY(undo) entry;
258 gchar *uri;
259 GList *history;
260 int back; /* Keeps track of how many back
261 * history items there are. */
263 TAILQ_HEAD(undo_tailq, undo);
265 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
266 int next_download_id = 1;
268 struct karg {
269 int i;
270 char *s;
271 int p;
274 /* defines */
275 #define XT_NAME ("XXXTerm")
276 #define XT_DIR (".xxxterm")
277 #define XT_CACHE_DIR ("cache")
278 #define XT_CERT_DIR ("certs/")
279 #define XT_SESSIONS_DIR ("sessions/")
280 #define XT_CONF_FILE ("xxxterm.conf")
281 #define XT_FAVS_FILE ("favorites")
282 #define XT_SAVED_TABS_FILE ("main_session")
283 #define XT_RESTART_TABS_FILE ("restart_tabs")
284 #define XT_SOCKET_FILE ("socket")
285 #define XT_HISTORY_FILE ("history")
286 #define XT_REJECT_FILE ("rejected.txt")
287 #define XT_COOKIE_FILE ("cookies.txt")
288 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
289 #define XT_CB_HANDLED (TRUE)
290 #define XT_CB_PASSTHROUGH (FALSE)
291 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
292 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
293 #define XT_DLMAN_REFRESH "10"
294 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
295 "td{overflow: hidden;" \
296 " padding: 2px 2px 2px 2px;" \
297 " border: 1px solid black;" \
298 " vertical-align:top;" \
299 " word-wrap: break-word}\n" \
300 "tr:hover{background: #ffff99}\n" \
301 "th{background-color: #cccccc;" \
302 " border: 1px solid black}\n" \
303 "table{width: 100%%;" \
304 " border: 1px black solid;" \
305 " border-collapse:collapse}\n" \
306 ".progress-outer{" \
307 "border: 1px solid black;" \
308 " height: 8px;" \
309 " width: 90%%}\n" \
310 ".progress-inner{float: left;" \
311 " height: 8px;" \
312 " background: green}\n" \
313 ".dlstatus{font-size: small;" \
314 " text-align: center}\n" \
315 "</style>\n"
316 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
317 #define XT_MAX_UNDO_CLOSE_TAB (32)
318 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
319 #define XT_PRINT_EXTRA_MARGIN 10
321 /* colors */
322 #define XT_COLOR_RED "#cc0000"
323 #define XT_COLOR_YELLOW "#ffff66"
324 #define XT_COLOR_BLUE "lightblue"
325 #define XT_COLOR_GREEN "#99ff66"
326 #define XT_COLOR_WHITE "white"
327 #define XT_COLOR_BLACK "black"
330 * xxxterm "protocol" (xtp)
331 * We use this for managing stuff like downloads and favorites. They
332 * make magical HTML pages in memory which have xxxt:// links in order
333 * to communicate with xxxterm's internals. These links take the format:
334 * xxxt://class/session_key/action/arg
336 * Don't begin xtp class/actions as 0. atoi returns that on error.
338 * Typically we have not put addition of items in this framework, as
339 * adding items is either done via an ex-command or via a keybinding instead.
342 #define XT_XTP_STR "xxxt://"
344 /* XTP classes (xxxt://<class>) */
345 #define XT_XTP_INVALID 0 /* invalid */
346 #define XT_XTP_DL 1 /* downloads */
347 #define XT_XTP_HL 2 /* history */
348 #define XT_XTP_CL 3 /* cookies */
349 #define XT_XTP_FL 4 /* favorites */
351 /* XTP download actions */
352 #define XT_XTP_DL_LIST 1
353 #define XT_XTP_DL_CANCEL 2
354 #define XT_XTP_DL_REMOVE 3
356 /* XTP history actions */
357 #define XT_XTP_HL_LIST 1
358 #define XT_XTP_HL_REMOVE 2
360 /* XTP cookie actions */
361 #define XT_XTP_CL_LIST 1
362 #define XT_XTP_CL_REMOVE 2
364 /* XTP cookie actions */
365 #define XT_XTP_FL_LIST 1
366 #define XT_XTP_FL_REMOVE 2
368 /* xtp tab meanings - identifies which tabs have xtp pages in */
369 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
370 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
371 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
372 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
373 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
375 /* actions */
376 #define XT_MOVE_INVALID (0)
377 #define XT_MOVE_DOWN (1)
378 #define XT_MOVE_UP (2)
379 #define XT_MOVE_BOTTOM (3)
380 #define XT_MOVE_TOP (4)
381 #define XT_MOVE_PAGEDOWN (5)
382 #define XT_MOVE_PAGEUP (6)
383 #define XT_MOVE_HALFDOWN (7)
384 #define XT_MOVE_HALFUP (8)
385 #define XT_MOVE_LEFT (9)
386 #define XT_MOVE_FARLEFT (10)
387 #define XT_MOVE_RIGHT (11)
388 #define XT_MOVE_FARRIGHT (12)
390 #define XT_TAB_LAST (-4)
391 #define XT_TAB_FIRST (-3)
392 #define XT_TAB_PREV (-2)
393 #define XT_TAB_NEXT (-1)
394 #define XT_TAB_INVALID (0)
395 #define XT_TAB_NEW (1)
396 #define XT_TAB_DELETE (2)
397 #define XT_TAB_DELQUIT (3)
398 #define XT_TAB_OPEN (4)
399 #define XT_TAB_UNDO_CLOSE (5)
400 #define XT_TAB_SHOW (6)
401 #define XT_TAB_HIDE (7)
403 #define XT_NAV_INVALID (0)
404 #define XT_NAV_BACK (1)
405 #define XT_NAV_FORWARD (2)
406 #define XT_NAV_RELOAD (3)
407 #define XT_NAV_RELOAD_CACHE (4)
409 #define XT_FOCUS_INVALID (0)
410 #define XT_FOCUS_URI (1)
411 #define XT_FOCUS_SEARCH (2)
413 #define XT_SEARCH_INVALID (0)
414 #define XT_SEARCH_NEXT (1)
415 #define XT_SEARCH_PREV (2)
417 #define XT_PASTE_CURRENT_TAB (0)
418 #define XT_PASTE_NEW_TAB (1)
420 #define XT_FONT_SET (0)
422 #define XT_URL_SHOW (1)
423 #define XT_URL_HIDE (2)
425 #define XT_STATUSBAR_SHOW (1)
426 #define XT_STATUSBAR_HIDE (2)
428 #define XT_WL_TOGGLE (1<<0)
429 #define XT_WL_ENABLE (1<<1)
430 #define XT_WL_DISABLE (1<<2)
431 #define XT_WL_FQDN (1<<3) /* default */
432 #define XT_WL_TOPLEVEL (1<<4)
433 #define XT_WL_PERSISTENT (1<<5)
434 #define XT_WL_SESSION (1<<6)
435 #define XT_WL_RELOAD (1<<7)
437 #define XT_SHOW (1<<7)
438 #define XT_DELETE (1<<8)
439 #define XT_SAVE (1<<9)
440 #define XT_OPEN (1<<10)
442 #define XT_CMD_OPEN (0)
443 #define XT_CMD_OPEN_CURRENT (1)
444 #define XT_CMD_TABNEW (2)
445 #define XT_CMD_TABNEW_CURRENT (3)
447 #define XT_STATUS_NOTHING (0)
448 #define XT_STATUS_LINK (1)
449 #define XT_STATUS_URI (2)
450 #define XT_STATUS_LOADING (3)
452 #define XT_SES_DONOTHING (0)
453 #define XT_SES_CLOSETABS (1)
455 #define XT_BM_NORMAL (0)
456 #define XT_BM_WHITELIST (1)
457 #define XT_BM_KIOSK (2)
459 #define XT_PREFIX (1<<0)
460 #define XT_USERARG (1<<1)
461 #define XT_URLARG (1<<2)
462 #define XT_INTARG (1<<3)
464 /* mime types */
465 struct mime_type {
466 char *mt_type;
467 char *mt_action;
468 int mt_default;
469 int mt_download;
470 TAILQ_ENTRY(mime_type) entry;
472 TAILQ_HEAD(mime_type_list, mime_type);
474 /* uri aliases */
475 struct alias {
476 char *a_name;
477 char *a_uri;
478 TAILQ_ENTRY(alias) entry;
480 TAILQ_HEAD(alias_list, alias);
482 /* settings that require restart */
483 int tabless = 0; /* allow only 1 tab */
484 int enable_socket = 0;
485 int single_instance = 0; /* only allow one xxxterm to run */
486 int fancy_bar = 1; /* fancy toolbar */
487 int browser_mode = XT_BM_NORMAL;
488 int enable_localstorage = 0;
490 /* runtime settings */
491 int show_tabs = 1; /* show tabs on notebook */
492 int show_url = 1; /* show url toolbar on notebook */
493 int show_statusbar = 0; /* vimperator style status bar */
494 int ctrl_click_focus = 0; /* ctrl click gets focus */
495 int cookies_enabled = 1; /* enable cookies */
496 int read_only_cookies = 0; /* enable to not write cookies */
497 int enable_scripts = 1;
498 int enable_plugins = 0;
499 int default_font_size = 12;
500 gfloat default_zoom_level = 1.0;
501 int window_height = 768;
502 int window_width = 1024;
503 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
504 unsigned refresh_interval = 10; /* download refresh interval */
505 int enable_cookie_whitelist = 0;
506 int enable_js_whitelist = 0;
507 time_t session_timeout = 3600; /* cookie session timeout */
508 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
509 char *ssl_ca_file = NULL;
510 char *resource_dir = NULL;
511 gboolean ssl_strict_certs = FALSE;
512 int append_next = 1; /* append tab after current tab */
513 char *home = NULL;
514 char *search_string = NULL;
515 char *http_proxy = NULL;
516 char download_dir[PATH_MAX];
517 char runtime_settings[PATH_MAX]; /* override of settings */
518 int allow_volatile_cookies = 0;
519 int save_global_history = 0; /* save global history to disk */
520 char *user_agent = NULL;
521 int save_rejected_cookies = 0;
522 time_t session_autosave = 0;
523 int guess_search = 0;
524 int dns_prefetch = FALSE;
525 gint max_connections = 25;
526 gint max_host_connections = 5;
527 gint enable_spell_checking = 0;
528 char *spell_check_languages = NULL;
530 char *cmd_font_name = NULL;
531 char *statusbar_font_name = NULL;
532 PangoFontDescription *cmd_font;
533 PangoFontDescription *statusbar_font;
535 struct settings;
536 struct key_binding;
537 int set_download_dir(struct settings *, char *);
538 int set_work_dir(struct settings *, char *);
539 int set_runtime_dir(struct settings *, char *);
540 int set_browser_mode(struct settings *, char *);
541 int set_cookie_policy(struct settings *, char *);
542 int add_alias(struct settings *, char *);
543 int add_mime_type(struct settings *, char *);
544 int add_cookie_wl(struct settings *, char *);
545 int add_js_wl(struct settings *, char *);
546 int add_kb(struct settings *, char *);
547 void button_set_stockid(GtkWidget *, char *);
548 GtkWidget * create_button(char *, char *, int);
550 char *get_browser_mode(struct settings *);
551 char *get_cookie_policy(struct settings *);
553 char *get_download_dir(struct settings *);
554 char *get_work_dir(struct settings *);
555 char *get_runtime_dir(struct settings *);
557 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
558 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
559 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
560 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
561 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
563 void recalc_tabs(void);
565 struct special {
566 int (*set)(struct settings *, char *);
567 char *(*get)(struct settings *);
568 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
571 struct special s_browser_mode = {
572 set_browser_mode,
573 get_browser_mode,
574 NULL
577 struct special s_cookie = {
578 set_cookie_policy,
579 get_cookie_policy,
580 NULL
583 struct special s_alias = {
584 add_alias,
585 NULL,
586 walk_alias
589 struct special s_mime = {
590 add_mime_type,
591 NULL,
592 walk_mime_type
595 struct special s_js = {
596 add_js_wl,
597 NULL,
598 walk_js_wl
601 struct special s_kb = {
602 add_kb,
603 NULL,
604 walk_kb
607 struct special s_cookie_wl = {
608 add_cookie_wl,
609 NULL,
610 walk_cookie_wl
613 struct special s_download_dir = {
614 set_download_dir,
615 get_download_dir,
616 NULL
619 struct special s_work_dir = {
620 set_work_dir,
621 get_work_dir,
622 NULL
625 struct settings {
626 char *name;
627 int type;
628 #define XT_S_INVALID (0)
629 #define XT_S_INT (1)
630 #define XT_S_STR (2)
631 #define XT_S_FLOAT (3)
632 uint32_t flags;
633 #define XT_SF_RESTART (1<<0)
634 #define XT_SF_RUNTIME (1<<1)
635 int *ival;
636 char **sval;
637 struct special *s;
638 gfloat *fval;
639 } rs[] = {
640 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
641 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
642 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
643 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
644 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
645 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
646 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
647 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
648 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
649 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
650 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
651 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
652 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
653 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
654 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
655 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
656 { "home", XT_S_STR, 0, NULL, &home, NULL },
657 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
658 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
659 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
660 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
661 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
662 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
663 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
664 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
665 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
666 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
667 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
668 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
669 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
670 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
671 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
672 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
673 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
674 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
675 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
676 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
677 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
678 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
679 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
680 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
681 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
683 /* font settings */
684 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
685 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
687 /* runtime settings */
688 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
689 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
690 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
691 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
692 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
695 int about(struct tab *, struct karg *);
696 int blank(struct tab *, struct karg *);
697 int ca_cmd(struct tab *, struct karg *);
698 int cookie_show_wl(struct tab *, struct karg *);
699 int js_show_wl(struct tab *, struct karg *);
700 int help(struct tab *, struct karg *);
701 int set(struct tab *, struct karg *);
702 int stats(struct tab *, struct karg *);
703 int marco(struct tab *, struct karg *);
704 const char * marco_message(int *);
705 int xtp_page_cl(struct tab *, struct karg *);
706 int xtp_page_dl(struct tab *, struct karg *);
707 int xtp_page_fl(struct tab *, struct karg *);
708 int xtp_page_hl(struct tab *, struct karg *);
709 void xt_icon_from_file(struct tab *, char *);
711 #define XT_URI_ABOUT ("about:")
712 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
713 #define XT_URI_ABOUT_ABOUT ("about")
714 #define XT_URI_ABOUT_BLANK ("blank")
715 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
716 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
717 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
718 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
719 #define XT_URI_ABOUT_FAVORITES ("favorites")
720 #define XT_URI_ABOUT_HELP ("help")
721 #define XT_URI_ABOUT_HISTORY ("history")
722 #define XT_URI_ABOUT_JSWL ("jswl")
723 #define XT_URI_ABOUT_SET ("set")
724 #define XT_URI_ABOUT_STATS ("stats")
725 #define XT_URI_ABOUT_MARCO ("marco")
727 struct about_type {
728 char *name;
729 int (*func)(struct tab *, struct karg *);
730 } about_list[] = {
731 { XT_URI_ABOUT_ABOUT, about },
732 { XT_URI_ABOUT_BLANK, blank },
733 { XT_URI_ABOUT_CERTS, ca_cmd },
734 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
735 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
736 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
737 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
738 { XT_URI_ABOUT_HELP, help },
739 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
740 { XT_URI_ABOUT_JSWL, js_show_wl },
741 { XT_URI_ABOUT_SET, set },
742 { XT_URI_ABOUT_STATS, stats },
743 { XT_URI_ABOUT_MARCO, marco },
746 /* globals */
747 extern char *__progname;
748 char **start_argv;
749 struct passwd *pwd;
750 GtkWidget *main_window;
751 GtkNotebook *notebook;
752 GtkWidget *arrow, *abtn;
753 struct tab_list tabs;
754 struct history_list hl;
755 struct download_list downloads;
756 struct domain_list c_wl;
757 struct domain_list js_wl;
758 struct undo_tailq undos;
759 struct keybinding_list kbl;
760 int undo_count;
761 int updating_dl_tabs = 0;
762 int updating_hl_tabs = 0;
763 int updating_cl_tabs = 0;
764 int updating_fl_tabs = 0;
765 char *global_search;
766 uint64_t blocked_cookies = 0;
767 char named_session[PATH_MAX];
768 int icon_size_map(int);
770 GtkListStore *completion_model;
771 void completion_add(struct tab *);
772 void completion_add_uri(const gchar *);
773 void xxx_dir(char *);
775 void
776 sigchild(int sig)
778 int saved_errno, status;
779 pid_t pid;
781 saved_errno = errno;
783 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
784 if (pid == -1) {
785 if (errno == EINTR)
786 continue;
787 if (errno != ECHILD) {
789 clog_warn("sigchild: waitpid:");
792 break;
795 if (WIFEXITED(status)) {
796 if (WEXITSTATUS(status) != 0) {
798 clog_warnx("sigchild: child exit status: %d",
799 WEXITSTATUS(status));
802 } else {
804 clog_warnx("sigchild: child is terminated abnormally");
809 errno = saved_errno;
813 is_g_object_setting(GObject *o, char *str)
815 guint n_props = 0, i;
816 GParamSpec **proplist;
818 if (! G_IS_OBJECT(o))
819 return (0);
821 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
822 &n_props);
824 for (i=0; i < n_props; i++) {
825 if (! strcmp(proplist[i]->name, str))
826 return (1);
828 return (0);
831 gchar *
832 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
834 gchar *r;
836 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
837 "<head>\n"
838 "<title>%s</title>\n"
839 "%s"
840 "%s"
841 "</head>\n"
842 "<body>\n"
843 "<h1>%s</h1>\n"
844 "%s\n</body>\n"
845 "</html>",
846 title,
847 addstyles ? XT_PAGE_STYLE : "",
848 head,
849 title,
850 body);
852 return r;
856 * Display a web page from a HTML string in memory, rather than from a URL
858 void
859 load_webkit_string(struct tab *t, const char *str, gchar *title)
861 gchar *uri;
862 char file[PATH_MAX];
864 /* we set this to indicate we want to manually do navaction */
865 if (t->bfl)
866 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
868 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
869 #if GTK_CHECK_VERSION(2, 20, 0)
870 gtk_spinner_stop(GTK_SPINNER(t->spinner));
871 gtk_widget_hide(t->spinner);
872 #endif
874 if (title) {
875 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
876 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
877 g_free(uri);
879 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
880 xt_icon_from_file(t, file);
884 void
885 set_status(struct tab *t, gchar *s, int status)
887 gchar *type = NULL;
889 if (s == NULL)
890 return;
892 switch (status) {
893 case XT_STATUS_LOADING:
894 type = g_strdup_printf("Loading: %s", s);
895 s = type;
896 break;
897 case XT_STATUS_LINK:
898 type = g_strdup_printf("Link: %s", s);
899 if (!t->status)
900 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
901 s = type;
902 break;
903 case XT_STATUS_URI:
904 type = g_strdup_printf("%s", s);
905 if (!t->status) {
906 t->status = g_strdup(type);
908 s = type;
909 if (!t->status)
910 t->status = g_strdup(s);
911 break;
912 case XT_STATUS_NOTHING:
913 /* FALL THROUGH */
914 default:
915 break;
917 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
918 if (type)
919 g_free(type);
922 void
923 hide_oops(struct tab *t)
925 gtk_widget_hide(t->oops);
928 void
929 hide_cmd(struct tab *t)
931 gtk_widget_hide(t->cmd);
934 void
935 show_cmd(struct tab *t)
937 gtk_widget_hide(t->oops);
938 gtk_widget_show(t->cmd);
941 void
942 show_oops(struct tab *t, const char *fmt, ...)
944 va_list ap;
945 char *msg;
947 if (fmt == NULL)
948 return;
950 va_start(ap, fmt);
951 if (vasprintf(&msg, fmt, ap) == -1)
952 errx(1, "show_oops failed");
953 va_end(ap);
955 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
956 gtk_widget_hide(t->cmd);
957 gtk_widget_show(t->oops);
960 /* XXX collapse with show_oops */
961 void
962 show_oops_s(const char *fmt, ...)
964 va_list ap;
965 char *msg;
966 struct tab *ti, *t = NULL;
968 if (fmt == NULL)
969 return;
971 TAILQ_FOREACH(ti, &tabs, entry)
972 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
973 t = ti;
974 break;
976 if (t == NULL)
977 return;
979 va_start(ap, fmt);
980 if (vasprintf(&msg, fmt, ap) == -1)
981 errx(1, "show_oops_s failed");
982 va_end(ap);
984 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
985 gtk_widget_hide(t->cmd);
986 gtk_widget_show(t->oops);
989 char *
990 get_as_string(struct settings *s)
992 char *r = NULL;
994 if (s == NULL)
995 return (NULL);
997 if (s->s) {
998 if (s->s->get)
999 r = s->s->get(s);
1000 else
1001 warnx("get_as_string skip %s\n", s->name);
1002 } else if (s->type == XT_S_INT)
1003 r = g_strdup_printf("%d", *s->ival);
1004 else if (s->type == XT_S_STR)
1005 r = g_strdup(*s->sval);
1006 else if (s->type == XT_S_FLOAT)
1007 r = g_strdup_printf("%f", *s->fval);
1008 else
1009 r = g_strdup_printf("INVALID TYPE");
1011 return (r);
1014 void
1015 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1017 int i;
1018 char *s;
1020 for (i = 0; i < LENGTH(rs); i++) {
1021 if (rs[i].s && rs[i].s->walk)
1022 rs[i].s->walk(&rs[i], cb, cb_args);
1023 else {
1024 s = get_as_string(&rs[i]);
1025 cb(&rs[i], s, cb_args);
1026 g_free(s);
1032 set_browser_mode(struct settings *s, char *val)
1034 if (!strcmp(val, "whitelist")) {
1035 browser_mode = XT_BM_WHITELIST;
1036 allow_volatile_cookies = 0;
1037 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1038 cookies_enabled = 1;
1039 enable_cookie_whitelist = 1;
1040 read_only_cookies = 0;
1041 save_rejected_cookies = 0;
1042 session_timeout = 3600;
1043 enable_scripts = 0;
1044 enable_js_whitelist = 1;
1045 enable_localstorage = 0;
1046 } else if (!strcmp(val, "normal")) {
1047 browser_mode = XT_BM_NORMAL;
1048 allow_volatile_cookies = 0;
1049 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1050 cookies_enabled = 1;
1051 enable_cookie_whitelist = 0;
1052 read_only_cookies = 0;
1053 save_rejected_cookies = 0;
1054 session_timeout = 3600;
1055 enable_scripts = 1;
1056 enable_js_whitelist = 0;
1057 enable_localstorage = 1;
1058 } else if (!strcmp(val, "kiosk")) {
1059 browser_mode = XT_BM_KIOSK;
1060 allow_volatile_cookies = 0;
1061 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1062 cookies_enabled = 1;
1063 enable_cookie_whitelist = 0;
1064 read_only_cookies = 0;
1065 save_rejected_cookies = 0;
1066 session_timeout = 3600;
1067 enable_scripts = 1;
1068 enable_js_whitelist = 0;
1069 enable_localstorage = 1;
1070 show_tabs = 0;
1071 tabless = 1;
1072 } else
1073 return (1);
1075 return (0);
1078 char *
1079 get_browser_mode(struct settings *s)
1081 char *r = NULL;
1083 if (browser_mode == XT_BM_WHITELIST)
1084 r = g_strdup("whitelist");
1085 else if (browser_mode == XT_BM_NORMAL)
1086 r = g_strdup("normal");
1087 else if (browser_mode == XT_BM_KIOSK)
1088 r = g_strdup("kiosk");
1089 else
1090 return (NULL);
1092 return (r);
1096 set_cookie_policy(struct settings *s, char *val)
1098 if (!strcmp(val, "no3rdparty"))
1099 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1100 else if (!strcmp(val, "accept"))
1101 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1102 else if (!strcmp(val, "reject"))
1103 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1104 else
1105 return (1);
1107 return (0);
1110 char *
1111 get_cookie_policy(struct settings *s)
1113 char *r = NULL;
1115 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1116 r = g_strdup("no3rdparty");
1117 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1118 r = g_strdup("accept");
1119 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1120 r = g_strdup("reject");
1121 else
1122 return (NULL);
1124 return (r);
1127 char *
1128 get_download_dir(struct settings *s)
1130 if (download_dir[0] == '\0')
1131 return (0);
1132 return (g_strdup(download_dir));
1136 set_download_dir(struct settings *s, char *val)
1138 if (val[0] == '~')
1139 snprintf(download_dir, sizeof download_dir, "%s/%s",
1140 pwd->pw_dir, &val[1]);
1141 else
1142 strlcpy(download_dir, val, sizeof download_dir);
1144 return (0);
1148 * Session IDs.
1149 * We use these to prevent people putting xxxt:// URLs on
1150 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1152 #define XT_XTP_SES_KEY_SZ 8
1153 #define XT_XTP_SES_KEY_HEX_FMT \
1154 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1155 char *dl_session_key; /* downloads */
1156 char *hl_session_key; /* history list */
1157 char *cl_session_key; /* cookie list */
1158 char *fl_session_key; /* favorites list */
1160 char work_dir[PATH_MAX];
1161 char certs_dir[PATH_MAX];
1162 char cache_dir[PATH_MAX];
1163 char sessions_dir[PATH_MAX];
1164 char cookie_file[PATH_MAX];
1165 SoupURI *proxy_uri = NULL;
1166 SoupSession *session;
1167 SoupCookieJar *s_cookiejar;
1168 SoupCookieJar *p_cookiejar;
1169 char rc_fname[PATH_MAX];
1171 struct mime_type_list mtl;
1172 struct alias_list aliases;
1174 /* protos */
1175 struct tab *create_new_tab(char *, struct undo *, int, int);
1176 void delete_tab(struct tab *);
1177 void adjustfont_webkit(struct tab *, int);
1178 int run_script(struct tab *, char *);
1179 int download_rb_cmp(struct download *, struct download *);
1180 gboolean cmd_execute(struct tab *t, char *str);
1183 history_rb_cmp(struct history *h1, struct history *h2)
1185 return (strcmp(h1->uri, h2->uri));
1187 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1190 domain_rb_cmp(struct domain *d1, struct domain *d2)
1192 return (strcmp(d1->d, d2->d));
1194 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1196 char *
1197 get_work_dir(struct settings *s)
1199 if (work_dir[0] == '\0')
1200 return (0);
1201 return (g_strdup(work_dir));
1205 set_work_dir(struct settings *s, char *val)
1207 if (val[0] == '~')
1208 snprintf(work_dir, sizeof work_dir, "%s/%s",
1209 pwd->pw_dir, &val[1]);
1210 else
1211 strlcpy(work_dir, val, sizeof work_dir);
1213 return (0);
1217 * generate a session key to secure xtp commands.
1218 * pass in a ptr to the key in question and it will
1219 * be modified in place.
1221 void
1222 generate_xtp_session_key(char **key)
1224 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1226 /* free old key */
1227 if (*key)
1228 g_free(*key);
1230 /* make a new one */
1231 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1232 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1233 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1234 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1236 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1240 * validate a xtp session key.
1241 * return 1 if OK
1244 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1246 if (strcmp(trusted, untrusted) != 0) {
1247 show_oops(t, "%s: xtp session key mismatch possible spoof",
1248 __func__);
1249 return (0);
1252 return (1);
1256 download_rb_cmp(struct download *e1, struct download *e2)
1258 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1260 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1262 struct valid_url_types {
1263 char *type;
1264 } vut[] = {
1265 { "http://" },
1266 { "https://" },
1267 { "ftp://" },
1268 { "file://" },
1269 { XT_XTP_STR },
1273 valid_url_type(char *url)
1275 int i;
1277 for (i = 0; i < LENGTH(vut); i++)
1278 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1279 return (0);
1281 return (1);
1284 void
1285 print_cookie(char *msg, SoupCookie *c)
1287 if (c == NULL)
1288 return;
1290 if (msg)
1291 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1292 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1293 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1294 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1295 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1296 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1297 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1298 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1299 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1300 DNPRINTF(XT_D_COOKIE, "====================================\n");
1303 void
1304 walk_alias(struct settings *s,
1305 void (*cb)(struct settings *, char *, void *), void *cb_args)
1307 struct alias *a;
1308 char *str;
1310 if (s == NULL || cb == NULL) {
1311 show_oops_s("walk_alias invalid parameters");
1312 return;
1315 TAILQ_FOREACH(a, &aliases, entry) {
1316 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1317 cb(s, str, cb_args);
1318 g_free(str);
1322 char *
1323 match_alias(char *url_in)
1325 struct alias *a;
1326 char *arg;
1327 char *url_out = NULL, *search, *enc_arg;
1329 search = g_strdup(url_in);
1330 arg = search;
1331 if (strsep(&arg, " \t") == NULL) {
1332 show_oops_s("match_alias: NULL URL");
1333 goto done;
1336 TAILQ_FOREACH(a, &aliases, entry) {
1337 if (!strcmp(search, a->a_name))
1338 break;
1341 if (a != NULL) {
1342 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1343 a->a_name);
1344 if (arg != NULL) {
1345 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1346 url_out = g_strdup_printf(a->a_uri, enc_arg);
1347 g_free(enc_arg);
1348 } else
1349 url_out = g_strdup(a->a_uri);
1351 done:
1352 g_free(search);
1353 return (url_out);
1356 char *
1357 guess_url_type(char *url_in)
1359 struct stat sb;
1360 char *url_out = NULL, *enc_search = NULL;
1362 url_out = match_alias(url_in);
1363 if (url_out != NULL)
1364 return (url_out);
1366 if (guess_search) {
1368 * If there is no dot nor slash in the string and it isn't a
1369 * path to a local file and doesn't resolves to an IP, assume
1370 * that the user wants to search for the string.
1373 if (strchr(url_in, '.') == NULL &&
1374 strchr(url_in, '/') == NULL &&
1375 stat(url_in, &sb) != 0 &&
1376 gethostbyname(url_in) == NULL) {
1378 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1379 url_out = g_strdup_printf(search_string, enc_search);
1380 g_free(enc_search);
1381 return (url_out);
1385 /* XXX not sure about this heuristic */
1386 if (stat(url_in, &sb) == 0)
1387 url_out = g_strdup_printf("file://%s", url_in);
1388 else
1389 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1391 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1393 return (url_out);
1396 void
1397 load_uri(struct tab *t, gchar *uri)
1399 struct karg args;
1400 gchar *newuri = NULL;
1401 int i;
1403 if (uri == NULL)
1404 return;
1406 /* Strip leading spaces. */
1407 while(*uri && isspace(*uri))
1408 uri++;
1410 if (strlen(uri) == 0) {
1411 blank(t, NULL);
1412 return;
1415 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1416 for (i = 0; i < LENGTH(about_list); i++)
1417 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1418 bzero(&args, sizeof args);
1419 about_list[i].func(t, &args);
1420 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1421 FALSE);
1422 return;
1424 show_oops(t, "invalid about page");
1425 return;
1428 if (valid_url_type(uri)) {
1429 newuri = guess_url_type(uri);
1430 uri = newuri;
1433 set_status(t, (char *)uri, XT_STATUS_LOADING);
1434 webkit_web_view_load_uri(t->wv, uri);
1436 if (newuri)
1437 g_free(newuri);
1440 const gchar *
1441 get_uri(WebKitWebView *wv)
1443 const gchar *uri;
1445 uri = webkit_web_view_get_uri(wv);
1447 if (uri && strlen(uri) > 0)
1448 return (uri);
1449 else
1450 return (NULL);
1454 add_alias(struct settings *s, char *line)
1456 char *l, *alias;
1457 struct alias *a = NULL;
1459 if (s == NULL || line == NULL) {
1460 show_oops_s("add_alias invalid parameters");
1461 return (1);
1464 l = line;
1465 a = g_malloc(sizeof(*a));
1467 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1468 show_oops_s("add_alias: incomplete alias definition");
1469 goto bad;
1471 if (strlen(alias) == 0 || strlen(l) == 0) {
1472 show_oops_s("add_alias: invalid alias definition");
1473 goto bad;
1476 a->a_name = g_strdup(alias);
1477 a->a_uri = g_strdup(l);
1479 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1481 TAILQ_INSERT_TAIL(&aliases, a, entry);
1483 return (0);
1484 bad:
1485 if (a)
1486 g_free(a);
1487 return (1);
1491 add_mime_type(struct settings *s, char *line)
1493 char *mime_type;
1494 char *l;
1495 struct mime_type *m = NULL;
1496 int downloadfirst = 0;
1498 /* XXX this could be smarter */
1500 if (line == NULL && strlen(line) == 0) {
1501 show_oops_s("add_mime_type invalid parameters");
1502 return (1);
1505 l = line;
1506 if (*l == '@') {
1507 downloadfirst = 1;
1508 l++;
1510 m = g_malloc(sizeof(*m));
1512 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1513 show_oops_s("add_mime_type: invalid mime_type");
1514 goto bad;
1516 if (mime_type[strlen(mime_type) - 1] == '*') {
1517 mime_type[strlen(mime_type) - 1] = '\0';
1518 m->mt_default = 1;
1519 } else
1520 m->mt_default = 0;
1522 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1523 show_oops_s("add_mime_type: invalid mime_type");
1524 goto bad;
1527 m->mt_type = g_strdup(mime_type);
1528 m->mt_action = g_strdup(l);
1529 m->mt_download = downloadfirst;
1531 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1532 m->mt_type, m->mt_action, m->mt_default);
1534 TAILQ_INSERT_TAIL(&mtl, m, entry);
1536 return (0);
1537 bad:
1538 if (m)
1539 g_free(m);
1540 return (1);
1543 struct mime_type *
1544 find_mime_type(char *mime_type)
1546 struct mime_type *m, *def = NULL, *rv = NULL;
1548 TAILQ_FOREACH(m, &mtl, entry) {
1549 if (m->mt_default &&
1550 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1551 def = m;
1553 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1554 rv = m;
1555 break;
1559 if (rv == NULL)
1560 rv = def;
1562 return (rv);
1565 void
1566 walk_mime_type(struct settings *s,
1567 void (*cb)(struct settings *, char *, void *), void *cb_args)
1569 struct mime_type *m;
1570 char *str;
1572 if (s == NULL || cb == NULL)
1573 show_oops_s("walk_mime_type invalid parameters");
1575 TAILQ_FOREACH(m, &mtl, entry) {
1576 str = g_strdup_printf("%s%s --> %s",
1577 m->mt_type,
1578 m->mt_default ? "*" : "",
1579 m->mt_action);
1580 cb(s, str, cb_args);
1581 g_free(str);
1585 void
1586 wl_add(char *str, struct domain_list *wl, int handy)
1588 struct domain *d;
1589 int add_dot = 0;
1591 if (str == NULL || wl == NULL || strlen(str) < 2)
1592 return;
1594 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1596 /* treat *.moo.com the same as .moo.com */
1597 if (str[0] == '*' && str[1] == '.')
1598 str = &str[1];
1599 else if (str[0] == '.')
1600 str = &str[0];
1601 else
1602 add_dot = 1;
1604 d = g_malloc(sizeof *d);
1605 if (add_dot)
1606 d->d = g_strdup_printf(".%s", str);
1607 else
1608 d->d = g_strdup(str);
1609 d->handy = handy;
1611 if (RB_INSERT(domain_list, wl, d))
1612 goto unwind;
1614 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1615 return;
1616 unwind:
1617 if (d) {
1618 if (d->d)
1619 g_free(d->d);
1620 g_free(d);
1625 add_cookie_wl(struct settings *s, char *entry)
1627 wl_add(entry, &c_wl, 1);
1628 return (0);
1631 void
1632 walk_cookie_wl(struct settings *s,
1633 void (*cb)(struct settings *, char *, void *), void *cb_args)
1635 struct domain *d;
1637 if (s == NULL || cb == NULL) {
1638 show_oops_s("walk_cookie_wl invalid parameters");
1639 return;
1642 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1643 cb(s, d->d, cb_args);
1646 void
1647 walk_js_wl(struct settings *s,
1648 void (*cb)(struct settings *, char *, void *), void *cb_args)
1650 struct domain *d;
1652 if (s == NULL || cb == NULL) {
1653 show_oops_s("walk_js_wl invalid parameters");
1654 return;
1657 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1658 cb(s, d->d, cb_args);
1662 add_js_wl(struct settings *s, char *entry)
1664 wl_add(entry, &js_wl, 1 /* persistent */);
1665 return (0);
1668 struct domain *
1669 wl_find(const gchar *search, struct domain_list *wl)
1671 int i;
1672 struct domain *d = NULL, dfind;
1673 gchar *s = NULL;
1675 if (search == NULL || wl == NULL)
1676 return (NULL);
1677 if (strlen(search) < 2)
1678 return (NULL);
1680 if (search[0] != '.')
1681 s = g_strdup_printf(".%s", search);
1682 else
1683 s = g_strdup(search);
1685 for (i = strlen(s) - 1; i >= 0; i--) {
1686 if (s[i] == '.') {
1687 dfind.d = &s[i];
1688 d = RB_FIND(domain_list, wl, &dfind);
1689 if (d)
1690 goto done;
1694 done:
1695 if (s)
1696 g_free(s);
1698 return (d);
1701 struct domain *
1702 wl_find_uri(const gchar *s, struct domain_list *wl)
1704 int i;
1705 char *ss;
1706 struct domain *r;
1708 if (s == NULL || wl == NULL)
1709 return (NULL);
1711 if (!strncmp(s, "http://", strlen("http://")))
1712 s = &s[strlen("http://")];
1713 else if (!strncmp(s, "https://", strlen("https://")))
1714 s = &s[strlen("https://")];
1716 if (strlen(s) < 2)
1717 return (NULL);
1719 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1720 /* chop string at first slash */
1721 if (s[i] == '/' || s[i] == '\0') {
1722 ss = g_strdup(s);
1723 ss[i] = '\0';
1724 r = wl_find(ss, wl);
1725 g_free(ss);
1726 return (r);
1729 return (NULL);
1732 char *
1733 get_toplevel_domain(char *domain)
1735 char *s;
1736 int found = 0;
1738 if (domain == NULL)
1739 return (NULL);
1740 if (strlen(domain) < 2)
1741 return (NULL);
1743 s = &domain[strlen(domain) - 1];
1744 while (s != domain) {
1745 if (*s == '.') {
1746 found++;
1747 if (found == 2)
1748 return (s);
1750 s--;
1753 if (found)
1754 return (domain);
1756 return (NULL);
1760 settings_add(char *var, char *val)
1762 int i, rv, *p;
1763 gfloat *f;
1764 char **s;
1766 /* get settings */
1767 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1768 if (strcmp(var, rs[i].name))
1769 continue;
1771 if (rs[i].s) {
1772 if (rs[i].s->set(&rs[i], val))
1773 errx(1, "invalid value for %s: %s", var, val);
1774 rv = 1;
1775 break;
1776 } else
1777 switch (rs[i].type) {
1778 case XT_S_INT:
1779 p = rs[i].ival;
1780 *p = atoi(val);
1781 rv = 1;
1782 break;
1783 case XT_S_STR:
1784 s = rs[i].sval;
1785 if (s == NULL)
1786 errx(1, "invalid sval for %s",
1787 rs[i].name);
1788 if (*s)
1789 g_free(*s);
1790 *s = g_strdup(val);
1791 rv = 1;
1792 break;
1793 case XT_S_FLOAT:
1794 f = rs[i].fval;
1795 *f = atof(val);
1796 rv = 1;
1797 break;
1798 case XT_S_INVALID:
1799 default:
1800 errx(1, "invalid type for %s", var);
1802 break;
1804 return (rv);
1807 #define WS "\n= \t"
1808 void
1809 config_parse(char *filename, int runtime)
1811 FILE *config, *f;
1812 char *line, *cp, *var, *val;
1813 size_t len, lineno = 0;
1814 int handled;
1815 char file[PATH_MAX];
1816 struct stat sb;
1818 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1820 if (filename == NULL)
1821 return;
1823 if (runtime && runtime_settings[0] != '\0') {
1824 snprintf(file, sizeof file, "%s/%s",
1825 work_dir, runtime_settings);
1826 if (stat(file, &sb)) {
1827 warnx("runtime file doesn't exist, creating it");
1828 if ((f = fopen(file, "w")) == NULL)
1829 err(1, "runtime");
1830 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1831 fclose(f);
1833 } else
1834 strlcpy(file, filename, sizeof file);
1836 if ((config = fopen(file, "r")) == NULL) {
1837 warn("config_parse: cannot open %s", filename);
1838 return;
1841 for (;;) {
1842 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1843 if (feof(config) || ferror(config))
1844 break;
1846 cp = line;
1847 cp += (long)strspn(cp, WS);
1848 if (cp[0] == '\0') {
1849 /* empty line */
1850 free(line);
1851 continue;
1854 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1855 errx(1, "invalid config file entry: %s", line);
1857 cp += (long)strspn(cp, WS);
1859 if ((val = strsep(&cp, "\0")) == NULL)
1860 break;
1862 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
1863 handled = settings_add(var, val);
1864 if (handled == 0)
1865 errx(1, "invalid conf file entry: %s=%s", var, val);
1867 free(line);
1870 fclose(config);
1873 char *
1874 js_ref_to_string(JSContextRef context, JSValueRef ref)
1876 char *s = NULL;
1877 size_t l;
1878 JSStringRef jsref;
1880 jsref = JSValueToStringCopy(context, ref, NULL);
1881 if (jsref == NULL)
1882 return (NULL);
1884 l = JSStringGetMaximumUTF8CStringSize(jsref);
1885 s = g_malloc(l);
1886 if (s)
1887 JSStringGetUTF8CString(jsref, s, l);
1888 JSStringRelease(jsref);
1890 return (s);
1893 void
1894 disable_hints(struct tab *t)
1896 bzero(t->hint_buf, sizeof t->hint_buf);
1897 bzero(t->hint_num, sizeof t->hint_num);
1898 run_script(t, "vimprobable_clear()");
1899 t->hints_on = 0;
1900 t->hint_mode = XT_HINT_NONE;
1903 void
1904 enable_hints(struct tab *t)
1906 bzero(t->hint_buf, sizeof t->hint_buf);
1907 run_script(t, "vimprobable_show_hints()");
1908 t->hints_on = 1;
1909 t->hint_mode = XT_HINT_NONE;
1912 #define XT_JS_OPEN ("open;")
1913 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1914 #define XT_JS_FIRE ("fire;")
1915 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1916 #define XT_JS_FOUND ("found;")
1917 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1920 run_script(struct tab *t, char *s)
1922 JSGlobalContextRef ctx;
1923 WebKitWebFrame *frame;
1924 JSStringRef str;
1925 JSValueRef val, exception;
1926 char *es, buf[128];
1928 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1929 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1931 frame = webkit_web_view_get_main_frame(t->wv);
1932 ctx = webkit_web_frame_get_global_context(frame);
1934 str = JSStringCreateWithUTF8CString(s);
1935 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1936 NULL, 0, &exception);
1937 JSStringRelease(str);
1939 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1940 if (val == NULL) {
1941 es = js_ref_to_string(ctx, exception);
1942 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1943 g_free(es);
1944 return (1);
1945 } else {
1946 es = js_ref_to_string(ctx, val);
1947 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1949 /* handle return value right here */
1950 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1951 disable_hints(t);
1952 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1955 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1956 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1957 &es[XT_JS_FIRE_LEN]);
1958 run_script(t, buf);
1959 disable_hints(t);
1962 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1963 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1964 disable_hints(t);
1967 g_free(es);
1970 return (0);
1974 hint(struct tab *t, struct karg *args)
1977 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1979 if (t->hints_on == 0)
1980 enable_hints(t);
1981 else
1982 disable_hints(t);
1984 return (0);
1987 void
1988 apply_style(struct tab *t)
1990 g_object_set(G_OBJECT(t->settings),
1991 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1995 userstyle(struct tab *t, struct karg *args)
1997 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1999 if (t->styled) {
2000 t->styled = 0;
2001 g_object_set(G_OBJECT(t->settings),
2002 "user-stylesheet-uri", NULL, (char *)NULL);
2003 } else {
2004 t->styled = 1;
2005 apply_style(t);
2007 return (0);
2011 * Doesn't work fully, due to the following bug:
2012 * https://bugs.webkit.org/show_bug.cgi?id=51747
2015 restore_global_history(void)
2017 char file[PATH_MAX];
2018 FILE *f;
2019 struct history *h;
2020 gchar *uri;
2021 gchar *title;
2022 const char delim[3] = {'\\', '\\', '\0'};
2024 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2026 if ((f = fopen(file, "r")) == NULL) {
2027 warnx("%s: fopen", __func__);
2028 return (1);
2031 for (;;) {
2032 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2033 if (feof(f) || ferror(f))
2034 break;
2036 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2037 if (feof(f) || ferror(f)) {
2038 free(uri);
2039 warnx("%s: broken history file\n", __func__);
2040 return (1);
2043 if (uri && strlen(uri) && title && strlen(title)) {
2044 webkit_web_history_item_new_with_data(uri, title);
2045 h = g_malloc(sizeof(struct history));
2046 h->uri = g_strdup(uri);
2047 h->title = g_strdup(title);
2048 RB_INSERT(history_list, &hl, h);
2049 completion_add_uri(h->uri);
2050 } else {
2051 warnx("%s: failed to restore history\n", __func__);
2052 free(uri);
2053 free(title);
2054 return (1);
2057 free(uri);
2058 free(title);
2059 uri = NULL;
2060 title = NULL;
2063 return (0);
2067 save_global_history_to_disk(struct tab *t)
2069 char file[PATH_MAX];
2070 FILE *f;
2071 struct history *h;
2073 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2075 if ((f = fopen(file, "w")) == NULL) {
2076 show_oops(t, "%s: global history file: %s",
2077 __func__, strerror(errno));
2078 return (1);
2081 RB_FOREACH_REVERSE(h, history_list, &hl) {
2082 if (h->uri && h->title)
2083 fprintf(f, "%s\n%s\n", h->uri, h->title);
2086 fclose(f);
2088 return (0);
2092 quit(struct tab *t, struct karg *args)
2094 if (save_global_history)
2095 save_global_history_to_disk(t);
2097 gtk_main_quit();
2099 return (1);
2103 open_tabs(struct tab *t, struct karg *a)
2105 char file[PATH_MAX];
2106 FILE *f = NULL;
2107 char *uri = NULL;
2108 int rv = 1;
2109 struct tab *ti, *tt;
2111 if (a == NULL)
2112 goto done;
2114 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2115 if ((f = fopen(file, "r")) == NULL)
2116 goto done;
2118 ti = TAILQ_LAST(&tabs, tab_list);
2120 for (;;) {
2121 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2122 if (feof(f) || ferror(f))
2123 break;
2125 /* retrieve session name */
2126 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2127 strlcpy(named_session,
2128 &uri[strlen(XT_SAVE_SESSION_ID)],
2129 sizeof named_session);
2130 continue;
2133 if (uri && strlen(uri))
2134 create_new_tab(uri, NULL, 1, -1);
2136 free(uri);
2137 uri = NULL;
2140 /* close open tabs */
2141 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2142 for (;;) {
2143 tt = TAILQ_FIRST(&tabs);
2144 if (tt != ti) {
2145 delete_tab(tt);
2146 continue;
2148 delete_tab(tt);
2149 break;
2153 rv = 0;
2154 done:
2155 if (f)
2156 fclose(f);
2158 return (rv);
2162 restore_saved_tabs(void)
2164 char file[PATH_MAX];
2165 int unlink_file = 0;
2166 struct stat sb;
2167 struct karg a;
2168 int rv = 0;
2170 snprintf(file, sizeof file, "%s/%s",
2171 sessions_dir, XT_RESTART_TABS_FILE);
2172 if (stat(file, &sb) == -1)
2173 a.s = XT_SAVED_TABS_FILE;
2174 else {
2175 unlink_file = 1;
2176 a.s = XT_RESTART_TABS_FILE;
2179 a.i = XT_SES_DONOTHING;
2180 rv = open_tabs(NULL, &a);
2182 if (unlink_file)
2183 unlink(file);
2185 return (rv);
2189 save_tabs(struct tab *t, struct karg *a)
2191 char file[PATH_MAX];
2192 FILE *f;
2193 struct tab *ti;
2194 const gchar *uri;
2195 int len = 0, i;
2196 const gchar **arr = NULL;
2198 if (a == NULL)
2199 return (1);
2200 if (a->s == NULL)
2201 snprintf(file, sizeof file, "%s/%s",
2202 sessions_dir, named_session);
2203 else
2204 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2206 if ((f = fopen(file, "w")) == NULL) {
2207 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2208 return (1);
2211 /* save session name */
2212 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2214 /* save tabs, in the order they are arranged in the notebook */
2215 TAILQ_FOREACH(ti, &tabs, entry)
2216 len++;
2218 arr = g_malloc0(len * sizeof(gchar *));
2220 TAILQ_FOREACH(ti, &tabs, entry) {
2221 if ((uri = get_uri(ti->wv)) != NULL)
2222 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2225 for (i = 0; i < len; i++)
2226 if (arr[i])
2227 fprintf(f, "%s\n", arr[i]);
2229 g_free(arr);
2230 /* try and make sure this gets to disk NOW. XXX Backup first? */
2231 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2232 show_oops(t, "May not have managed to save session: %s",
2233 strerror(errno));
2236 fclose(f);
2238 return (0);
2242 save_tabs_and_quit(struct tab *t, struct karg *args)
2244 struct karg a;
2246 a.s = NULL;
2247 save_tabs(t, &a);
2248 quit(t, NULL);
2250 return (1);
2254 yank_uri(struct tab *t, struct karg *args)
2256 const gchar *uri;
2257 GtkClipboard *clipboard;
2259 if ((uri = get_uri(t->wv)) == NULL)
2260 return (1);
2262 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2263 gtk_clipboard_set_text(clipboard, uri, -1);
2265 return (0);
2269 paste_uri(struct tab *t, struct karg *args)
2271 GtkClipboard *clipboard;
2272 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2273 gint len;
2274 gchar *p = NULL, *uri;
2276 /* try primary clipboard first */
2277 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2278 p = gtk_clipboard_wait_for_text(clipboard);
2280 /* if it failed get whatever text is in cut_buffer0 */
2281 if (p == NULL)
2282 if (gdk_property_get(gdk_get_default_root_window(),
2283 atom,
2284 gdk_atom_intern("STRING", FALSE),
2286 65536 /* picked out of my butt */,
2287 FALSE,
2288 NULL,
2289 NULL,
2290 &len,
2291 (guchar **)&p)) {
2292 /* yes sir, we need to NUL the string */
2293 p[len] = '\0';
2296 if (p) {
2297 uri = p;
2298 while(*uri && isspace(*uri))
2299 uri++;
2300 if (strlen(uri) == 0) {
2301 show_oops(t, "empty paste buffer");
2302 goto done;
2304 if (valid_url_type(uri)) {
2305 /* we can be clever and paste this in search box */
2306 show_oops(t, "not a valid URL");
2307 goto done;
2310 if (args->i == XT_PASTE_CURRENT_TAB)
2311 load_uri(t, uri);
2312 else if (args->i == XT_PASTE_NEW_TAB)
2313 create_new_tab(uri, NULL, 1, -1);
2316 done:
2317 if (p)
2318 g_free(p);
2320 return (0);
2323 char *
2324 find_domain(const gchar *s, int add_dot)
2326 int i;
2327 char *r = NULL, *ss = NULL;
2329 if (s == NULL)
2330 return (NULL);
2332 if (!strncmp(s, "http://", strlen("http://")))
2333 s = &s[strlen("http://")];
2334 else if (!strncmp(s, "https://", strlen("https://")))
2335 s = &s[strlen("https://")];
2337 if (strlen(s) < 2)
2338 return (NULL);
2340 ss = g_strdup(s);
2341 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2342 /* chop string at first slash */
2343 if (ss[i] == '/' || ss[i] == '\0') {
2344 ss[i] = '\0';
2345 if (add_dot)
2346 r = g_strdup_printf(".%s", ss);
2347 else
2348 r = g_strdup(ss);
2349 break;
2351 g_free(ss);
2353 return (r);
2357 toggle_cwl(struct tab *t, struct karg *args)
2359 struct domain *d;
2360 const gchar *uri;
2361 char *dom = NULL, *dom_toggle = NULL;
2362 int es;
2364 if (args == NULL)
2365 return (1);
2367 uri = get_uri(t->wv);
2368 dom = find_domain(uri, 1);
2369 d = wl_find(dom, &c_wl);
2371 if (d == NULL)
2372 es = 0;
2373 else
2374 es = 1;
2376 if (args->i & XT_WL_TOGGLE)
2377 es = !es;
2378 else if ((args->i & XT_WL_ENABLE) && es != 1)
2379 es = 1;
2380 else if ((args->i & XT_WL_DISABLE) && es != 0)
2381 es = 0;
2383 if (args->i & XT_WL_TOPLEVEL)
2384 dom_toggle = get_toplevel_domain(dom);
2385 else
2386 dom_toggle = dom;
2388 if (es)
2389 /* enable cookies for domain */
2390 wl_add(dom_toggle, &c_wl, 0);
2391 else
2392 /* disable cookies for domain */
2393 RB_REMOVE(domain_list, &c_wl, d);
2395 if (args->i & XT_WL_RELOAD)
2396 webkit_web_view_reload(t->wv);
2398 g_free(dom);
2399 return (0);
2403 toggle_js(struct tab *t, struct karg *args)
2405 int es;
2406 const gchar *uri;
2407 struct domain *d;
2408 char *dom = NULL, *dom_toggle = NULL;
2410 if (args == NULL)
2411 return (1);
2413 g_object_get(G_OBJECT(t->settings),
2414 "enable-scripts", &es, (char *)NULL);
2415 if (args->i & XT_WL_TOGGLE)
2416 es = !es;
2417 else if ((args->i & XT_WL_ENABLE) && es != 1)
2418 es = 1;
2419 else if ((args->i & XT_WL_DISABLE) && es != 0)
2420 es = 0;
2421 else
2422 return (1);
2424 uri = get_uri(t->wv);
2425 dom = find_domain(uri, 1);
2427 if (uri == NULL || dom == NULL) {
2428 show_oops(t, "Can't toggle domain in JavaScript white list");
2429 goto done;
2432 if (args->i & XT_WL_TOPLEVEL)
2433 dom_toggle = get_toplevel_domain(dom);
2434 else
2435 dom_toggle = dom;
2437 if (es) {
2438 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2439 wl_add(dom_toggle, &js_wl, 0 /* session */);
2440 } else {
2441 d = wl_find(dom_toggle, &js_wl);
2442 if (d)
2443 RB_REMOVE(domain_list, &js_wl, d);
2444 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2446 g_object_set(G_OBJECT(t->settings),
2447 "enable-scripts", es, (char *)NULL);
2448 g_object_set(G_OBJECT(t->settings),
2449 "javascript-can-open-windows-automatically", es, (char *)NULL);
2450 webkit_web_view_set_settings(t->wv, t->settings);
2452 if (args->i & XT_WL_RELOAD)
2453 webkit_web_view_reload(t->wv);
2454 done:
2455 if (dom)
2456 g_free(dom);
2457 return (0);
2460 void
2461 js_toggle_cb(GtkWidget *w, struct tab *t)
2463 struct karg a;
2465 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2466 toggle_cwl(t, &a);
2468 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2469 toggle_js(t, &a);
2473 toggle_src(struct tab *t, struct karg *args)
2475 gboolean mode;
2477 if (t == NULL)
2478 return (0);
2480 mode = webkit_web_view_get_view_source_mode(t->wv);
2481 webkit_web_view_set_view_source_mode(t->wv, !mode);
2482 webkit_web_view_reload(t->wv);
2484 return (0);
2487 void
2488 focus_webview(struct tab *t)
2490 if (t == NULL)
2491 return;
2493 /* only grab focus if we are visible */
2494 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2495 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2499 focus(struct tab *t, struct karg *args)
2501 if (t == NULL || args == NULL)
2502 return (1);
2504 if (show_url == 0)
2505 return (0);
2507 if (args->i == XT_FOCUS_URI)
2508 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2509 else if (args->i == XT_FOCUS_SEARCH)
2510 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2512 return (0);
2516 stats(struct tab *t, struct karg *args)
2518 char *page, *body, *s, line[64 * 1024];
2519 uint64_t line_count = 0;
2520 FILE *r_cookie_f;
2522 if (t == NULL)
2523 show_oops_s("stats invalid parameters");
2525 line[0] = '\0';
2526 if (save_rejected_cookies) {
2527 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2528 for (;;) {
2529 s = fgets(line, sizeof line, r_cookie_f);
2530 if (s == NULL || feof(r_cookie_f) ||
2531 ferror(r_cookie_f))
2532 break;
2533 line_count++;
2535 fclose(r_cookie_f);
2536 snprintf(line, sizeof line,
2537 "<br/>Cookies blocked(*) total: %llu", line_count);
2538 } else
2539 show_oops(t, "Can't open blocked cookies file: %s",
2540 strerror(errno));
2543 body = g_strdup_printf(
2544 "Cookies blocked(*) this session: %llu"
2545 "%s"
2546 "<p><small><b>*</b> results vary based on settings</small></p>",
2547 blocked_cookies,
2548 line);
2550 page = get_html_page("Statistics", body, "", 0);
2551 g_free(body);
2553 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2554 g_free(page);
2556 return (0);
2560 marco(struct tab *t, struct karg *args)
2562 char *page, line[64 * 1024];
2563 int len;
2565 if (t == NULL)
2566 show_oops_s("marco invalid parameters");
2568 line[0] = '\0';
2569 snprintf(line, sizeof line, "%s", marco_message(&len));
2571 page = get_html_page("Marco Sez...", line, "", 0);
2573 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2574 g_free(page);
2576 return (0);
2580 blank(struct tab *t, struct karg *args)
2582 if (t == NULL)
2583 show_oops_s("blank invalid parameters");
2585 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2587 return (0);
2590 about(struct tab *t, struct karg *args)
2592 char *page, *body;
2594 if (t == NULL)
2595 show_oops_s("about invalid parameters");
2597 body = g_strdup_printf("<b>Version: %s</b><p>"
2598 "Authors:"
2599 "<ul>"
2600 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2601 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2602 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2603 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2604 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2605 "</ul>"
2606 "Copyrights and licenses can be found on the XXXterm "
2607 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2608 version
2611 page = get_html_page("About", body, "", 0);
2612 g_free(body);
2614 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2615 g_free(page);
2617 return (0);
2621 help(struct tab *t, struct karg *args)
2623 char *page, *head, *body;
2625 if (t == NULL)
2626 show_oops_s("help invalid parameters");
2628 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2629 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2630 "</head>\n";
2631 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2632 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2633 "cgi-bin/man-cgi?xxxterm</a>";
2635 page = get_html_page("XXXterm", body, head, FALSE);
2637 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2638 g_free(page);
2640 return (0);
2644 * update all favorite tabs apart from one. Pass NULL if
2645 * you want to update all.
2647 void
2648 update_favorite_tabs(struct tab *apart_from)
2650 struct tab *t;
2651 if (!updating_fl_tabs) {
2652 updating_fl_tabs = 1; /* stop infinite recursion */
2653 TAILQ_FOREACH(t, &tabs, entry)
2654 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2655 && (t != apart_from))
2656 xtp_page_fl(t, NULL);
2657 updating_fl_tabs = 0;
2661 /* show a list of favorites (bookmarks) */
2663 xtp_page_fl(struct tab *t, struct karg *args)
2665 char file[PATH_MAX];
2666 FILE *f;
2667 char *uri = NULL, *title = NULL;
2668 size_t len, lineno = 0;
2669 int i, failed = 0;
2670 char *body, *tmp, *page = NULL;
2671 const char delim[3] = {'\\', '\\', '\0'};
2673 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2675 if (t == NULL)
2676 warn("%s: bad param", __func__);
2678 /* mark tab as favorite list */
2679 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2681 /* new session key */
2682 if (!updating_fl_tabs)
2683 generate_xtp_session_key(&fl_session_key);
2685 /* open favorites */
2686 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2687 if ((f = fopen(file, "r")) == NULL) {
2688 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2689 return (1);
2692 /* body */
2693 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2694 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2695 "<th style='width: 40px'>Rm</th></tr>\n");
2697 for (i = 1;;) {
2698 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2699 if (feof(f) || ferror(f))
2700 break;
2701 if (len == 0) {
2702 free(title);
2703 title = NULL;
2704 continue;
2707 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2708 if (feof(f) || ferror(f)) {
2709 show_oops(t, "favorites file corrupt");
2710 failed = 1;
2711 break;
2714 tmp = body;
2715 body = g_strdup_printf("%s<tr>"
2716 "<td>%d</td>"
2717 "<td><a href='%s'>%s</a></td>"
2718 "<td style='text-align: center'>"
2719 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2720 "</tr>\n",
2721 body, i, uri, title,
2722 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2724 g_free(tmp);
2726 free(uri);
2727 uri = NULL;
2728 free(title);
2729 title = NULL;
2730 i++;
2732 fclose(f);
2734 /* if none, say so */
2735 if (i == 1) {
2736 tmp = body;
2737 body = g_strdup_printf("%s<tr>"
2738 "<td colspan='3' style='text-align: center'>"
2739 "No favorites - To add one use the 'favadd' command."
2740 "</td></tr>", body);
2741 g_free(tmp);
2744 tmp = body;
2745 body = g_strdup_printf("%s</table>", body);
2746 g_free(tmp);
2748 if (uri)
2749 free(uri);
2750 if (title)
2751 free(title);
2753 /* render */
2754 if (!failed) {
2755 page = get_html_page("Favorites", body, "", 1);
2756 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2757 g_free(page);
2760 update_favorite_tabs(t);
2762 if (body)
2763 g_free(body);
2765 return (failed);
2768 void
2769 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2770 size_t cert_count, char *title)
2772 gnutls_datum_t cinfo;
2773 char *tmp, *body;
2774 int i;
2776 body = g_strdup("");
2778 for (i = 0; i < cert_count; i++) {
2779 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2780 &cinfo))
2781 return;
2783 tmp = body;
2784 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2785 body, i, cinfo.data);
2786 gnutls_free(cinfo.data);
2787 g_free(tmp);
2790 tmp = get_html_page(title, body, "", 0);
2791 g_free(body);
2793 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2794 g_free(tmp);
2798 ca_cmd(struct tab *t, struct karg *args)
2800 FILE *f = NULL;
2801 int rv = 1, certs = 0, certs_read;
2802 struct stat sb;
2803 gnutls_datum dt;
2804 gnutls_x509_crt_t *c = NULL;
2805 char *certs_buf = NULL, *s;
2807 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2808 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2809 return (1);
2812 if (fstat(fileno(f), &sb) == -1) {
2813 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2814 goto done;
2817 certs_buf = g_malloc(sb.st_size + 1);
2818 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2819 show_oops(t, "Can't read CA file: %s", strerror(errno));
2820 goto done;
2822 certs_buf[sb.st_size] = '\0';
2824 s = certs_buf;
2825 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2826 certs++;
2827 s += strlen("BEGIN CERTIFICATE");
2830 bzero(&dt, sizeof dt);
2831 dt.data = certs_buf;
2832 dt.size = sb.st_size;
2833 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2834 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2835 GNUTLS_X509_FMT_PEM, 0);
2836 if (certs_read <= 0) {
2837 show_oops(t, "No cert(s) available");
2838 goto done;
2840 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2841 done:
2842 if (c)
2843 g_free(c);
2844 if (certs_buf)
2845 g_free(certs_buf);
2846 if (f)
2847 fclose(f);
2849 return (rv);
2853 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2855 SoupURI *su = NULL;
2856 struct addrinfo hints, *res = NULL, *ai;
2857 int s = -1, on;
2858 char port[8];
2860 if (uri && !g_str_has_prefix(uri, "https://"))
2861 goto done;
2863 su = soup_uri_new(uri);
2864 if (su == NULL)
2865 goto done;
2866 if (!SOUP_URI_VALID_FOR_HTTP(su))
2867 goto done;
2869 snprintf(port, sizeof port, "%d", su->port);
2870 bzero(&hints, sizeof(struct addrinfo));
2871 hints.ai_flags = AI_CANONNAME;
2872 hints.ai_family = AF_UNSPEC;
2873 hints.ai_socktype = SOCK_STREAM;
2875 if (getaddrinfo(su->host, port, &hints, &res))
2876 goto done;
2878 for (ai = res; ai; ai = ai->ai_next) {
2879 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2880 continue;
2882 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2883 if (s < 0)
2884 goto done;
2885 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2886 sizeof(on)) == -1)
2887 goto done;
2889 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2890 goto done;
2893 if (domain)
2894 strlcpy(domain, su->host, domain_sz);
2895 done:
2896 if (su)
2897 soup_uri_free(su);
2898 if (res)
2899 freeaddrinfo(res);
2901 return (s);
2905 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2907 if (gsession)
2908 gnutls_deinit(gsession);
2909 if (xcred)
2910 gnutls_certificate_free_credentials(xcred);
2912 return (0);
2916 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2917 gnutls_certificate_credentials_t *xc)
2919 gnutls_certificate_credentials_t xcred;
2920 gnutls_session_t gsession;
2921 int rv = 1;
2923 if (gs == NULL || xc == NULL)
2924 goto done;
2926 *gs = NULL;
2927 *xc = NULL;
2929 gnutls_certificate_allocate_credentials(&xcred);
2930 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2931 GNUTLS_X509_FMT_PEM);
2932 gnutls_init(&gsession, GNUTLS_CLIENT);
2933 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2934 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2935 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2936 if ((rv = gnutls_handshake(gsession)) < 0) {
2937 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2939 gnutls_error_is_fatal(rv),
2940 gnutls_strerror_name(rv));
2941 stop_tls(gsession, xcred);
2942 goto done;
2945 gnutls_credentials_type_t cred;
2946 cred = gnutls_auth_get_type(gsession);
2947 if (cred != GNUTLS_CRD_CERTIFICATE) {
2948 stop_tls(gsession, xcred);
2949 goto done;
2952 *gs = gsession;
2953 *xc = xcred;
2954 rv = 0;
2955 done:
2956 return (rv);
2960 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2961 size_t *cert_count)
2963 unsigned int len;
2964 const gnutls_datum_t *cl;
2965 gnutls_x509_crt_t *all_certs;
2966 int i, rv = 1;
2968 if (certs == NULL || cert_count == NULL)
2969 goto done;
2970 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2971 goto done;
2972 cl = gnutls_certificate_get_peers(gsession, &len);
2973 if (len == 0)
2974 goto done;
2976 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2977 for (i = 0; i < len; i++) {
2978 gnutls_x509_crt_init(&all_certs[i]);
2979 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2980 GNUTLS_X509_FMT_PEM < 0)) {
2981 g_free(all_certs);
2982 goto done;
2986 *certs = all_certs;
2987 *cert_count = len;
2988 rv = 0;
2989 done:
2990 return (rv);
2993 void
2994 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2996 int i;
2998 for (i = 0; i < cert_count; i++)
2999 gnutls_x509_crt_deinit(certs[i]);
3000 g_free(certs);
3003 void
3004 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3005 size_t cert_count, char *domain)
3007 size_t cert_buf_sz;
3008 char cert_buf[64 * 1024], file[PATH_MAX];
3009 int i;
3010 FILE *f;
3011 GdkColor color;
3013 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3014 return;
3016 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3017 if ((f = fopen(file, "w")) == NULL) {
3018 show_oops(t, "Can't create cert file %s %s",
3019 file, strerror(errno));
3020 return;
3023 for (i = 0; i < cert_count; i++) {
3024 cert_buf_sz = sizeof cert_buf;
3025 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3026 cert_buf, &cert_buf_sz)) {
3027 show_oops(t, "gnutls_x509_crt_export failed");
3028 goto done;
3030 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3031 show_oops(t, "Can't write certs: %s", strerror(errno));
3032 goto done;
3036 /* not the best spot but oh well */
3037 gdk_color_parse("lightblue", &color);
3038 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3039 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3040 gdk_color_parse(XT_COLOR_BLACK, &color);
3041 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3042 done:
3043 fclose(f);
3047 load_compare_cert(struct tab *t, struct karg *args)
3049 const gchar *uri;
3050 char domain[8182], file[PATH_MAX];
3051 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3052 int s = -1, rv = 1, i;
3053 size_t cert_count;
3054 FILE *f = NULL;
3055 size_t cert_buf_sz;
3056 gnutls_session_t gsession;
3057 gnutls_x509_crt_t *certs;
3058 gnutls_certificate_credentials_t xcred;
3060 if (t == NULL)
3061 return (1);
3063 if ((uri = get_uri(t->wv)) == NULL)
3064 return (1);
3066 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3067 return (1);
3069 /* go ssl/tls */
3070 if (start_tls(t, s, &gsession, &xcred)) {
3071 show_oops(t, "Start TLS failed");
3072 goto done;
3075 /* get certs */
3076 if (get_connection_certs(gsession, &certs, &cert_count)) {
3077 show_oops(t, "Can't get connection certificates");
3078 goto done;
3081 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3082 if ((f = fopen(file, "r")) == NULL)
3083 goto freeit;
3085 for (i = 0; i < cert_count; i++) {
3086 cert_buf_sz = sizeof cert_buf;
3087 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3088 cert_buf, &cert_buf_sz)) {
3089 goto freeit;
3091 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3092 rv = -1; /* critical */
3093 goto freeit;
3095 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3096 rv = -1; /* critical */
3097 goto freeit;
3101 rv = 0;
3102 freeit:
3103 if (f)
3104 fclose(f);
3105 free_connection_certs(certs, cert_count);
3106 done:
3107 /* we close the socket first for speed */
3108 if (s != -1)
3109 close(s);
3110 stop_tls(gsession, xcred);
3112 return (rv);
3116 cert_cmd(struct tab *t, struct karg *args)
3118 const gchar *uri;
3119 char domain[8182];
3120 int s = -1;
3121 size_t cert_count;
3122 gnutls_session_t gsession;
3123 gnutls_x509_crt_t *certs;
3124 gnutls_certificate_credentials_t xcred;
3126 if (t == NULL)
3127 return (1);
3129 if (ssl_ca_file == NULL) {
3130 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3131 return (1);
3134 if ((uri = get_uri(t->wv)) == NULL) {
3135 show_oops(t, "Invalid URI");
3136 return (1);
3139 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3140 show_oops(t, "Invalid certificate URI: %s", uri);
3141 return (1);
3144 /* go ssl/tls */
3145 if (start_tls(t, s, &gsession, &xcred)) {
3146 show_oops(t, "Start TLS failed");
3147 goto done;
3150 /* get certs */
3151 if (get_connection_certs(gsession, &certs, &cert_count)) {
3152 show_oops(t, "get_connection_certs failed");
3153 goto done;
3156 if (args->i & XT_SHOW)
3157 show_certs(t, certs, cert_count, "Certificate Chain");
3158 else if (args->i & XT_SAVE)
3159 save_certs(t, certs, cert_count, domain);
3161 free_connection_certs(certs, cert_count);
3162 done:
3163 /* we close the socket first for speed */
3164 if (s != -1)
3165 close(s);
3166 stop_tls(gsession, xcred);
3168 return (0);
3172 remove_cookie(int index)
3174 int i, rv = 1;
3175 GSList *cf;
3176 SoupCookie *c;
3178 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3180 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3182 for (i = 1; cf; cf = cf->next, i++) {
3183 if (i != index)
3184 continue;
3185 c = cf->data;
3186 print_cookie("remove cookie", c);
3187 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3188 rv = 0;
3189 break;
3192 soup_cookies_free(cf);
3194 return (rv);
3198 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3200 struct domain *d;
3201 char *tmp, *body;
3203 body = g_strdup("");
3205 /* p list */
3206 if (args->i & XT_WL_PERSISTENT) {
3207 tmp = body;
3208 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3209 g_free(tmp);
3210 RB_FOREACH(d, domain_list, wl) {
3211 if (d->handy == 0)
3212 continue;
3213 tmp = body;
3214 body = g_strdup_printf("%s%s<br/>", body, d->d);
3215 g_free(tmp);
3219 /* s list */
3220 if (args->i & XT_WL_SESSION) {
3221 tmp = body;
3222 body = g_strdup_printf("%s<h2>Session</h2>", body);
3223 g_free(tmp);
3224 RB_FOREACH(d, domain_list, wl) {
3225 if (d->handy == 1)
3226 continue;
3227 tmp = body;
3228 body = g_strdup_printf("%s%s<br/>", body, d->d);
3229 g_free(tmp);
3233 tmp = get_html_page(title, body, "", 0);
3234 g_free(body);
3235 if (wl == &js_wl)
3236 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3237 else
3238 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3239 g_free(tmp);
3240 return (0);
3244 wl_save(struct tab *t, struct karg *args, int js)
3246 char file[PATH_MAX];
3247 FILE *f;
3248 char *line = NULL, *lt = NULL;
3249 size_t linelen;
3250 const gchar *uri;
3251 char *dom = NULL, *dom_save = NULL;
3252 struct karg a;
3253 struct domain *d;
3254 GSList *cf;
3255 SoupCookie *ci, *c;
3257 if (t == NULL || args == NULL)
3258 return (1);
3260 if (runtime_settings[0] == '\0')
3261 return (1);
3263 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3264 if ((f = fopen(file, "r+")) == NULL)
3265 return (1);
3267 uri = get_uri(t->wv);
3268 dom = find_domain(uri, 1);
3269 if (uri == NULL || dom == NULL) {
3270 show_oops(t, "Can't add domain to %s white list",
3271 js ? "JavaScript" : "cookie");
3272 goto done;
3275 if (args->i & XT_WL_TOPLEVEL) {
3276 /* save domain */
3277 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3278 show_oops(t, "invalid domain: %s", dom);
3279 goto done;
3281 } else if (args->i & XT_WL_FQDN) {
3282 /* save fqdn */
3283 dom_save = dom;
3284 } else
3285 goto done;
3287 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3289 while (!feof(f)) {
3290 line = fparseln(f, &linelen, NULL, NULL, 0);
3291 if (line == NULL)
3292 continue;
3293 if (!strcmp(line, lt))
3294 goto done;
3295 free(line);
3296 line = NULL;
3299 fprintf(f, "%s\n", lt);
3301 a.i = XT_WL_ENABLE;
3302 a.i |= args->i;
3303 if (js) {
3304 d = wl_find(dom_save, &js_wl);
3305 if (!d) {
3306 settings_add("js_wl", dom_save);
3307 d = wl_find(dom_save, &js_wl);
3309 toggle_js(t, &a);
3310 } else {
3311 d = wl_find(dom_save, &c_wl);
3312 if (!d) {
3313 settings_add("cookie_wl", dom_save);
3314 d = wl_find(dom_save, &c_wl);
3316 toggle_cwl(t, &a);
3318 /* find and add to persistent jar */
3319 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3320 for (;cf; cf = cf->next) {
3321 ci = cf->data;
3322 if (!strcmp(dom_save, ci->domain) ||
3323 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3324 c = soup_cookie_copy(ci);
3325 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3328 soup_cookies_free(cf);
3330 if (d)
3331 d->handy = 1;
3333 done:
3334 if (line)
3335 free(line);
3336 if (dom)
3337 g_free(dom);
3338 if (lt)
3339 g_free(lt);
3340 fclose(f);
3342 return (0);
3346 js_show_wl(struct tab *t, struct karg *args)
3348 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3349 wl_show(t, args, "JavaScript White List", &js_wl);
3351 return (0);
3355 cookie_show_wl(struct tab *t, struct karg *args)
3357 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3358 wl_show(t, args, "Cookie White List", &c_wl);
3360 return (0);
3364 cookie_cmd(struct tab *t, struct karg *args)
3366 if (args->i & XT_SHOW)
3367 wl_show(t, args, "Cookie White List", &c_wl);
3368 else if (args->i & XT_WL_TOGGLE) {
3369 args->i |= XT_WL_RELOAD;
3370 toggle_cwl(t, args);
3371 } else if (args->i & XT_SAVE) {
3372 args->i |= XT_WL_RELOAD;
3373 wl_save(t, args, 0);
3374 } else if (args->i & XT_DELETE)
3375 show_oops(t, "'cookie delete' currently unimplemented");
3377 return (0);
3381 js_cmd(struct tab *t, struct karg *args)
3383 if (args->i & XT_SHOW)
3384 wl_show(t, args, "JavaScript White List", &js_wl);
3385 else if (args->i & XT_SAVE) {
3386 args->i |= XT_WL_RELOAD;
3387 wl_save(t, args, 1);
3388 } else if (args->i & XT_WL_TOGGLE) {
3389 args->i |= XT_WL_RELOAD;
3390 toggle_js(t, args);
3391 } else if (args->i & XT_DELETE)
3392 show_oops(t, "'js delete' currently unimplemented");
3394 return (0);
3398 toplevel_cmd(struct tab *t, struct karg *args)
3400 js_toggle_cb(t->js_toggle, t);
3402 return (0);
3406 add_favorite(struct tab *t, struct karg *args)
3408 char file[PATH_MAX];
3409 FILE *f;
3410 char *line = NULL;
3411 size_t urilen, linelen;
3412 const gchar *uri, *title;
3414 if (t == NULL)
3415 return (1);
3417 /* don't allow adding of xtp pages to favorites */
3418 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3419 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3420 return (1);
3423 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3424 if ((f = fopen(file, "r+")) == NULL) {
3425 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3426 return (1);
3429 title = webkit_web_view_get_title(t->wv);
3430 uri = get_uri(t->wv);
3432 if (title == NULL)
3433 title = uri;
3435 if (title == NULL || uri == NULL) {
3436 show_oops(t, "can't add page to favorites");
3437 goto done;
3440 urilen = strlen(uri);
3442 for (;;) {
3443 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3444 if (feof(f) || ferror(f))
3445 break;
3447 if (linelen == urilen && !strcmp(line, uri))
3448 goto done;
3450 free(line);
3451 line = NULL;
3454 fprintf(f, "\n%s\n%s", title, uri);
3455 done:
3456 if (line)
3457 free(line);
3458 fclose(f);
3460 update_favorite_tabs(NULL);
3462 return (0);
3466 navaction(struct tab *t, struct karg *args)
3468 WebKitWebHistoryItem *item;
3470 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3471 t->tab_id, args->i);
3473 if (t->item) {
3474 if (args->i == XT_NAV_BACK)
3475 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3476 else
3477 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3478 if (item == NULL)
3479 return (XT_CB_PASSTHROUGH);
3480 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3481 t->item = NULL;
3482 return (XT_CB_PASSTHROUGH);
3485 switch (args->i) {
3486 case XT_NAV_BACK:
3487 webkit_web_view_go_back(t->wv);
3488 break;
3489 case XT_NAV_FORWARD:
3490 webkit_web_view_go_forward(t->wv);
3491 break;
3492 case XT_NAV_RELOAD:
3493 webkit_web_view_reload(t->wv);
3494 break;
3495 case XT_NAV_RELOAD_CACHE:
3496 webkit_web_view_reload_bypass_cache(t->wv);
3497 break;
3499 return (XT_CB_PASSTHROUGH);
3503 move(struct tab *t, struct karg *args)
3505 GtkAdjustment *adjust;
3506 double pi, si, pos, ps, upper, lower, max;
3508 switch (args->i) {
3509 case XT_MOVE_DOWN:
3510 case XT_MOVE_UP:
3511 case XT_MOVE_BOTTOM:
3512 case XT_MOVE_TOP:
3513 case XT_MOVE_PAGEDOWN:
3514 case XT_MOVE_PAGEUP:
3515 case XT_MOVE_HALFDOWN:
3516 case XT_MOVE_HALFUP:
3517 adjust = t->adjust_v;
3518 break;
3519 default:
3520 adjust = t->adjust_h;
3521 break;
3524 pos = gtk_adjustment_get_value(adjust);
3525 ps = gtk_adjustment_get_page_size(adjust);
3526 upper = gtk_adjustment_get_upper(adjust);
3527 lower = gtk_adjustment_get_lower(adjust);
3528 si = gtk_adjustment_get_step_increment(adjust);
3529 pi = gtk_adjustment_get_page_increment(adjust);
3530 max = upper - ps;
3532 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3533 "max %f si %f pi %f\n",
3534 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3535 pos, ps, upper, lower, max, si, pi);
3537 switch (args->i) {
3538 case XT_MOVE_DOWN:
3539 case XT_MOVE_RIGHT:
3540 pos += si;
3541 gtk_adjustment_set_value(adjust, MIN(pos, max));
3542 break;
3543 case XT_MOVE_UP:
3544 case XT_MOVE_LEFT:
3545 pos -= si;
3546 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3547 break;
3548 case XT_MOVE_BOTTOM:
3549 case XT_MOVE_FARRIGHT:
3550 gtk_adjustment_set_value(adjust, max);
3551 break;
3552 case XT_MOVE_TOP:
3553 case XT_MOVE_FARLEFT:
3554 gtk_adjustment_set_value(adjust, lower);
3555 break;
3556 case XT_MOVE_PAGEDOWN:
3557 pos += pi;
3558 gtk_adjustment_set_value(adjust, MIN(pos, max));
3559 break;
3560 case XT_MOVE_PAGEUP:
3561 pos -= pi;
3562 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3563 break;
3564 case XT_MOVE_HALFDOWN:
3565 pos += pi / 2;
3566 gtk_adjustment_set_value(adjust, MIN(pos, max));
3567 break;
3568 case XT_MOVE_HALFUP:
3569 pos -= pi / 2;
3570 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3571 break;
3572 default:
3573 return (XT_CB_PASSTHROUGH);
3576 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3578 return (XT_CB_HANDLED);
3581 void
3582 url_set_visibility(void)
3584 struct tab *t;
3586 TAILQ_FOREACH(t, &tabs, entry) {
3587 if (show_url == 0) {
3588 gtk_widget_hide(t->toolbar);
3589 focus_webview(t);
3590 } else
3591 gtk_widget_show(t->toolbar);
3595 void
3596 notebook_tab_set_visibility(GtkNotebook *notebook)
3598 if (show_tabs == 0)
3599 gtk_notebook_set_show_tabs(notebook, FALSE);
3600 else
3601 gtk_notebook_set_show_tabs(notebook, TRUE);
3604 void
3605 statusbar_set_visibility(void)
3607 struct tab *t;
3609 TAILQ_FOREACH(t, &tabs, entry) {
3610 if (show_statusbar == 0) {
3611 gtk_widget_hide(t->statusbar);
3612 focus_webview(t);
3613 } else
3614 gtk_widget_show(t->statusbar);
3618 void
3619 url_set(struct tab *t, int enable_url_entry)
3621 GdkPixbuf *pixbuf;
3622 int progress;
3624 show_url = enable_url_entry;
3626 if (enable_url_entry) {
3627 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3628 GTK_ENTRY_ICON_PRIMARY, NULL);
3629 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3630 } else {
3631 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3632 GTK_ENTRY_ICON_PRIMARY);
3633 progress =
3634 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3635 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3636 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3637 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3638 progress);
3643 fullscreen(struct tab *t, struct karg *args)
3645 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3647 if (t == NULL)
3648 return (XT_CB_PASSTHROUGH);
3650 if (show_url == 0) {
3651 url_set(t, 1);
3652 show_tabs = 1;
3653 } else {
3654 url_set(t, 0);
3655 show_tabs = 0;
3658 url_set_visibility();
3659 notebook_tab_set_visibility(notebook);
3661 return (XT_CB_HANDLED);
3665 statusaction(struct tab *t, struct karg *args)
3667 int rv = XT_CB_HANDLED;
3669 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3671 if (t == NULL)
3672 return (XT_CB_PASSTHROUGH);
3674 switch (args->i) {
3675 case XT_STATUSBAR_SHOW:
3676 if (show_statusbar == 0) {
3677 show_statusbar = 1;
3678 statusbar_set_visibility();
3680 break;
3681 case XT_STATUSBAR_HIDE:
3682 if (show_statusbar == 1) {
3683 show_statusbar = 0;
3684 statusbar_set_visibility();
3686 break;
3688 return (rv);
3692 urlaction(struct tab *t, struct karg *args)
3694 int rv = XT_CB_HANDLED;
3696 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3698 if (t == NULL)
3699 return (XT_CB_PASSTHROUGH);
3701 switch (args->i) {
3702 case XT_URL_SHOW:
3703 if (show_url == 0) {
3704 url_set(t, 1);
3705 url_set_visibility();
3707 break;
3708 case XT_URL_HIDE:
3709 if (show_url == 1) {
3710 url_set(t, 0);
3711 url_set_visibility();
3713 break;
3715 return (rv);
3719 tabaction(struct tab *t, struct karg *args)
3721 int rv = XT_CB_HANDLED;
3722 char *url = args->s;
3723 struct undo *u;
3724 struct tab *tt;
3726 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3728 if (t == NULL)
3729 return (XT_CB_PASSTHROUGH);
3731 switch (args->i) {
3732 case XT_TAB_NEW:
3733 if (strlen(url) > 0)
3734 create_new_tab(url, NULL, 1, args->p);
3735 else
3736 create_new_tab(NULL, NULL, 1, args->p);
3737 break;
3738 case XT_TAB_DELETE:
3739 if (args->p < 0)
3740 delete_tab(t);
3741 else
3742 TAILQ_FOREACH(tt, &tabs, entry)
3743 if (tt->tab_id == args->p - 1) {
3744 delete_tab(tt);
3745 recalc_tabs();
3746 break;
3748 break;
3749 case XT_TAB_DELQUIT:
3750 if (gtk_notebook_get_n_pages(notebook) > 1)
3751 delete_tab(t);
3752 else
3753 quit(t, args);
3754 break;
3755 case XT_TAB_OPEN:
3756 if (strlen(url) > 0)
3758 else {
3759 rv = XT_CB_PASSTHROUGH;
3760 goto done;
3762 load_uri(t, url);
3763 break;
3764 case XT_TAB_SHOW:
3765 if (show_tabs == 0) {
3766 show_tabs = 1;
3767 notebook_tab_set_visibility(notebook);
3769 break;
3770 case XT_TAB_HIDE:
3771 if (show_tabs == 1) {
3772 show_tabs = 0;
3773 notebook_tab_set_visibility(notebook);
3775 break;
3776 case XT_TAB_UNDO_CLOSE:
3777 if (undo_count == 0) {
3778 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3779 goto done;
3780 } else {
3781 undo_count--;
3782 u = TAILQ_FIRST(&undos);
3783 create_new_tab(u->uri, u, 1, -1);
3785 TAILQ_REMOVE(&undos, u, entry);
3786 g_free(u->uri);
3787 /* u->history is freed in create_new_tab() */
3788 g_free(u);
3790 break;
3791 default:
3792 rv = XT_CB_PASSTHROUGH;
3793 goto done;
3796 done:
3797 if (args->s) {
3798 g_free(args->s);
3799 args->s = NULL;
3802 return (rv);
3806 resizetab(struct tab *t, struct karg *args)
3808 if (t == NULL || args == NULL) {
3809 show_oops_s("resizetab invalid parameters");
3810 return (XT_CB_PASSTHROUGH);
3813 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3814 t->tab_id, args->i);
3816 adjustfont_webkit(t, args->i);
3818 return (XT_CB_HANDLED);
3822 movetab(struct tab *t, struct karg *args)
3824 int n, dest;
3826 if (t == NULL || args == NULL) {
3827 show_oops_s("movetab invalid parameters");
3828 return (XT_CB_PASSTHROUGH);
3831 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3832 t->tab_id, args->i);
3834 if (args->i >= XT_TAB_INVALID)
3835 return (XT_CB_PASSTHROUGH);
3837 if (TAILQ_EMPTY(&tabs))
3838 return (XT_CB_PASSTHROUGH);
3840 n = gtk_notebook_get_n_pages(notebook);
3841 dest = gtk_notebook_get_current_page(notebook);
3843 switch (args->i) {
3844 case XT_TAB_NEXT:
3845 if (args->p < 0)
3846 dest = dest == n - 1 ? 0 : dest + 1;
3847 else
3848 dest = args->p - 1;
3850 break;
3851 case XT_TAB_PREV:
3852 if (args->p < 0)
3853 dest -= 1;
3854 else
3855 dest -= args->p % n;
3857 if (dest < 0)
3858 dest += n;
3860 break;
3861 case XT_TAB_FIRST:
3862 dest = 0;
3863 break;
3864 case XT_TAB_LAST:
3865 dest = n - 1;
3866 break;
3867 default:
3868 return (XT_CB_PASSTHROUGH);
3871 if (dest < 0 || dest >= n)
3872 return (XT_CB_PASSTHROUGH);
3873 if (t->tab_id == dest) {
3874 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3875 return (XT_CB_HANDLED);
3878 gtk_notebook_set_current_page(notebook, dest);
3880 return (XT_CB_HANDLED);
3883 int cmd_prefix = 0;
3886 command(struct tab *t, struct karg *args)
3888 char *s = NULL, *ss = NULL;
3889 GdkColor color;
3890 const gchar *uri;
3892 if (t == NULL || args == NULL) {
3893 show_oops_s("command invalid parameters");
3894 return (XT_CB_PASSTHROUGH);
3897 switch (args->i) {
3898 case '/':
3899 s = "/";
3900 break;
3901 case '?':
3902 s = "?";
3903 break;
3904 case ':':
3905 if (cmd_prefix == 0)
3906 s = ":";
3907 else {
3908 ss = g_strdup_printf(":%d", cmd_prefix);
3909 s = ss;
3910 cmd_prefix = 0;
3912 break;
3913 case XT_CMD_OPEN:
3914 s = ":open ";
3915 break;
3916 case XT_CMD_TABNEW:
3917 s = ":tabnew ";
3918 break;
3919 case XT_CMD_OPEN_CURRENT:
3920 s = ":open ";
3921 /* FALL THROUGH */
3922 case XT_CMD_TABNEW_CURRENT:
3923 if (!s) /* FALL THROUGH? */
3924 s = ":tabnew ";
3925 if ((uri = get_uri(t->wv)) != NULL) {
3926 ss = g_strdup_printf("%s%s", s, uri);
3927 s = ss;
3929 break;
3930 default:
3931 show_oops(t, "command: invalid opcode %d", args->i);
3932 return (XT_CB_PASSTHROUGH);
3935 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3937 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3938 gdk_color_parse(XT_COLOR_WHITE, &color);
3939 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3940 show_cmd(t);
3941 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3942 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3944 if (ss)
3945 g_free(ss);
3947 return (XT_CB_HANDLED);
3951 * Return a new string with a download row (in html)
3952 * appended. Old string is freed.
3954 char *
3955 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3958 WebKitDownloadStatus stat;
3959 char *status_html = NULL, *cmd_html = NULL, *new_html;
3960 gdouble progress;
3961 char cur_sz[FMT_SCALED_STRSIZE];
3962 char tot_sz[FMT_SCALED_STRSIZE];
3963 char *xtp_prefix;
3965 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3967 /* All actions wil take this form:
3968 * xxxt://class/seskey
3970 xtp_prefix = g_strdup_printf("%s%d/%s/",
3971 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3973 stat = webkit_download_get_status(dl->download);
3975 switch (stat) {
3976 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3977 status_html = g_strdup_printf("Finished");
3978 cmd_html = g_strdup_printf(
3979 "<a href='%s%d/%d'>Remove</a>",
3980 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3981 break;
3982 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3983 /* gather size info */
3984 progress = 100 * webkit_download_get_progress(dl->download);
3986 fmt_scaled(
3987 webkit_download_get_current_size(dl->download), cur_sz);
3988 fmt_scaled(
3989 webkit_download_get_total_size(dl->download), tot_sz);
3991 status_html = g_strdup_printf(
3992 "<div style='width: 100%%' align='center'>"
3993 "<div class='progress-outer'>"
3994 "<div class='progress-inner' style='width: %.2f%%'>"
3995 "</div></div></div>"
3996 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3997 progress, cur_sz, tot_sz, progress);
3999 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4000 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4002 break;
4003 /* LLL */
4004 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4005 status_html = g_strdup_printf("Cancelled");
4006 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4007 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4008 break;
4009 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4010 status_html = g_strdup_printf("Error!");
4011 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4012 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4013 break;
4014 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4015 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4016 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4017 status_html = g_strdup_printf("Starting");
4018 break;
4019 default:
4020 show_oops(t, "%s: unknown download status", __func__);
4023 new_html = g_strdup_printf(
4024 "%s\n<tr><td>%s</td><td>%s</td>"
4025 "<td style='text-align:center'>%s</td></tr>\n",
4026 html, basename(webkit_download_get_destination_uri(dl->download)),
4027 status_html, cmd_html);
4028 g_free(html);
4030 if (status_html)
4031 g_free(status_html);
4033 if (cmd_html)
4034 g_free(cmd_html);
4036 g_free(xtp_prefix);
4038 return new_html;
4042 * update all download tabs apart from one. Pass NULL if
4043 * you want to update all.
4045 void
4046 update_download_tabs(struct tab *apart_from)
4048 struct tab *t;
4049 if (!updating_dl_tabs) {
4050 updating_dl_tabs = 1; /* stop infinite recursion */
4051 TAILQ_FOREACH(t, &tabs, entry)
4052 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4053 && (t != apart_from))
4054 xtp_page_dl(t, NULL);
4055 updating_dl_tabs = 0;
4060 * update all cookie tabs apart from one. Pass NULL if
4061 * you want to update all.
4063 void
4064 update_cookie_tabs(struct tab *apart_from)
4066 struct tab *t;
4067 if (!updating_cl_tabs) {
4068 updating_cl_tabs = 1; /* stop infinite recursion */
4069 TAILQ_FOREACH(t, &tabs, entry)
4070 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4071 && (t != apart_from))
4072 xtp_page_cl(t, NULL);
4073 updating_cl_tabs = 0;
4078 * update all history tabs apart from one. Pass NULL if
4079 * you want to update all.
4081 void
4082 update_history_tabs(struct tab *apart_from)
4084 struct tab *t;
4086 if (!updating_hl_tabs) {
4087 updating_hl_tabs = 1; /* stop infinite recursion */
4088 TAILQ_FOREACH(t, &tabs, entry)
4089 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4090 && (t != apart_from))
4091 xtp_page_hl(t, NULL);
4092 updating_hl_tabs = 0;
4096 /* cookie management XTP page */
4098 xtp_page_cl(struct tab *t, struct karg *args)
4100 char *body, *page, *tmp;
4101 int i = 1; /* all ids start 1 */
4102 GSList *sc, *pc, *pc_start;
4103 SoupCookie *c;
4104 char *type, *table_headers;
4105 char *last_domain = strdup("");
4107 DNPRINTF(XT_D_CMD, "%s", __func__);
4109 if (t == NULL) {
4110 show_oops_s("%s invalid parameters", __func__);
4111 return (1);
4113 /* mark this tab as cookie jar */
4114 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4116 /* Generate a new session key */
4117 if (!updating_cl_tabs)
4118 generate_xtp_session_key(&cl_session_key);
4120 /* table headers */
4121 table_headers = g_strdup_printf("<table><tr>"
4122 "<th>Type</th>"
4123 "<th>Name</th>"
4124 "<th style='width:200px'>Value</th>"
4125 "<th>Path</th>"
4126 "<th>Expires</th>"
4127 "<th>Secure</th>"
4128 "<th>HTTP<br />only</th>"
4129 "<th style='width:40px'>Rm</th></tr>\n");
4131 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4132 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4133 pc_start = pc;
4135 body = NULL;
4136 for (; sc; sc = sc->next) {
4137 c = sc->data;
4139 if (strcmp(last_domain, c->domain) != 0) {
4140 /* new domain */
4141 free(last_domain);
4142 last_domain = strdup(c->domain);
4144 if (body != NULL) {
4145 tmp = body;
4146 body = g_strdup_printf("%s</table>"
4147 "<h2>%s</h2>%s\n",
4148 body, c->domain, table_headers);
4149 g_free(tmp);
4150 } else {
4151 /* first domain */
4152 body = g_strdup_printf("<h2>%s</h2>%s\n",
4153 c->domain, table_headers);
4157 type = "Session";
4158 for (pc = pc_start; pc; pc = pc->next)
4159 if (soup_cookie_equal(pc->data, c)) {
4160 type = "Session + Persistent";
4161 break;
4164 tmp = body;
4165 body = g_strdup_printf(
4166 "%s\n<tr>"
4167 "<td>%s</td>"
4168 "<td style='word-wrap:normal'>%s</td>"
4169 "<td>"
4170 " <textarea rows='4'>%s</textarea>"
4171 "</td>"
4172 "<td>%s</td>"
4173 "<td>%s</td>"
4174 "<td>%d</td>"
4175 "<td>%d</td>"
4176 "<td style='text-align:center'>"
4177 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4178 body,
4179 type,
4180 c->name,
4181 c->value,
4182 c->path,
4183 c->expires ?
4184 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4185 c->secure,
4186 c->http_only,
4188 XT_XTP_STR,
4189 XT_XTP_CL,
4190 cl_session_key,
4191 XT_XTP_CL_REMOVE,
4195 g_free(tmp);
4196 i++;
4199 soup_cookies_free(sc);
4200 soup_cookies_free(pc);
4202 /* small message if there are none */
4203 if (i == 1) {
4204 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4205 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4207 tmp = body;
4208 body = g_strdup_printf("%s</table>", body);
4209 g_free(tmp);
4211 page = get_html_page("Cookie Jar", body, "", TRUE);
4212 g_free(body);
4213 g_free(table_headers);
4214 g_free(last_domain);
4216 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4217 update_cookie_tabs(t);
4219 g_free(page);
4221 return (0);
4225 xtp_page_hl(struct tab *t, struct karg *args)
4227 char *body, *page, *tmp;
4228 struct history *h;
4229 int i = 1; /* all ids start 1 */
4231 DNPRINTF(XT_D_CMD, "%s", __func__);
4233 if (t == NULL) {
4234 show_oops_s("%s invalid parameters", __func__);
4235 return (1);
4238 /* mark this tab as history manager */
4239 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4241 /* Generate a new session key */
4242 if (!updating_hl_tabs)
4243 generate_xtp_session_key(&hl_session_key);
4245 /* body */
4246 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4247 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4249 RB_FOREACH_REVERSE(h, history_list, &hl) {
4250 tmp = body;
4251 body = g_strdup_printf(
4252 "%s\n<tr>"
4253 "<td><a href='%s'>%s</a></td>"
4254 "<td>%s</td>"
4255 "<td style='text-align: center'>"
4256 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4257 body, h->uri, h->uri, h->title,
4258 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4259 XT_XTP_HL_REMOVE, i);
4261 g_free(tmp);
4262 i++;
4265 /* small message if there are none */
4266 if (i == 1) {
4267 tmp = body;
4268 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4269 "colspan='3'>No History</td></tr>\n", body);
4270 g_free(tmp);
4273 tmp = body;
4274 body = g_strdup_printf("%s</table>", body);
4275 g_free(tmp);
4277 page = get_html_page("History", body, "", TRUE);
4278 g_free(body);
4281 * update all history manager tabs as the xtp session
4282 * key has now changed. No need to update the current tab.
4283 * Already did that above.
4285 update_history_tabs(t);
4287 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4288 g_free(page);
4290 return (0);
4294 * Generate a web page detailing the status of any downloads
4297 xtp_page_dl(struct tab *t, struct karg *args)
4299 struct download *dl;
4300 char *body, *page, *tmp;
4301 char *ref;
4302 int n_dl = 1;
4304 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4306 if (t == NULL) {
4307 show_oops_s("%s invalid parameters", __func__);
4308 return (1);
4310 /* mark as a download manager tab */
4311 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4314 * Generate a new session key for next page instance.
4315 * This only happens for the top level call to xtp_page_dl()
4316 * in which case updating_dl_tabs is 0.
4318 if (!updating_dl_tabs)
4319 generate_xtp_session_key(&dl_session_key);
4321 /* header - with refresh so as to update */
4322 if (refresh_interval >= 1)
4323 ref = g_strdup_printf(
4324 "<meta http-equiv='refresh' content='%u"
4325 ";url=%s%d/%s/%d' />\n",
4326 refresh_interval,
4327 XT_XTP_STR,
4328 XT_XTP_DL,
4329 dl_session_key,
4330 XT_XTP_DL_LIST);
4331 else
4332 ref = g_strdup("");
4334 body = g_strdup_printf("<div align='center'>"
4335 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4336 "</p><table><tr><th style='width: 60%%'>"
4337 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4338 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4340 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4341 body = xtp_page_dl_row(t, body, dl);
4342 n_dl++;
4345 /* message if no downloads in list */
4346 if (n_dl == 1) {
4347 tmp = body;
4348 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4349 " style='text-align: center'>"
4350 "No downloads</td></tr>\n", body);
4351 g_free(tmp);
4354 tmp = body;
4355 body = g_strdup_printf("%s</table></div>", body);
4356 g_free(tmp);
4358 page = get_html_page("Downloads", body, ref, 1);
4359 g_free(ref);
4360 g_free(body);
4363 * update all download manager tabs as the xtp session
4364 * key has now changed. No need to update the current tab.
4365 * Already did that above.
4367 update_download_tabs(t);
4369 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4370 g_free(page);
4372 return (0);
4376 search(struct tab *t, struct karg *args)
4378 gboolean d;
4380 if (t == NULL || args == NULL) {
4381 show_oops_s("search invalid parameters");
4382 return (1);
4384 if (t->search_text == NULL) {
4385 if (global_search == NULL)
4386 return (XT_CB_PASSTHROUGH);
4387 else {
4388 t->search_text = g_strdup(global_search);
4389 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4390 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4394 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4395 t->tab_id, args->i, t->search_forward, t->search_text);
4397 switch (args->i) {
4398 case XT_SEARCH_NEXT:
4399 d = t->search_forward;
4400 break;
4401 case XT_SEARCH_PREV:
4402 d = !t->search_forward;
4403 break;
4404 default:
4405 return (XT_CB_PASSTHROUGH);
4408 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4410 return (XT_CB_HANDLED);
4413 struct settings_args {
4414 char **body;
4415 int i;
4418 void
4419 print_setting(struct settings *s, char *val, void *cb_args)
4421 char *tmp, *color;
4422 struct settings_args *sa = cb_args;
4424 if (sa == NULL)
4425 return;
4427 if (s->flags & XT_SF_RUNTIME)
4428 color = "#22cc22";
4429 else
4430 color = "#cccccc";
4432 tmp = *sa->body;
4433 *sa->body = g_strdup_printf(
4434 "%s\n<tr>"
4435 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4436 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4437 *sa->body,
4438 color,
4439 s->name,
4440 color,
4443 g_free(tmp);
4444 sa->i++;
4448 set(struct tab *t, struct karg *args)
4450 char *body, *page, *tmp;
4451 int i = 1;
4452 struct settings_args sa;
4454 bzero(&sa, sizeof sa);
4455 sa.body = &body;
4457 /* body */
4458 body = g_strdup_printf("<div align='center'><table><tr>"
4459 "<th align='left'>Setting</th>"
4460 "<th align='left'>Value</th></tr>\n");
4462 settings_walk(print_setting, &sa);
4463 i = sa.i;
4465 /* small message if there are none */
4466 if (i == 1) {
4467 tmp = body;
4468 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4469 "colspan='2'>No settings</td></tr>\n", body);
4470 g_free(tmp);
4473 tmp = body;
4474 body = g_strdup_printf("%s</table></div>", body);
4475 g_free(tmp);
4477 page = get_html_page("Settings", body, "", 0);
4479 g_free(body);
4481 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4483 g_free(page);
4485 return (XT_CB_PASSTHROUGH);
4489 session_save(struct tab *t, char *filename)
4491 struct karg a;
4492 int rv = 1;
4494 if (strlen(filename) == 0)
4495 goto done;
4497 if (filename[0] == '.' || filename[0] == '/')
4498 goto done;
4500 a.s = filename;
4501 if (save_tabs(t, &a))
4502 goto done;
4503 strlcpy(named_session, filename, sizeof named_session);
4505 rv = 0;
4506 done:
4507 return (rv);
4511 session_open(struct tab *t, char *filename)
4513 struct karg a;
4514 int rv = 1;
4516 if (strlen(filename) == 0)
4517 goto done;
4519 if (filename[0] == '.' || filename[0] == '/')
4520 goto done;
4522 a.s = filename;
4523 a.i = XT_SES_CLOSETABS;
4524 if (open_tabs(t, &a))
4525 goto done;
4527 strlcpy(named_session, filename, sizeof named_session);
4529 rv = 0;
4530 done:
4531 return (rv);
4535 session_delete(struct tab *t, char *filename)
4537 char file[PATH_MAX];
4538 int rv = 1;
4540 if (strlen(filename) == 0)
4541 goto done;
4543 if (filename[0] == '.' || filename[0] == '/')
4544 goto done;
4546 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4547 if (unlink(file))
4548 goto done;
4550 if (!strcmp(filename, named_session))
4551 strlcpy(named_session, XT_SAVED_TABS_FILE,
4552 sizeof named_session);
4554 rv = 0;
4555 done:
4556 return (rv);
4560 session_cmd(struct tab *t, struct karg *args)
4562 char *filename = args->s;
4564 if (t == NULL)
4565 return (1);
4567 if (args->i & XT_SHOW)
4568 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4569 XT_SAVED_TABS_FILE : named_session);
4570 else if (args->i & XT_SAVE) {
4571 if (session_save(t, filename)) {
4572 show_oops(t, "Can't save session: %s",
4573 filename ? filename : "INVALID");
4574 goto done;
4576 } else if (args->i & XT_OPEN) {
4577 if (session_open(t, filename)) {
4578 show_oops(t, "Can't open session: %s",
4579 filename ? filename : "INVALID");
4580 goto done;
4582 } else if (args->i & XT_DELETE) {
4583 if (session_delete(t, filename)) {
4584 show_oops(t, "Can't delete session: %s",
4585 filename ? filename : "INVALID");
4586 goto done;
4589 done:
4590 return (XT_CB_PASSTHROUGH);
4594 * Make a hardcopy of the page
4597 print_page(struct tab *t, struct karg *args)
4599 WebKitWebFrame *frame;
4600 GtkPageSetup *ps;
4601 GtkPrintOperation *op;
4602 GtkPrintOperationAction action;
4603 GtkPrintOperationResult print_res;
4604 GError *g_err = NULL;
4605 int marg_l, marg_r, marg_t, marg_b;
4607 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4609 ps = gtk_page_setup_new();
4610 op = gtk_print_operation_new();
4611 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4612 frame = webkit_web_view_get_main_frame(t->wv);
4614 /* the default margins are too small, so we will bump them */
4615 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4616 XT_PRINT_EXTRA_MARGIN;
4617 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4618 XT_PRINT_EXTRA_MARGIN;
4619 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4620 XT_PRINT_EXTRA_MARGIN;
4621 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4622 XT_PRINT_EXTRA_MARGIN;
4624 /* set margins */
4625 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4626 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4627 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4628 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4630 gtk_print_operation_set_default_page_setup(op, ps);
4632 /* this appears to free 'op' and 'ps' */
4633 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4635 /* check it worked */
4636 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4637 show_oops_s("can't print: %s", g_err->message);
4638 g_error_free (g_err);
4639 return (1);
4642 return (0);
4646 go_home(struct tab *t, struct karg *args)
4648 load_uri(t, home);
4649 return (0);
4653 restart(struct tab *t, struct karg *args)
4655 struct karg a;
4657 a.s = XT_RESTART_TABS_FILE;
4658 save_tabs(t, &a);
4659 execvp(start_argv[0], start_argv);
4660 /* NOTREACHED */
4662 return (0);
4665 #define CTRL GDK_CONTROL_MASK
4666 #define MOD1 GDK_MOD1_MASK
4667 #define SHFT GDK_SHIFT_MASK
4669 /* inherent to GTK not all keys will be caught at all times */
4670 /* XXX sort key bindings */
4671 struct key_binding {
4672 char *cmd;
4673 guint mask;
4674 guint use_in_entry;
4675 guint key;
4676 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4677 } keys[] = {
4678 { "cookiejar", MOD1, 0, GDK_j },
4679 { "downloadmgr", MOD1, 0, GDK_d },
4680 { "history", MOD1, 0, GDK_h },
4681 { "print", CTRL, 0, GDK_p },
4682 { "search", 0, 0, GDK_slash },
4683 { "searchb", 0, 0, GDK_question },
4684 { "command", 0, 0, GDK_colon },
4685 { "qa", CTRL, 0, GDK_q },
4686 { "restart", MOD1, 0, GDK_q },
4687 { "js toggle", CTRL, 0, GDK_j },
4688 { "cookie toggle", MOD1, 0, GDK_c },
4689 { "togglesrc", CTRL, 0, GDK_s },
4690 { "yankuri", 0, 0, GDK_y },
4691 { "pasteuricur", 0, 0, GDK_p },
4692 { "pasteurinew", 0, 0, GDK_P },
4693 { "toplevel toggle", 0, 0, GDK_F4 },
4694 { "help", 0, 0, GDK_F1 },
4696 /* search */
4697 { "searchnext", 0, 0, GDK_n },
4698 { "searchprevious", 0, 0, GDK_N },
4700 /* focus */
4701 { "focusaddress", 0, 0, GDK_F6 },
4702 { "focussearch", 0, 0, GDK_F7 },
4704 /* hinting */
4705 { "hinting", 0, 0, GDK_f },
4707 /* custom stylesheet */
4708 { "userstyle", 0, 0, GDK_i },
4710 /* navigation */
4711 { "goback", 0, 0, GDK_BackSpace },
4712 { "goback", MOD1, 0, GDK_Left },
4713 { "goforward", SHFT, 0, GDK_BackSpace },
4714 { "goforward", MOD1, 0, GDK_Right },
4715 { "reload", 0, 0, GDK_F5 },
4716 { "reload", CTRL, 0, GDK_r },
4717 { "reloadforce", CTRL, 0, GDK_R },
4718 { "reload", CTRL, 0, GDK_l },
4719 { "favorites", MOD1, 1, GDK_f },
4721 /* vertical movement */
4722 { "scrolldown", 0, 0, GDK_j },
4723 { "scrolldown", 0, 0, GDK_Down },
4724 { "scrollup", 0, 0, GDK_Up },
4725 { "scrollup", 0, 0, GDK_k },
4726 { "scrollbottom", 0, 0, GDK_G },
4727 { "scrollbottom", 0, 0, GDK_End },
4728 { "scrolltop", 0, 0, GDK_Home },
4729 { "scrolltop", 0, 0, GDK_g },
4730 { "scrollpagedown", 0, 0, GDK_space },
4731 { "scrollpagedown", CTRL, 0, GDK_f },
4732 { "scrollhalfdown", CTRL, 0, GDK_d },
4733 { "scrollpagedown", 0, 0, GDK_Page_Down },
4734 { "scrollpageup", 0, 0, GDK_Page_Up },
4735 { "scrollpageup", CTRL, 0, GDK_b },
4736 { "scrollhalfup", CTRL, 0, GDK_u },
4737 /* horizontal movement */
4738 { "scrollright", 0, 0, GDK_l },
4739 { "scrollright", 0, 0, GDK_Right },
4740 { "scrollleft", 0, 0, GDK_Left },
4741 { "scrollleft", 0, 0, GDK_h },
4742 { "scrollfarright", 0, 0, GDK_dollar },
4743 { "scrollfarleft", 0, 0, GDK_0 },
4745 /* tabs */
4746 { "tabnew", CTRL, 0, GDK_t },
4747 { "999tabnew", CTRL, 0, GDK_T },
4748 { "tabclose", CTRL, 1, GDK_w },
4749 { "tabundoclose", 0, 0, GDK_U },
4750 { "tabnext 1", CTRL, 0, GDK_1 },
4751 { "tabnext 2", CTRL, 0, GDK_2 },
4752 { "tabnext 3", CTRL, 0, GDK_3 },
4753 { "tabnext 4", CTRL, 0, GDK_4 },
4754 { "tabnext 5", CTRL, 0, GDK_5 },
4755 { "tabnext 6", CTRL, 0, GDK_6 },
4756 { "tabnext 7", CTRL, 0, GDK_7 },
4757 { "tabnext 8", CTRL, 0, GDK_8 },
4758 { "tabnext 9", CTRL, 0, GDK_9 },
4759 { "tabnext 10", CTRL, 0, GDK_0 },
4760 { "tabfirst", CTRL, 0, GDK_less },
4761 { "tablast", CTRL, 0, GDK_greater },
4762 { "tabprevious", CTRL, 0, GDK_Left },
4763 { "tabnext", CTRL, 0, GDK_Right },
4764 { "focusout", CTRL, 0, GDK_minus },
4765 { "focusin", CTRL, 0, GDK_plus },
4766 { "focusin", CTRL, 0, GDK_equal },
4768 /* command aliases (handy when -S flag is used) */
4769 { "promptopen", 0, 0, GDK_F9 },
4770 { "promptopencurrent", 0, 0, GDK_F10 },
4771 { "prompttabnew", 0, 0, GDK_F11 },
4772 { "prompttabnewcurrent",0, 0, GDK_F12 },
4774 TAILQ_HEAD(keybinding_list, key_binding);
4776 void
4777 walk_kb(struct settings *s,
4778 void (*cb)(struct settings *, char *, void *), void *cb_args)
4780 struct key_binding *k;
4781 char str[1024];
4783 if (s == NULL || cb == NULL) {
4784 show_oops_s("walk_kb invalid parameters");
4785 return;
4788 TAILQ_FOREACH(k, &kbl, entry) {
4789 if (k->cmd == NULL)
4790 continue;
4791 str[0] = '\0';
4793 /* sanity */
4794 if (gdk_keyval_name(k->key) == NULL)
4795 continue;
4797 strlcat(str, k->cmd, sizeof str);
4798 strlcat(str, ",", sizeof str);
4800 if (k->mask & GDK_SHIFT_MASK)
4801 strlcat(str, "S-", sizeof str);
4802 if (k->mask & GDK_CONTROL_MASK)
4803 strlcat(str, "C-", sizeof str);
4804 if (k->mask & GDK_MOD1_MASK)
4805 strlcat(str, "M1-", sizeof str);
4806 if (k->mask & GDK_MOD2_MASK)
4807 strlcat(str, "M2-", sizeof str);
4808 if (k->mask & GDK_MOD3_MASK)
4809 strlcat(str, "M3-", sizeof str);
4810 if (k->mask & GDK_MOD4_MASK)
4811 strlcat(str, "M4-", sizeof str);
4812 if (k->mask & GDK_MOD5_MASK)
4813 strlcat(str, "M5-", sizeof str);
4815 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4816 cb(s, str, cb_args);
4820 void
4821 init_keybindings(void)
4823 int i;
4824 struct key_binding *k;
4826 for (i = 0; i < LENGTH(keys); i++) {
4827 k = g_malloc0(sizeof *k);
4828 k->cmd = keys[i].cmd;
4829 k->mask = keys[i].mask;
4830 k->use_in_entry = keys[i].use_in_entry;
4831 k->key = keys[i].key;
4832 TAILQ_INSERT_HEAD(&kbl, k, entry);
4834 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4835 k->cmd ? k->cmd : "unnamed key");
4839 void
4840 keybinding_clearall(void)
4842 struct key_binding *k, *next;
4844 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4845 next = TAILQ_NEXT(k, entry);
4846 if (k->cmd == NULL)
4847 continue;
4849 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4850 k->cmd ? k->cmd : "unnamed key");
4851 TAILQ_REMOVE(&kbl, k, entry);
4852 g_free(k);
4857 keybinding_add(char *cmd, char *key, int use_in_entry)
4859 struct key_binding *k;
4860 guint keyval, mask = 0;
4861 int i;
4863 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4865 /* Keys which are to be used in entry have been prefixed with an
4866 * exclamation mark. */
4867 if (use_in_entry)
4868 key++;
4870 /* find modifier keys */
4871 if (strstr(key, "S-"))
4872 mask |= GDK_SHIFT_MASK;
4873 if (strstr(key, "C-"))
4874 mask |= GDK_CONTROL_MASK;
4875 if (strstr(key, "M1-"))
4876 mask |= GDK_MOD1_MASK;
4877 if (strstr(key, "M2-"))
4878 mask |= GDK_MOD2_MASK;
4879 if (strstr(key, "M3-"))
4880 mask |= GDK_MOD3_MASK;
4881 if (strstr(key, "M4-"))
4882 mask |= GDK_MOD4_MASK;
4883 if (strstr(key, "M5-"))
4884 mask |= GDK_MOD5_MASK;
4886 /* find keyname */
4887 for (i = strlen(key) - 1; i > 0; i--)
4888 if (key[i] == '-')
4889 key = &key[i + 1];
4891 /* validate keyname */
4892 keyval = gdk_keyval_from_name(key);
4893 if (keyval == GDK_VoidSymbol) {
4894 warnx("invalid keybinding name %s", key);
4895 return (1);
4897 /* must run this test too, gtk+ doesn't handle 10 for example */
4898 if (gdk_keyval_name(keyval) == NULL) {
4899 warnx("invalid keybinding name %s", key);
4900 return (1);
4903 /* Remove eventual dupes. */
4904 TAILQ_FOREACH(k, &kbl, entry)
4905 if (k->key == keyval && k->mask == mask) {
4906 TAILQ_REMOVE(&kbl, k, entry);
4907 g_free(k);
4908 break;
4911 /* add keyname */
4912 k = g_malloc0(sizeof *k);
4913 k->cmd = g_strdup(cmd);
4914 k->mask = mask;
4915 k->use_in_entry = use_in_entry;
4916 k->key = keyval;
4918 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4919 k->cmd,
4920 k->mask,
4921 k->use_in_entry,
4922 k->key);
4923 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4924 k->cmd, gdk_keyval_name(keyval));
4926 TAILQ_INSERT_HEAD(&kbl, k, entry);
4928 return (0);
4932 add_kb(struct settings *s, char *entry)
4934 char *kb, *key;
4936 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4938 /* clearall is special */
4939 if (!strcmp(entry, "clearall")) {
4940 keybinding_clearall();
4941 return (0);
4944 kb = strstr(entry, ",");
4945 if (kb == NULL)
4946 return (1);
4947 *kb = '\0';
4948 key = kb + 1;
4950 return (keybinding_add(entry, key, key[0] == '!'));
4953 struct cmd {
4954 char *cmd;
4955 int level;
4956 int (*func)(struct tab *, struct karg *);
4957 int arg;
4958 int type;
4959 } cmds[] = {
4960 { "command", 0, command, ':', 0 },
4961 { "search", 0, command, '/', 0 },
4962 { "searchb", 0, command, '?', 0 },
4963 { "togglesrc", 0, toggle_src, 0, 0 },
4965 /* yanking and pasting */
4966 { "yankuri", 0, yank_uri, 0, 0 },
4967 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4968 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
4969 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
4971 /* search */
4972 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
4973 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
4975 /* focus */
4976 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
4977 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
4979 /* hinting */
4980 { "hinting", 0, hint, 0, 0 },
4982 /* custom stylesheet */
4983 { "userstyle", 0, userstyle, 0, 0 },
4985 /* navigation */
4986 { "goback", 0, navaction, XT_NAV_BACK, 0 },
4987 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
4988 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
4989 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
4991 /* vertical movement */
4992 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
4993 { "scrollup", 0, move, XT_MOVE_UP, 0 },
4994 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
4995 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
4996 { "1", 0, move, XT_MOVE_TOP, 0 },
4997 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
4998 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
4999 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5000 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5001 /* horizontal movement */
5002 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5003 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5004 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5005 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5008 { "favorites", 0, xtp_page_fl, 0, 0 },
5009 { "fav", 0, xtp_page_fl, 0, 0 },
5010 { "favadd", 0, add_favorite, 0, 0 },
5012 { "qall", 0, quit, 0, 0 },
5013 { "quitall", 0, quit, 0, 0 },
5014 { "w", 0, save_tabs, 0, 0 },
5015 { "wq", 0, save_tabs_and_quit, 0, 0 },
5016 { "help", 0, help, 0, 0 },
5017 { "about", 0, about, 0, 0 },
5018 { "stats", 0, stats, 0, 0 },
5019 { "version", 0, about, 0, 0 },
5021 /* js command */
5022 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5023 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5024 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5025 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5026 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5027 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5028 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5029 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5030 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5031 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5032 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5034 /* cookie command */
5035 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5036 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5037 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5038 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5039 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5040 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5041 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5042 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5043 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5044 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5045 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5047 /* toplevel (domain) command */
5048 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5049 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5051 /* cookie jar */
5052 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5054 /* cert command */
5055 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5056 { "save", 1, cert_cmd, XT_SAVE, 0 },
5057 { "show", 1, cert_cmd, XT_SHOW, 0 },
5059 { "ca", 0, ca_cmd, 0, 0 },
5060 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5061 { "dl", 0, xtp_page_dl, 0, 0 },
5062 { "h", 0, xtp_page_hl, 0, 0 },
5063 { "history", 0, xtp_page_hl, 0, 0 },
5064 { "home", 0, go_home, 0, 0 },
5065 { "restart", 0, restart, 0, 0 },
5066 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5067 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5068 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5069 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5071 { "print", 0, print_page, 0, 0 },
5073 /* tabs */
5074 { "focusin", 0, resizetab, 1, 0 },
5075 { "focusout", 0, resizetab, -1, 0 },
5076 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5077 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5078 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5079 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5080 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5081 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5082 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5083 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5084 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5085 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5086 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5087 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5088 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5089 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5091 /* command aliases (handy when -S flag is used) */
5092 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5093 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5094 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5095 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5097 /* settings */
5098 { "set", 0, set, 0, 0 },
5099 { "fullscreen", 0, fullscreen, 0, 0 },
5100 { "f", 0, fullscreen, 0, 0 },
5102 /* sessions */
5103 { "session", 0, session_cmd, XT_SHOW, 0 },
5104 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5105 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5106 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5107 { "show", 1, session_cmd, XT_SHOW, 0 },
5110 struct {
5111 int index;
5112 int len;
5113 gchar *list[256];
5114 } cmd_status = {-1, 0};
5116 gboolean
5117 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5119 struct karg a;
5121 hide_oops(t);
5123 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5124 /* go backward */
5125 a.i = XT_NAV_BACK;
5126 navaction(t, &a);
5128 return (TRUE);
5129 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5130 /* go forward */
5131 a.i = XT_NAV_FORWARD;
5132 navaction(t, &a);
5134 return (TRUE);
5137 return (FALSE);
5140 gboolean
5141 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5143 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5145 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5146 delete_tab(t);
5148 return (FALSE);
5152 * cancel, remove, etc. downloads
5154 void
5155 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5157 struct download find, *d = NULL;
5159 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5161 /* some commands require a valid download id */
5162 if (cmd != XT_XTP_DL_LIST) {
5163 /* lookup download in question */
5164 find.id = id;
5165 d = RB_FIND(download_list, &downloads, &find);
5167 if (d == NULL) {
5168 show_oops(t, "%s: no such download", __func__);
5169 return;
5173 /* decide what to do */
5174 switch (cmd) {
5175 case XT_XTP_DL_CANCEL:
5176 webkit_download_cancel(d->download);
5177 break;
5178 case XT_XTP_DL_REMOVE:
5179 webkit_download_cancel(d->download); /* just incase */
5180 g_object_unref(d->download);
5181 RB_REMOVE(download_list, &downloads, d);
5182 break;
5183 case XT_XTP_DL_LIST:
5184 /* Nothing */
5185 break;
5186 default:
5187 show_oops(t, "%s: unknown command", __func__);
5188 break;
5190 xtp_page_dl(t, NULL);
5194 * Actions on history, only does one thing for now, but
5195 * we provide the function for future actions
5197 void
5198 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5200 struct history *h, *next;
5201 int i = 1;
5203 switch (cmd) {
5204 case XT_XTP_HL_REMOVE:
5205 /* walk backwards, as listed in reverse */
5206 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5207 next = RB_PREV(history_list, &hl, h);
5208 if (id == i) {
5209 RB_REMOVE(history_list, &hl, h);
5210 g_free((gpointer) h->title);
5211 g_free((gpointer) h->uri);
5212 g_free(h);
5213 break;
5215 i++;
5217 break;
5218 case XT_XTP_HL_LIST:
5219 /* Nothing - just xtp_page_hl() below */
5220 break;
5221 default:
5222 show_oops(t, "%s: unknown command", __func__);
5223 break;
5226 xtp_page_hl(t, NULL);
5229 /* remove a favorite */
5230 void
5231 remove_favorite(struct tab *t, int index)
5233 char file[PATH_MAX], *title, *uri = NULL;
5234 char *new_favs, *tmp;
5235 FILE *f;
5236 int i;
5237 size_t len, lineno;
5239 /* open favorites */
5240 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5242 if ((f = fopen(file, "r")) == NULL) {
5243 show_oops(t, "%s: can't open favorites: %s",
5244 __func__, strerror(errno));
5245 return;
5248 /* build a string which will become the new favroites file */
5249 new_favs = g_strdup("");
5251 for (i = 1;;) {
5252 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5253 if (feof(f) || ferror(f))
5254 break;
5255 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5256 if (len == 0) {
5257 free(title);
5258 title = NULL;
5259 continue;
5262 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5263 if (feof(f) || ferror(f)) {
5264 show_oops(t, "%s: can't parse favorites %s",
5265 __func__, strerror(errno));
5266 goto clean;
5270 /* as long as this isn't the one we are deleting add to file */
5271 if (i != index) {
5272 tmp = new_favs;
5273 new_favs = g_strdup_printf("%s%s\n%s\n",
5274 new_favs, title, uri);
5275 g_free(tmp);
5278 free(uri);
5279 uri = NULL;
5280 free(title);
5281 title = NULL;
5282 i++;
5284 fclose(f);
5286 /* write back new favorites file */
5287 if ((f = fopen(file, "w")) == NULL) {
5288 show_oops(t, "%s: can't open favorites: %s",
5289 __func__, strerror(errno));
5290 goto clean;
5293 fwrite(new_favs, strlen(new_favs), 1, f);
5294 fclose(f);
5296 clean:
5297 if (uri)
5298 free(uri);
5299 if (title)
5300 free(title);
5302 g_free(new_favs);
5305 void
5306 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5308 switch (cmd) {
5309 case XT_XTP_FL_LIST:
5310 /* nothing, just the below call to xtp_page_fl() */
5311 break;
5312 case XT_XTP_FL_REMOVE:
5313 remove_favorite(t, arg);
5314 break;
5315 default:
5316 show_oops(t, "%s: invalid favorites command", __func__);
5317 break;
5320 xtp_page_fl(t, NULL);
5323 void
5324 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5326 switch (cmd) {
5327 case XT_XTP_CL_LIST:
5328 /* nothing, just xtp_page_cl() */
5329 break;
5330 case XT_XTP_CL_REMOVE:
5331 remove_cookie(arg);
5332 break;
5333 default:
5334 show_oops(t, "%s: unknown cookie xtp command", __func__);
5335 break;
5338 xtp_page_cl(t, NULL);
5341 /* link an XTP class to it's session key and handler function */
5342 struct xtp_despatch {
5343 uint8_t xtp_class;
5344 char **session_key;
5345 void (*handle_func)(struct tab *, uint8_t, int);
5348 struct xtp_despatch xtp_despatches[] = {
5349 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5350 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5351 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5352 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5353 { XT_XTP_INVALID, NULL, NULL }
5357 * is the url xtp protocol? (xxxt://)
5358 * if so, parse and despatch correct bahvior
5361 parse_xtp_url(struct tab *t, const char *url)
5363 char *dup = NULL, *p, *last;
5364 uint8_t n_tokens = 0;
5365 char *tokens[4] = {NULL, NULL, NULL, ""};
5366 struct xtp_despatch *dsp, *dsp_match = NULL;
5367 uint8_t req_class;
5368 int ret = FALSE;
5371 * tokens array meaning:
5372 * tokens[0] = class
5373 * tokens[1] = session key
5374 * tokens[2] = action
5375 * tokens[3] = optional argument
5378 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5380 /*xtp tab meaning is normal unless proven special */
5381 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5383 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5384 goto clean;
5386 dup = g_strdup(url + strlen(XT_XTP_STR));
5388 /* split out the url */
5389 for ((p = strtok_r(dup, "/", &last)); p;
5390 (p = strtok_r(NULL, "/", &last))) {
5391 if (n_tokens < 4)
5392 tokens[n_tokens++] = p;
5395 /* should be atleast three fields 'class/seskey/command/arg' */
5396 if (n_tokens < 3)
5397 goto clean;
5399 dsp = xtp_despatches;
5400 req_class = atoi(tokens[0]);
5401 while (dsp->xtp_class) {
5402 if (dsp->xtp_class == req_class) {
5403 dsp_match = dsp;
5404 break;
5406 dsp++;
5409 /* did we find one atall? */
5410 if (dsp_match == NULL) {
5411 show_oops(t, "%s: no matching xtp despatch found", __func__);
5412 goto clean;
5415 /* check session key and call despatch function */
5416 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5417 ret = TRUE; /* all is well, this was a valid xtp request */
5418 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5421 clean:
5422 if (dup)
5423 g_free(dup);
5425 return (ret);
5430 void
5431 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5433 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5435 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5437 if (t == NULL) {
5438 show_oops_s("activate_uri_entry_cb invalid parameters");
5439 return;
5442 if (uri == NULL) {
5443 show_oops(t, "activate_uri_entry_cb no uri");
5444 return;
5447 uri += strspn(uri, "\t ");
5449 /* if xxxt:// treat specially */
5450 if (parse_xtp_url(t, uri))
5451 return;
5453 /* otherwise continue to load page normally */
5454 load_uri(t, (gchar *)uri);
5455 focus_webview(t);
5458 void
5459 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5461 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5462 char *newuri = NULL;
5463 gchar *enc_search;
5465 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5467 if (t == NULL) {
5468 show_oops_s("activate_search_entry_cb invalid parameters");
5469 return;
5472 if (search_string == NULL) {
5473 show_oops(t, "no search_string");
5474 return;
5477 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5478 newuri = g_strdup_printf(search_string, enc_search);
5479 g_free(enc_search);
5481 webkit_web_view_load_uri(t->wv, newuri);
5482 focus_webview(t);
5484 if (newuri)
5485 g_free(newuri);
5488 void
5489 check_and_set_js(const gchar *uri, struct tab *t)
5491 struct domain *d = NULL;
5492 int es = 0;
5494 if (uri == NULL || t == NULL)
5495 return;
5497 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5498 es = 0;
5499 else
5500 es = 1;
5502 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5503 es ? "enable" : "disable", uri);
5505 g_object_set(G_OBJECT(t->settings),
5506 "enable-scripts", es, (char *)NULL);
5507 g_object_set(G_OBJECT(t->settings),
5508 "javascript-can-open-windows-automatically", es, (char *)NULL);
5509 webkit_web_view_set_settings(t->wv, t->settings);
5511 button_set_stockid(t->js_toggle,
5512 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5515 void
5516 show_ca_status(struct tab *t, const char *uri)
5518 WebKitWebFrame *frame;
5519 WebKitWebDataSource *source;
5520 WebKitNetworkRequest *request;
5521 SoupMessage *message;
5522 GdkColor color;
5523 gchar *col_str = XT_COLOR_WHITE;
5524 int r;
5526 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5527 ssl_strict_certs, ssl_ca_file, uri);
5529 if (uri == NULL)
5530 goto done;
5531 if (ssl_ca_file == NULL) {
5532 if (g_str_has_prefix(uri, "http://"))
5533 goto done;
5534 if (g_str_has_prefix(uri, "https://")) {
5535 col_str = XT_COLOR_RED;
5536 goto done;
5538 return;
5540 if (g_str_has_prefix(uri, "http://") ||
5541 !g_str_has_prefix(uri, "https://"))
5542 goto done;
5544 frame = webkit_web_view_get_main_frame(t->wv);
5545 source = webkit_web_frame_get_data_source(frame);
5546 request = webkit_web_data_source_get_request(source);
5547 message = webkit_network_request_get_message(request);
5549 if (message && (soup_message_get_flags(message) &
5550 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5551 col_str = XT_COLOR_GREEN;
5552 goto done;
5553 } else {
5554 r = load_compare_cert(t, NULL);
5555 if (r == 0)
5556 col_str = XT_COLOR_BLUE;
5557 else if (r == 1)
5558 col_str = XT_COLOR_YELLOW;
5559 else
5560 col_str = XT_COLOR_RED;
5561 goto done;
5563 done:
5564 if (col_str) {
5565 gdk_color_parse(col_str, &color);
5566 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5568 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5569 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5570 &color);
5571 gdk_color_parse(XT_COLOR_BLACK, &color);
5572 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5573 &color);
5574 } else {
5575 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5576 &color);
5577 gdk_color_parse(XT_COLOR_BLACK, &color);
5578 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5579 &color);
5584 void
5585 free_favicon(struct tab *t)
5587 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5588 __func__, t->icon_download, t->icon_request);
5590 if (t->icon_request)
5591 g_object_unref(t->icon_request);
5592 if (t->icon_dest_uri)
5593 g_free(t->icon_dest_uri);
5595 t->icon_request = NULL;
5596 t->icon_dest_uri = NULL;
5599 void
5600 xt_icon_from_name(struct tab *t, gchar *name)
5602 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5603 GTK_ENTRY_ICON_PRIMARY, "text-html");
5604 if (show_url == 0)
5605 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5606 GTK_ENTRY_ICON_PRIMARY, "text-html");
5607 else
5608 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5609 GTK_ENTRY_ICON_PRIMARY, NULL);
5612 void
5613 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5615 GdkPixbuf *pb_scaled;
5617 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5618 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16, GDK_INTERP_BILINEAR);
5619 else
5620 pb_scaled = pb;
5622 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5623 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5624 if (show_url == 0)
5625 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5626 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5627 else
5628 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5629 GTK_ENTRY_ICON_PRIMARY, NULL);
5631 if (pb_scaled != pb)
5632 g_object_unref(pb_scaled);
5635 void
5636 xt_icon_from_file(struct tab *t, char *file)
5638 GdkPixbuf *pb;
5640 if (g_str_has_prefix(file, "file://"))
5641 file += strlen("file://");
5643 pb = gdk_pixbuf_new_from_file(file, NULL);
5644 if (pb) {
5645 xt_icon_from_pixbuf(t, pb);
5646 g_object_unref(pb);
5647 } else
5648 xt_icon_from_name(t, "text-html");
5651 gboolean
5652 is_valid_icon(char *file)
5654 gboolean valid = 0;
5655 const char *mime_type;
5656 GFileInfo *fi;
5657 GFile *gf;
5659 gf = g_file_new_for_path(file);
5660 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5661 NULL, NULL);
5662 mime_type = g_file_info_get_content_type(fi);
5663 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5664 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5665 g_strcmp0(mime_type, "image/png") == 0 ||
5666 g_strcmp0(mime_type, "image/gif") == 0 ||
5667 g_strcmp0(mime_type, "application/octet-stream") == 0;
5668 g_object_unref(fi);
5669 g_object_unref(gf);
5671 return (valid);
5674 void
5675 set_favicon_from_file(struct tab *t, char *file)
5677 struct stat sb;
5679 if (t == NULL || file == NULL)
5680 return;
5682 if (g_str_has_prefix(file, "file://"))
5683 file += strlen("file://");
5684 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5686 if (!stat(file, &sb)) {
5687 if (sb.st_size == 0 || !is_valid_icon(file)) {
5688 /* corrupt icon so trash it */
5689 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5690 __func__, file);
5691 unlink(file);
5692 /* no need to set icon to default here */
5693 return;
5696 xt_icon_from_file(t, file);
5699 void
5700 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5701 WebKitWebView *wv)
5703 WebKitDownloadStatus status = webkit_download_get_status(download);
5704 struct tab *tt = NULL, *t = NULL;
5707 * find the webview instead of passing in the tab as it could have been
5708 * deleted from underneath us.
5710 TAILQ_FOREACH(tt, &tabs, entry) {
5711 if (tt->wv == wv) {
5712 t = tt;
5713 break;
5716 if (t == NULL)
5717 return;
5719 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5720 __func__, t->tab_id, status);
5722 switch (status) {
5723 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5724 /* -1 */
5725 t->icon_download = NULL;
5726 free_favicon(t);
5727 break;
5728 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5729 /* 0 */
5730 break;
5731 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5732 /* 1 */
5733 break;
5734 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5735 /* 2 */
5736 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5737 __func__, t->tab_id);
5738 t->icon_download = NULL;
5739 free_favicon(t);
5740 break;
5741 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5742 /* 3 */
5744 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5745 __func__, t->icon_dest_uri);
5746 set_favicon_from_file(t, t->icon_dest_uri);
5747 /* these will be freed post callback */
5748 t->icon_request = NULL;
5749 t->icon_download = NULL;
5750 break;
5751 default:
5752 break;
5756 void
5757 abort_favicon_download(struct tab *t)
5759 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5761 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5762 if (t->icon_download) {
5763 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5764 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5765 webkit_download_cancel(t->icon_download);
5766 t->icon_download = NULL;
5767 } else
5768 free_favicon(t);
5771 xt_icon_from_name(t, "text-html");
5774 void
5775 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5777 GdkPixbuf *pb;
5778 gchar *name_hash, file[PATH_MAX];
5779 struct stat sb;
5781 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5783 if (uri == NULL || t == NULL)
5784 return;
5786 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5787 /* take icon from WebKitIconDatabase */
5788 pb = webkit_web_view_get_icon_pixbuf(wv);
5789 if (pb) {
5790 xt_icon_from_pixbuf(t, pb);
5791 g_object_unref(pb);
5792 } else
5793 xt_icon_from_name(t, "text-html");
5795 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5796 /* download icon to cache dir */
5797 if (t->icon_request) {
5798 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5799 return;
5802 /* check to see if we got the icon in cache */
5803 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5804 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5805 g_free(name_hash);
5807 if (!stat(file, &sb)) {
5808 if (sb.st_size > 0) {
5809 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5810 __func__, file);
5811 set_favicon_from_file(t, file);
5812 return;
5815 /* corrupt icon so trash it */
5816 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5817 __func__, file);
5818 unlink(file);
5821 /* create download for icon */
5822 t->icon_request = webkit_network_request_new(uri);
5823 if (t->icon_request == NULL) {
5824 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5825 __func__, uri);
5826 return;
5829 t->icon_download = webkit_download_new(t->icon_request);
5830 if (t->icon_download == NULL)
5831 return;
5833 /* we have to free icon_dest_uri later */
5834 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5835 webkit_download_set_destination_uri(t->icon_download,
5836 t->icon_dest_uri);
5838 if (webkit_download_get_status(t->icon_download) ==
5839 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5840 g_object_unref(t->icon_request);
5841 g_free(t->icon_dest_uri);
5842 t->icon_request = NULL;
5843 t->icon_dest_uri = NULL;
5844 return;
5847 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5848 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5850 webkit_download_start(t->icon_download);
5854 void
5855 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5857 const gchar *set = NULL, *uri = NULL, *title = NULL;
5858 struct history *h, find;
5859 const gchar *s_loading;
5860 struct karg a;
5862 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5863 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5865 if (t == NULL) {
5866 show_oops_s("notify_load_status_cb invalid parameters");
5867 return;
5870 switch (webkit_web_view_get_load_status(wview)) {
5871 case WEBKIT_LOAD_PROVISIONAL:
5872 /* 0 */
5873 abort_favicon_download(t);
5874 #if GTK_CHECK_VERSION(2, 20, 0)
5875 gtk_widget_show(t->spinner);
5876 gtk_spinner_start(GTK_SPINNER(t->spinner));
5877 #endif
5878 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5880 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5882 /* take focus if we are visible */
5883 focus_webview(t);
5884 t->focus_wv = 1;
5886 break;
5888 case WEBKIT_LOAD_COMMITTED:
5889 /* 1 */
5890 if ((uri = get_uri(wview)) != NULL) {
5891 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5892 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5894 if (t->status) {
5895 g_free(t->status);
5896 t->status = NULL;
5898 set_status(t, (char *)uri, XT_STATUS_LOADING);
5901 /* check if js white listing is enabled */
5902 if (enable_js_whitelist) {
5903 uri = get_uri(wview);
5904 check_and_set_js(uri, t);
5907 if (t->styled)
5908 apply_style(t);
5910 show_ca_status(t, uri);
5912 /* we know enough to autosave the session */
5913 if (session_autosave) {
5914 a.s = NULL;
5915 save_tabs(t, &a);
5917 break;
5919 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5920 /* 3 */
5921 break;
5923 case WEBKIT_LOAD_FINISHED:
5924 /* 2 */
5925 uri = get_uri(wview);
5926 if (uri == NULL)
5927 return;
5929 if (!strncmp(uri, "http://", strlen("http://")) ||
5930 !strncmp(uri, "https://", strlen("https://")) ||
5931 !strncmp(uri, "file://", strlen("file://"))) {
5932 find.uri = uri;
5933 h = RB_FIND(history_list, &hl, &find);
5934 if (!h) {
5935 title = webkit_web_view_get_title(wview);
5936 set = title ? title: uri;
5937 h = g_malloc(sizeof *h);
5938 h->uri = g_strdup(uri);
5939 h->title = g_strdup(set);
5940 RB_INSERT(history_list, &hl, h);
5941 completion_add_uri(h->uri);
5942 update_history_tabs(NULL);
5946 set_status(t, (char *)uri, XT_STATUS_URI);
5947 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5948 case WEBKIT_LOAD_FAILED:
5949 /* 4 */
5950 #endif
5951 #if GTK_CHECK_VERSION(2, 20, 0)
5952 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5953 gtk_widget_hide(t->spinner);
5954 #endif
5955 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5956 if (s_loading && !strcmp(s_loading, "Loading"))
5957 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5958 default:
5959 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5960 break;
5963 if (t->item)
5964 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5965 else
5966 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5967 webkit_web_view_can_go_back(wview));
5969 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5970 webkit_web_view_can_go_forward(wview));
5973 void
5974 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5976 const gchar *set = NULL, *title = NULL;
5978 title = webkit_web_view_get_title(wview);
5979 set = title ? title: get_uri(wview);
5980 if (set) {
5981 gtk_label_set_text(GTK_LABEL(t->label), set);
5982 gtk_window_set_title(GTK_WINDOW(main_window), set);
5983 } else {
5984 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5985 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5989 void
5990 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5992 run_script(t, JS_HINTING);
5995 void
5996 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5998 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5999 progress == 100 ? 0 : (double)progress / 100);
6000 if (show_url == 0) {
6001 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
6002 progress == 100 ? 0 : (double)progress / 100);
6007 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6008 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6009 WebKitWebPolicyDecision *pd, struct tab *t)
6011 char *uri;
6012 WebKitWebNavigationReason reason;
6013 struct domain *d = NULL;
6015 if (t == NULL) {
6016 show_oops_s("webview_npd_cb invalid parameters");
6017 return (FALSE);
6020 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6021 t->ctrl_click,
6022 webkit_network_request_get_uri(request));
6024 uri = (char *)webkit_network_request_get_uri(request);
6026 /* if this is an xtp url, we don't load anything else */
6027 if (parse_xtp_url(t, uri))
6028 return (TRUE);
6030 if (t->ctrl_click) {
6031 t->ctrl_click = 0;
6032 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6033 webkit_web_policy_decision_ignore(pd);
6034 return (TRUE); /* we made the decission */
6038 * This is a little hairy but it comes down to this:
6039 * when we run in whitelist mode we have to assist the browser in
6040 * opening the URL that it would have opened in a new tab.
6042 reason = webkit_web_navigation_action_get_reason(na);
6043 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6044 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6045 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6046 load_uri(t, uri);
6048 webkit_web_policy_decision_use(pd);
6049 return (TRUE); /* we made the decission */
6052 return (FALSE);
6055 WebKitWebView *
6056 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6058 struct tab *tt;
6059 struct domain *d = NULL;
6060 const gchar *uri;
6061 WebKitWebView *webview = NULL;
6063 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6064 webkit_web_view_get_uri(wv));
6066 if (tabless) {
6067 /* open in current tab */
6068 webview = t->wv;
6069 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6070 uri = webkit_web_view_get_uri(wv);
6071 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6072 return (NULL);
6074 tt = create_new_tab(NULL, NULL, 1, -1);
6075 webview = tt->wv;
6076 } else if (enable_scripts == 1) {
6077 tt = create_new_tab(NULL, NULL, 1, -1);
6078 webview = tt->wv;
6081 return (webview);
6084 gboolean
6085 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6087 const gchar *uri;
6088 struct domain *d = NULL;
6090 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6092 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6093 uri = webkit_web_view_get_uri(wv);
6094 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6095 return (FALSE);
6097 delete_tab(t);
6098 } else if (enable_scripts == 1)
6099 delete_tab(t);
6101 return (TRUE);
6105 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6107 /* we can not eat the event without throwing gtk off so defer it */
6109 /* catch middle click */
6110 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6111 t->ctrl_click = 1;
6112 goto done;
6115 /* catch ctrl click */
6116 if (e->type == GDK_BUTTON_RELEASE &&
6117 CLEAN(e->state) == GDK_CONTROL_MASK)
6118 t->ctrl_click = 1;
6119 else
6120 t->ctrl_click = 0;
6121 done:
6122 return (XT_CB_PASSTHROUGH);
6126 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6128 struct mime_type *m;
6130 m = find_mime_type(mime_type);
6131 if (m == NULL)
6132 return (1);
6133 if (m->mt_download)
6134 return (1);
6136 switch (fork()) {
6137 case -1:
6138 show_oops(t, "can't fork mime handler");
6139 /* NOTREACHED */
6140 case 0:
6141 break;
6142 default:
6143 return (0);
6146 /* child */
6147 execlp(m->mt_action, m->mt_action,
6148 webkit_network_request_get_uri(request), (void *)NULL);
6150 _exit(0);
6152 /* NOTREACHED */
6153 return (0);
6156 const gchar *
6157 get_mime_type(char *file)
6159 const char *mime_type;
6160 GFileInfo *fi;
6161 GFile *gf;
6163 if (g_str_has_prefix(file, "file://"))
6164 file += strlen("file://");
6166 gf = g_file_new_for_path(file);
6167 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6168 NULL, NULL);
6169 mime_type = g_file_info_get_content_type(fi);
6170 g_object_unref(fi);
6171 g_object_unref(gf);
6173 return (mime_type);
6177 run_download_mimehandler(char *mime_type, char *file)
6179 struct mime_type *m;
6181 m = find_mime_type(mime_type);
6182 if (m == NULL)
6183 return (1);
6185 switch (fork()) {
6186 case -1:
6187 show_oops_s("can't fork download mime handler");
6188 /* NOTREACHED */
6189 case 0:
6190 break;
6191 default:
6192 return (0);
6195 /* child */
6196 if (g_str_has_prefix(file, "file://"))
6197 file += strlen("file://");
6198 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6200 _exit(0);
6202 /* NOTREACHED */
6203 return (0);
6206 void
6207 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6208 WebKitWebView *wv)
6210 WebKitDownloadStatus status;
6211 const gchar *file = NULL, *mime = NULL;
6213 if (download == NULL)
6214 return;
6215 status = webkit_download_get_status(download);
6216 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6217 return;
6219 file = webkit_download_get_destination_uri(download);
6220 if (file == NULL)
6221 return;
6222 mime = get_mime_type((char *)file);
6223 if (mime == NULL)
6224 return;
6226 run_download_mimehandler((char *)mime, (char *)file);
6230 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6231 WebKitNetworkRequest *request, char *mime_type,
6232 WebKitWebPolicyDecision *decision, struct tab *t)
6234 if (t == NULL) {
6235 show_oops_s("webview_mimetype_cb invalid parameters");
6236 return (FALSE);
6239 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6240 t->tab_id, mime_type);
6242 if (run_mimehandler(t, mime_type, request) == 0) {
6243 webkit_web_policy_decision_ignore(decision);
6244 focus_webview(t);
6245 return (TRUE);
6248 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6249 webkit_web_policy_decision_download(decision);
6250 return (TRUE);
6253 return (FALSE);
6257 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6258 struct tab *t)
6260 struct stat sb;
6261 const gchar *suggested_name;
6262 gchar *filename = NULL;
6263 char *uri = NULL;
6264 struct download *download_entry;
6265 int i, ret = TRUE;
6267 if (wk_download == NULL || t == NULL) {
6268 show_oops_s("%s invalid parameters", __func__);
6269 return (FALSE);
6272 suggested_name = webkit_download_get_suggested_filename(wk_download);
6273 if (suggested_name == NULL)
6274 return (FALSE); /* abort download */
6276 i = 0;
6277 do {
6278 if (filename) {
6279 g_free(filename);
6280 filename = NULL;
6282 if (i) {
6283 g_free(uri);
6284 uri = NULL;
6285 filename = g_strdup_printf("%d%s", i, suggested_name);
6287 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6288 filename : suggested_name);
6289 i++;
6290 } while (!stat(uri + strlen("file://"), &sb));
6292 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6293 "local %s\n", __func__, t->tab_id, filename, uri);
6295 webkit_download_set_destination_uri(wk_download, uri);
6297 if (webkit_download_get_status(wk_download) ==
6298 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6299 show_oops(t, "%s: download failed to start", __func__);
6300 ret = FALSE;
6301 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6302 } else {
6303 /* connect "download first" mime handler */
6304 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6305 G_CALLBACK(download_status_changed_cb), NULL);
6307 download_entry = g_malloc(sizeof(struct download));
6308 download_entry->download = wk_download;
6309 download_entry->tab = t;
6310 download_entry->id = next_download_id++;
6311 RB_INSERT(download_list, &downloads, download_entry);
6312 /* get from history */
6313 g_object_ref(wk_download);
6314 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6315 show_oops(t, "Download of '%s' started...",
6316 basename(webkit_download_get_destination_uri(wk_download)));
6319 if (uri)
6320 g_free(uri);
6322 if (filename)
6323 g_free(filename);
6325 /* sync other download manager tabs */
6326 update_download_tabs(NULL);
6329 * NOTE: never redirect/render the current tab before this
6330 * function returns. This will cause the download to never start.
6332 return (ret); /* start download */
6335 void
6336 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6338 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6340 if (t == NULL) {
6341 show_oops_s("webview_hover_cb");
6342 return;
6345 if (uri)
6346 set_status(t, uri, XT_STATUS_LINK);
6347 else {
6348 if (t->status)
6349 set_status(t, t->status, XT_STATUS_NOTHING);
6353 gboolean
6354 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6356 struct key_binding *k;
6358 TAILQ_FOREACH(k, &kbl, entry)
6359 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6360 if (k->mask == 0) {
6361 if ((e->state & (CTRL | MOD1)) == 0)
6362 return (cmd_execute(t, k->cmd));
6363 } else if ((e->state & k->mask) == k->mask) {
6364 return (cmd_execute(t, k->cmd));
6368 return (XT_CB_PASSTHROUGH);
6372 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6374 char s[2], buf[128];
6375 const char *errstr = NULL;
6376 long long link;
6378 /* don't use w directly; use t->whatever instead */
6380 if (t == NULL) {
6381 show_oops_s("wv_keypress_after_cb");
6382 return (XT_CB_PASSTHROUGH);
6385 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6386 e->keyval, e->state, t);
6388 if (t->hints_on) {
6389 /* ESC */
6390 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6391 disable_hints(t);
6392 return (XT_CB_HANDLED);
6395 /* RETURN */
6396 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6397 link = strtonum(t->hint_num, 1, 1000, &errstr);
6398 if (errstr) {
6399 /* we have a string */
6400 } else {
6401 /* we have a number */
6402 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6403 t->hint_num);
6404 run_script(t, buf);
6406 disable_hints(t);
6409 /* BACKSPACE */
6410 /* XXX unfuck this */
6411 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6412 if (t->hint_mode == XT_HINT_NUMERICAL) {
6413 /* last input was numerical */
6414 int l;
6415 l = strlen(t->hint_num);
6416 if (l > 0) {
6417 l--;
6418 if (l == 0) {
6419 disable_hints(t);
6420 enable_hints(t);
6421 } else {
6422 t->hint_num[l] = '\0';
6423 goto num;
6426 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6427 /* last input was alphanumerical */
6428 int l;
6429 l = strlen(t->hint_buf);
6430 if (l > 0) {
6431 l--;
6432 if (l == 0) {
6433 disable_hints(t);
6434 enable_hints(t);
6435 } else {
6436 t->hint_buf[l] = '\0';
6437 goto anum;
6440 } else {
6441 /* bogus */
6442 disable_hints(t);
6446 /* numerical input */
6447 if (CLEAN(e->state) == 0 &&
6448 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6449 snprintf(s, sizeof s, "%c", e->keyval);
6450 strlcat(t->hint_num, s, sizeof t->hint_num);
6451 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6452 t->hint_num);
6453 num:
6454 link = strtonum(t->hint_num, 1, 1000, &errstr);
6455 if (errstr) {
6456 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6457 disable_hints(t);
6458 } else {
6459 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6460 t->hint_num);
6461 t->hint_mode = XT_HINT_NUMERICAL;
6462 run_script(t, buf);
6465 /* empty the counter buffer */
6466 bzero(t->hint_buf, sizeof t->hint_buf);
6467 return (XT_CB_HANDLED);
6470 /* alphanumerical input */
6471 if (
6472 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6473 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6474 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6475 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6476 snprintf(s, sizeof s, "%c", e->keyval);
6477 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6478 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6479 t->hint_buf);
6480 anum:
6481 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6482 run_script(t, buf);
6484 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6485 t->hint_buf);
6486 t->hint_mode = XT_HINT_ALPHANUM;
6487 run_script(t, buf);
6489 /* empty the counter buffer */
6490 bzero(t->hint_num, sizeof t->hint_num);
6491 return (XT_CB_HANDLED);
6494 return (XT_CB_HANDLED);
6495 } else {
6496 /* prefix input*/
6497 snprintf(s, sizeof s, "%c", e->keyval);
6498 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6499 cmd_prefix = 10 * cmd_prefix + atoi(s);
6503 return (handle_keypress(t, e, 0));
6507 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6509 hide_oops(t);
6511 return (XT_CB_PASSTHROUGH);
6515 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6517 const gchar *c = gtk_entry_get_text(w);
6518 GdkColor color;
6519 int forward = TRUE;
6521 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6522 e->keyval, e->state, t);
6524 if (t == NULL) {
6525 show_oops_s("cmd_keyrelease_cb invalid parameters");
6526 return (XT_CB_PASSTHROUGH);
6529 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6530 e->keyval, e->state, t);
6532 if (c[0] == ':')
6533 goto done;
6534 if (strlen(c) == 1) {
6535 webkit_web_view_unmark_text_matches(t->wv);
6536 goto done;
6539 if (c[0] == '/')
6540 forward = TRUE;
6541 else if (c[0] == '?')
6542 forward = FALSE;
6543 else
6544 goto done;
6546 /* search */
6547 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6548 FALSE) {
6549 /* not found, mark red */
6550 gdk_color_parse(XT_COLOR_RED, &color);
6551 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6552 /* unmark and remove selection */
6553 webkit_web_view_unmark_text_matches(t->wv);
6554 /* my kingdom for a way to unselect text in webview */
6555 } else {
6556 /* found, highlight all */
6557 webkit_web_view_unmark_text_matches(t->wv);
6558 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6559 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6560 gdk_color_parse(XT_COLOR_WHITE, &color);
6561 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6563 done:
6564 return (XT_CB_PASSTHROUGH);
6567 gboolean
6568 match_uri(const gchar *uri, const gchar *key) {
6569 gchar *voffset;
6570 size_t len;
6571 gboolean match = FALSE;
6573 len = strlen(key);
6575 if (!strncmp(key, uri, len))
6576 match = TRUE;
6577 else {
6578 voffset = strstr(uri, "/") + 2;
6579 if (!strncmp(key, voffset, len))
6580 match = TRUE;
6581 else if (g_str_has_prefix(voffset, "www.")) {
6582 voffset = voffset + strlen("www.");
6583 if (!strncmp(key, voffset, len))
6584 match = TRUE;
6588 return (match);
6591 void
6592 cmd_getlist(int id, char *key)
6594 int i, dep, c = 0;
6595 struct history *h;
6597 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6598 RB_FOREACH_REVERSE(h, history_list, &hl)
6599 if (match_uri(h->uri, key)) {
6600 cmd_status.list[c] = (char *)h->uri;
6601 if (++c > 255)
6602 break;
6605 cmd_status.len = c;
6606 return;
6609 dep = (id == -1) ? 0 : cmds[id].level + 1;
6611 for (i = id + 1; i < LENGTH(cmds); i++) {
6612 if(cmds[i].level < dep)
6613 break;
6614 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6615 cmd_status.list[c++] = cmds[i].cmd;
6619 cmd_status.len = c;
6622 char *
6623 cmd_getnext(int dir)
6625 cmd_status.index += dir;
6627 if (cmd_status.index < 0)
6628 cmd_status.index = cmd_status.len - 1;
6629 else if (cmd_status.index >= cmd_status.len)
6630 cmd_status.index = 0;
6632 return cmd_status.list[cmd_status.index];
6636 cmd_tokenize(char *s, char *tokens[])
6638 int i = 0;
6639 char *tok, *last;
6640 size_t len = strlen(s);
6641 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6643 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6644 tokens[i] = tok;
6646 if (blank && i < 3)
6647 tokens[i++] = "";
6649 return (i);
6652 void
6653 cmd_complete(struct tab *t, char *str, int dir)
6655 GtkEntry *w = GTK_ENTRY(t->cmd);
6656 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6657 char *tok, *match, *s = g_strdup(str);
6658 char *tokens[3];
6659 char res[XT_MAX_URL_LENGTH + 32] = ":";
6660 char *sc = s;
6662 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6664 /* copy prefix*/
6665 for (i = 0; isdigit(s[i]); i++)
6666 res[i + 1] = s[i];
6668 for (; isspace(s[i]); i++)
6669 res[i + 1] = s[i];
6671 s += i;
6673 levels = cmd_tokenize(s, tokens);
6675 for (i = 0; i < levels - 1; i++) {
6676 tok = tokens[i];
6677 matchcount = 0;
6678 for (j = c; j < LENGTH(cmds); j++) {
6679 if (cmds[j].level < dep)
6680 break;
6681 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6682 matchcount++;
6683 c = j + 1;
6684 if (strlen(tok) == strlen(cmds[j].cmd)) {
6685 matchcount = 1;
6686 break;
6691 if (matchcount == 1) {
6692 strlcat(res, tok, sizeof res);
6693 strlcat(res, " ", sizeof res);
6694 dep++;
6695 } else {
6696 g_free(sc);
6697 return;
6700 parent = c - 1;
6703 if (cmd_status.index == -1)
6704 cmd_getlist(parent, tokens[i]);
6706 if (cmd_status.len > 0) {
6707 match = cmd_getnext(dir);
6708 strlcat(res, match, sizeof res);
6709 gtk_entry_set_text(w, res);
6710 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6713 g_free(sc);
6716 gboolean
6717 cmd_execute(struct tab *t, char *str)
6719 struct cmd *cmd = NULL;
6720 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6721 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6722 struct karg arg = {0, NULL, -1};
6723 int rv = XT_CB_PASSTHROUGH;
6725 sc = s;
6727 /* copy prefix*/
6728 for (j = 0; j<3 && isdigit(s[j]); j++)
6729 prefixstr[j]=s[j];
6731 prefixstr[j]='\0';
6733 s += j;
6734 while (isspace(s[0]))
6735 s++;
6737 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6738 prefix = atoi(prefixstr);
6739 else
6740 s = sc;
6742 for (tok = strtok_r(s, " ", &last); tok;
6743 tok = strtok_r(NULL, " ", &last)) {
6744 matchcount = 0;
6745 for (j = c; j < LENGTH(cmds); j++) {
6746 if (cmds[j].level < dep)
6747 break;
6748 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6749 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6750 matchcount++;
6751 c = j + 1;
6752 cmd = &cmds[j];
6753 if (len == strlen(cmds[j].cmd)) {
6754 matchcount = 1;
6755 break;
6759 if (matchcount == 1) {
6760 if (cmd->type > 0)
6761 goto execute_cmd;
6762 dep++;
6763 } else {
6764 show_oops(t, "Invalid command: %s", str);
6765 goto done;
6768 execute_cmd:
6769 arg.i = cmd->arg;
6771 if (prefix != -1)
6772 arg.p = prefix;
6773 else if (cmd_prefix > 0)
6774 arg.p = cmd_prefix;
6776 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6777 show_oops(t, "No prefix allowed: %s", str);
6778 goto done;
6780 if (cmd->type > 1)
6781 arg.s = last ? g_strdup(last) : g_strdup("");
6782 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6783 arg.p = atoi(arg.s);
6784 if (arg.p <= 0) {
6785 if (arg.s[0]=='0')
6786 show_oops(t, "Zero count");
6787 else
6788 show_oops(t, "Trailing characters");
6789 goto done;
6793 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6795 cmd->func(t, &arg);
6797 rv = XT_CB_HANDLED;
6798 done:
6799 if (j > 0)
6800 cmd_prefix = 0;
6801 g_free(sc);
6802 if (arg.s)
6803 g_free(arg.s);
6805 return (rv);
6809 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6811 if (t == NULL) {
6812 show_oops_s("entry_key_cb invalid parameters");
6813 return (XT_CB_PASSTHROUGH);
6816 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6817 e->keyval, e->state, t);
6819 hide_oops(t);
6821 if (e->keyval == GDK_Escape) {
6822 /* don't use focus_webview(t) because we want to type :cmds */
6823 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6826 return (handle_keypress(t, e, 1));
6830 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6832 int rv = XT_CB_HANDLED;
6833 const gchar *c = gtk_entry_get_text(w);
6835 if (t == NULL) {
6836 show_oops_s("cmd_keypress_cb parameters");
6837 return (XT_CB_PASSTHROUGH);
6840 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6841 e->keyval, e->state, t);
6843 /* sanity */
6844 if (c == NULL)
6845 e->keyval = GDK_Escape;
6846 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6847 e->keyval = GDK_Escape;
6849 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6850 cmd_status.index = -1;
6852 switch (e->keyval) {
6853 case GDK_Tab:
6854 if (c[0] == ':')
6855 cmd_complete(t, (char *)&c[1], 1);
6856 goto done;
6857 case GDK_ISO_Left_Tab:
6858 if (c[0] == ':')
6859 cmd_complete(t, (char *)&c[1], -1);
6861 goto done;
6862 case GDK_BackSpace:
6863 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6864 break;
6865 /* FALLTHROUGH */
6866 case GDK_Escape:
6867 hide_cmd(t);
6868 focus_webview(t);
6870 /* cancel search */
6871 if (c[0] == '/' || c[0] == '?')
6872 webkit_web_view_unmark_text_matches(t->wv);
6873 goto done;
6876 rv = XT_CB_PASSTHROUGH;
6877 done:
6878 return (rv);
6882 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6884 if (t == NULL) {
6885 show_oops_s("cmd_focusout_cb invalid parameters");
6886 return (XT_CB_PASSTHROUGH);
6888 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6890 hide_cmd(t);
6891 hide_oops(t);
6893 if (show_url == 0 || t->focus_wv)
6894 focus_webview(t);
6895 else
6896 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6898 return (XT_CB_PASSTHROUGH);
6901 void
6902 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6904 char *s;
6905 const gchar *c = gtk_entry_get_text(entry);
6907 if (t == NULL) {
6908 show_oops_s("cmd_activate_cb invalid parameters");
6909 return;
6912 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6914 hide_cmd(t);
6916 /* sanity */
6917 if (c == NULL)
6918 goto done;
6919 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6920 goto done;
6921 if (strlen(c) < 2)
6922 goto done;
6923 s = (char *)&c[1];
6925 if (c[0] == '/' || c[0] == '?') {
6926 if (t->search_text) {
6927 g_free(t->search_text);
6928 t->search_text = NULL;
6931 t->search_text = g_strdup(s);
6932 if (global_search)
6933 g_free(global_search);
6934 global_search = g_strdup(s);
6935 t->search_forward = c[0] == '/';
6937 goto done;
6940 cmd_execute(t, s);
6942 done:
6943 return;
6946 void
6947 backward_cb(GtkWidget *w, struct tab *t)
6949 struct karg a;
6951 if (t == NULL) {
6952 show_oops_s("backward_cb invalid parameters");
6953 return;
6956 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6958 a.i = XT_NAV_BACK;
6959 navaction(t, &a);
6962 void
6963 forward_cb(GtkWidget *w, struct tab *t)
6965 struct karg a;
6967 if (t == NULL) {
6968 show_oops_s("forward_cb invalid parameters");
6969 return;
6972 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6974 a.i = XT_NAV_FORWARD;
6975 navaction(t, &a);
6978 void
6979 home_cb(GtkWidget *w, struct tab *t)
6981 if (t == NULL) {
6982 show_oops_s("home_cb invalid parameters");
6983 return;
6986 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6988 load_uri(t, home);
6991 void
6992 stop_cb(GtkWidget *w, struct tab *t)
6994 WebKitWebFrame *frame;
6996 if (t == NULL) {
6997 show_oops_s("stop_cb invalid parameters");
6998 return;
7001 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7003 frame = webkit_web_view_get_main_frame(t->wv);
7004 if (frame == NULL) {
7005 show_oops(t, "stop_cb: no frame");
7006 return;
7009 webkit_web_frame_stop_loading(frame);
7010 abort_favicon_download(t);
7013 void
7014 setup_webkit(struct tab *t)
7016 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7017 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7018 FALSE, (char *)NULL);
7019 else
7020 warnx("webkit does not have \"enable-dns-prefetching\" property");
7021 g_object_set(G_OBJECT(t->settings),
7022 "user-agent", t->user_agent, (char *)NULL);
7023 g_object_set(G_OBJECT(t->settings),
7024 "enable-scripts", enable_scripts, (char *)NULL);
7025 g_object_set(G_OBJECT(t->settings),
7026 "enable-plugins", enable_plugins, (char *)NULL);
7027 g_object_set(G_OBJECT(t->settings),
7028 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7029 g_object_set(G_OBJECT(t->settings),
7030 "enable-html5-database", FALSE, (char *)NULL);
7031 g_object_set(G_OBJECT(t->settings),
7032 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7033 g_object_set(G_OBJECT(t->settings),
7034 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7035 g_object_set(G_OBJECT(t->settings),
7036 "spell_checking_languages", spell_check_languages, (char *)NULL);
7037 g_object_set(G_OBJECT(t->wv),
7038 "full-content-zoom", TRUE, (char *)NULL);
7039 adjustfont_webkit(t, XT_FONT_SET);
7041 webkit_web_view_set_settings(t->wv, t->settings);
7044 GtkWidget *
7045 create_browser(struct tab *t)
7047 GtkWidget *w;
7048 gchar *strval;
7050 if (t == NULL) {
7051 show_oops_s("create_browser invalid parameters");
7052 return (NULL);
7055 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7056 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7057 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7058 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7060 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7061 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7062 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7064 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7065 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7067 /* set defaults */
7068 t->settings = webkit_web_settings_new();
7070 if (user_agent == NULL) {
7071 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7072 (char *)NULL);
7073 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7074 g_free(strval);
7075 } else
7076 t->user_agent = g_strdup(user_agent);
7078 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7080 setup_webkit(t);
7082 return (w);
7085 GtkWidget *
7086 create_window(void)
7088 GtkWidget *w;
7090 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7091 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7092 gtk_widget_set_name(w, "xxxterm");
7093 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7094 g_signal_connect(G_OBJECT(w), "delete_event",
7095 G_CALLBACK (gtk_main_quit), NULL);
7097 return (w);
7100 GtkWidget *
7101 create_kiosk_toolbar(struct tab *t)
7103 GtkWidget *toolbar = NULL, *b;
7105 b = gtk_hbox_new(FALSE, 0);
7106 toolbar = b;
7107 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7109 /* backward button */
7110 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7111 gtk_widget_set_sensitive(t->backward, FALSE);
7112 g_signal_connect(G_OBJECT(t->backward), "clicked",
7113 G_CALLBACK(backward_cb), t);
7114 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7116 /* forward button */
7117 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7118 gtk_widget_set_sensitive(t->forward, FALSE);
7119 g_signal_connect(G_OBJECT(t->forward), "clicked",
7120 G_CALLBACK(forward_cb), t);
7121 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7123 /* home button */
7124 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7125 gtk_widget_set_sensitive(t->gohome, true);
7126 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7127 G_CALLBACK(home_cb), t);
7128 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7130 /* create widgets but don't use them */
7131 t->uri_entry = gtk_entry_new();
7132 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7133 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7134 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7136 return (toolbar);
7139 GtkWidget *
7140 create_toolbar(struct tab *t)
7142 GtkWidget *toolbar = NULL, *b, *eb1;
7144 b = gtk_hbox_new(FALSE, 0);
7145 toolbar = b;
7146 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7148 if (fancy_bar) {
7149 /* backward button */
7150 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7151 gtk_widget_set_sensitive(t->backward, FALSE);
7152 g_signal_connect(G_OBJECT(t->backward), "clicked",
7153 G_CALLBACK(backward_cb), t);
7154 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7156 /* forward button */
7157 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7158 gtk_widget_set_sensitive(t->forward, FALSE);
7159 g_signal_connect(G_OBJECT(t->forward), "clicked",
7160 G_CALLBACK(forward_cb), t);
7161 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7162 FALSE, 0);
7164 /* stop button */
7165 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7166 gtk_widget_set_sensitive(t->stop, FALSE);
7167 g_signal_connect(G_OBJECT(t->stop), "clicked",
7168 G_CALLBACK(stop_cb), t);
7169 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7170 FALSE, 0);
7172 /* JS button */
7173 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7174 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7175 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7176 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7177 G_CALLBACK(js_toggle_cb), t);
7178 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7181 t->uri_entry = gtk_entry_new();
7182 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7183 G_CALLBACK(activate_uri_entry_cb), t);
7184 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7185 G_CALLBACK(entry_key_cb), t);
7186 completion_add(t);
7187 eb1 = gtk_hbox_new(FALSE, 0);
7188 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7189 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7190 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7192 /* search entry */
7193 if (fancy_bar && search_string) {
7194 GtkWidget *eb2;
7195 t->search_entry = gtk_entry_new();
7196 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7197 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7198 G_CALLBACK(activate_search_entry_cb), t);
7199 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7200 G_CALLBACK(entry_key_cb), t);
7201 gtk_widget_set_size_request(t->search_entry, -1, -1);
7202 eb2 = gtk_hbox_new(FALSE, 0);
7203 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7204 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7206 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7208 return (toolbar);
7211 void
7212 recalc_tabs(void)
7214 struct tab *t;
7216 TAILQ_FOREACH(t, &tabs, entry)
7217 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7221 undo_close_tab_save(struct tab *t)
7223 int m, n;
7224 const gchar *uri;
7225 struct undo *u1, *u2;
7226 GList *items;
7227 WebKitWebHistoryItem *item;
7229 if ((uri = get_uri(t->wv)) == NULL)
7230 return (1);
7232 u1 = g_malloc0(sizeof(struct undo));
7233 u1->uri = g_strdup(uri);
7235 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7237 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7238 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7239 u1->back = n;
7241 /* forward history */
7242 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7244 while (items) {
7245 item = items->data;
7246 u1->history = g_list_prepend(u1->history,
7247 webkit_web_history_item_copy(item));
7248 items = g_list_next(items);
7251 /* current item */
7252 if (m) {
7253 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7254 u1->history = g_list_prepend(u1->history,
7255 webkit_web_history_item_copy(item));
7258 /* back history */
7259 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7261 while (items) {
7262 item = items->data;
7263 u1->history = g_list_prepend(u1->history,
7264 webkit_web_history_item_copy(item));
7265 items = g_list_next(items);
7268 TAILQ_INSERT_HEAD(&undos, u1, entry);
7270 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7271 u2 = TAILQ_LAST(&undos, undo_tailq);
7272 TAILQ_REMOVE(&undos, u2, entry);
7273 g_free(u2->uri);
7274 g_list_free(u2->history);
7275 g_free(u2);
7276 } else
7277 undo_count++;
7279 return (0);
7282 void
7283 delete_tab(struct tab *t)
7285 struct karg a;
7287 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7289 if (t == NULL)
7290 return;
7292 TAILQ_REMOVE(&tabs, t, entry);
7294 /* halt all webkit activity */
7295 abort_favicon_download(t);
7296 webkit_web_view_stop_loading(t->wv);
7297 undo_close_tab_save(t);
7299 if (browser_mode == XT_BM_KIOSK) {
7300 gtk_widget_destroy(t->uri_entry);
7301 gtk_widget_destroy(t->stop);
7302 gtk_widget_destroy(t->js_toggle);
7305 gtk_widget_destroy(t->vbox);
7306 g_free(t->user_agent);
7307 g_free(t->stylesheet);
7308 g_free(t);
7310 if (TAILQ_EMPTY(&tabs)) {
7311 if (browser_mode == XT_BM_KIOSK)
7312 create_new_tab(home, NULL, 1, -1);
7313 else
7314 create_new_tab(NULL, NULL, 1, -1);
7317 /* recreate session */
7318 if (session_autosave) {
7319 a.s = NULL;
7320 save_tabs(t, &a);
7324 void
7325 adjustfont_webkit(struct tab *t, int adjust)
7327 gfloat zoom;
7329 if (t == NULL) {
7330 show_oops_s("adjustfont_webkit invalid parameters");
7331 return;
7334 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7335 if (adjust == XT_FONT_SET) {
7336 t->font_size = default_font_size;
7337 zoom = default_zoom_level;
7338 t->font_size += adjust;
7339 g_object_set(G_OBJECT(t->settings), "default-font-size",
7340 t->font_size, (char *)NULL);
7341 g_object_get(G_OBJECT(t->settings), "default-font-size",
7342 &t->font_size, (char *)NULL);
7343 } else {
7344 t->font_size += adjust;
7345 zoom += adjust/25.0;
7346 if (zoom < 0.0) {
7347 zoom = 0.04;
7350 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7351 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7354 void
7355 append_tab(struct tab *t)
7357 if (t == NULL)
7358 return;
7360 TAILQ_INSERT_TAIL(&tabs, t, entry);
7361 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7364 struct tab *
7365 create_new_tab(char *title, struct undo *u, int focus, int position)
7367 struct tab *t;
7368 int load = 1, id;
7369 GtkWidget *b, *bb;
7370 WebKitWebHistoryItem *item;
7371 GList *items;
7372 GdkColor color;
7374 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7376 if (tabless && !TAILQ_EMPTY(&tabs)) {
7377 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7378 return (NULL);
7381 t = g_malloc0(sizeof *t);
7383 if (title == NULL) {
7384 title = "(untitled)";
7385 load = 0;
7388 t->vbox = gtk_vbox_new(FALSE, 0);
7390 /* label + button for tab */
7391 b = gtk_hbox_new(FALSE, 0);
7392 t->tab_content = b;
7394 #if GTK_CHECK_VERSION(2, 20, 0)
7395 t->spinner = gtk_spinner_new ();
7396 #endif
7397 t->label = gtk_label_new(title);
7398 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7399 gtk_widget_set_size_request(t->label, 100, 0);
7400 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7401 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7402 gtk_widget_set_size_request(b, 130, 0);
7404 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7405 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7406 #if GTK_CHECK_VERSION(2, 20, 0)
7407 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7408 #endif
7410 /* toolbar */
7411 if (browser_mode == XT_BM_KIOSK)
7412 t->toolbar = create_kiosk_toolbar(t);
7413 else
7414 t->toolbar = create_toolbar(t);
7416 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7418 /* browser */
7419 t->browser_win = create_browser(t);
7420 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7422 /* oops message for user feedback */
7423 t->oops = gtk_entry_new();
7424 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7425 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7426 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7427 gdk_color_parse(XT_COLOR_RED, &color);
7428 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7429 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7431 /* command entry */
7432 t->cmd = gtk_entry_new();
7433 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7434 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7435 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7436 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7438 /* status bar */
7439 t->statusbar = gtk_entry_new();
7440 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7441 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7442 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7443 gdk_color_parse(XT_COLOR_BLACK, &color);
7444 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7445 gdk_color_parse(XT_COLOR_WHITE, &color);
7446 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7447 gtk_widget_modify_font(GTK_WIDGET(t->statusbar), statusbar_font);
7448 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7450 /* xtp meaning is normal by default */
7451 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7453 /* set empty favicon */
7454 xt_icon_from_name(t, "text-html");
7456 /* and show it all */
7457 gtk_widget_show_all(b);
7458 gtk_widget_show_all(t->vbox);
7460 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7461 append_tab(t);
7462 else {
7463 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7464 if (id > gtk_notebook_get_n_pages(notebook))
7465 append_tab(t);
7466 else {
7467 TAILQ_INSERT_TAIL(&tabs, t, entry);
7468 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7469 recalc_tabs();
7473 #if GTK_CHECK_VERSION(2, 20, 0)
7474 /* turn spinner off if we are a new tab without uri */
7475 if (!load) {
7476 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7477 gtk_widget_hide(t->spinner);
7479 #endif
7480 /* make notebook tabs reorderable */
7481 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7483 g_object_connect(G_OBJECT(t->cmd),
7484 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7485 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7486 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7487 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7488 (char *)NULL);
7490 /* reuse wv_button_cb to hide oops */
7491 g_object_connect(G_OBJECT(t->oops),
7492 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7493 (char *)NULL);
7495 g_object_connect(G_OBJECT(t->wv),
7496 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7497 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7498 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7499 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7500 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7501 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7502 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7503 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7504 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7505 "signal::event", G_CALLBACK(webview_event_cb), t,
7506 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7507 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7508 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7509 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7510 (char *)NULL);
7511 g_signal_connect(t->wv,
7512 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7513 g_signal_connect(t->wv,
7514 "notify::title", G_CALLBACK(notify_title_cb), t);
7516 /* hijack the unused keys as if we were the browser */
7517 g_object_connect(G_OBJECT(t->toolbar),
7518 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7519 (char *)NULL);
7521 g_signal_connect(G_OBJECT(bb), "button_press_event",
7522 G_CALLBACK(tab_close_cb), t);
7524 /* hide stuff */
7525 hide_cmd(t);
7526 hide_oops(t);
7527 url_set_visibility();
7528 statusbar_set_visibility();
7530 if (focus) {
7531 gtk_notebook_set_current_page(notebook, t->tab_id);
7532 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7533 t->tab_id);
7536 if (load) {
7537 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7538 load_uri(t, title);
7539 } else {
7540 if (show_url == 1)
7541 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7542 else
7543 focus_webview(t);
7546 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7547 /* restore the tab's history */
7548 if (u && u->history) {
7549 items = u->history;
7550 while (items) {
7551 item = items->data;
7552 webkit_web_back_forward_list_add_item(t->bfl, item);
7553 items = g_list_next(items);
7556 item = g_list_nth_data(u->history, u->back);
7557 if (item)
7558 webkit_web_view_go_to_back_forward_item(t->wv, item);
7560 g_list_free(items);
7561 g_list_free(u->history);
7562 } else
7563 webkit_web_back_forward_list_clear(t->bfl);
7565 return (t);
7568 void
7569 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7570 gpointer *udata)
7572 struct tab *t;
7573 const gchar *uri;
7575 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7577 if (gtk_notebook_get_current_page(notebook) == -1)
7578 recalc_tabs();
7580 TAILQ_FOREACH(t, &tabs, entry) {
7581 if (t->tab_id == pn) {
7582 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7583 "%d\n", pn);
7585 uri = webkit_web_view_get_title(t->wv);
7586 if (uri == NULL)
7587 uri = XT_NAME;
7588 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7590 hide_cmd(t);
7591 hide_oops(t);
7593 if (t->focus_wv) {
7594 /* can't use focus_webview here */
7595 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7601 void
7602 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7603 gpointer *udata)
7605 recalc_tabs();
7608 void
7609 menuitem_response(struct tab *t)
7611 gtk_notebook_set_current_page(notebook, t->tab_id);
7614 gboolean
7615 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7617 GtkWidget *menu, *menu_items;
7618 GdkEventButton *bevent;
7619 const gchar *uri;
7620 struct tab *ti;
7622 if (event->type == GDK_BUTTON_PRESS) {
7623 bevent = (GdkEventButton *) event;
7624 menu = gtk_menu_new();
7626 TAILQ_FOREACH(ti, &tabs, entry) {
7627 if ((uri = get_uri(ti->wv)) == NULL)
7628 /* XXX make sure there is something to print */
7629 /* XXX add gui pages in here to look purdy */
7630 uri = "(untitled)";
7631 menu_items = gtk_menu_item_new_with_label(uri);
7632 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7633 gtk_widget_show(menu_items);
7635 g_signal_connect_swapped((menu_items),
7636 "activate", G_CALLBACK(menuitem_response),
7637 (gpointer)ti);
7640 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7641 bevent->button, bevent->time);
7643 /* unref object so it'll free itself when popped down */
7644 #if !GTK_CHECK_VERSION(3, 0, 0)
7645 /* XXX does not need unref with gtk+3? */
7646 g_object_ref_sink(menu);
7647 g_object_unref(menu);
7648 #endif
7650 return (TRUE /* eat event */);
7653 return (FALSE /* propagate */);
7657 icon_size_map(int icon_size)
7659 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7660 icon_size > GTK_ICON_SIZE_DIALOG)
7661 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7663 return (icon_size);
7666 GtkWidget *
7667 create_button(char *name, char *stockid, int size)
7669 GtkWidget *button, *image;
7670 gchar *rcstring;
7671 int gtk_icon_size;
7673 rcstring = g_strdup_printf(
7674 "style \"%s-style\"\n"
7675 "{\n"
7676 " GtkWidget::focus-padding = 0\n"
7677 " GtkWidget::focus-line-width = 0\n"
7678 " xthickness = 0\n"
7679 " ythickness = 0\n"
7680 "}\n"
7681 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7682 gtk_rc_parse_string(rcstring);
7683 g_free(rcstring);
7684 button = gtk_button_new();
7685 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7686 gtk_icon_size = icon_size_map(size ? size : icon_size);
7688 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7689 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7690 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7691 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7692 gtk_widget_set_name(button, name);
7693 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7695 return (button);
7698 void
7699 button_set_stockid(GtkWidget *button, char *stockid)
7701 GtkWidget *image;
7703 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7704 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7705 gtk_button_set_image(GTK_BUTTON(button), image);
7708 void
7709 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
7711 GtkClipboard *clipboard;
7712 gchar *p = NULL, *s = NULL;
7715 * This code is very aggressive!
7716 * It basically ensures that the primary and regular clipboard are
7717 * always set the same. This obviously messes with standard X protocol
7718 * but those clowns should have come up with something better.
7721 /* XXX make this setting? */
7722 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7723 p = gtk_clipboard_wait_for_text(primary);
7724 if (p == NULL) {
7725 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
7726 p = gtk_clipboard_wait_for_text(clipboard);
7727 if (p)
7728 gtk_clipboard_set_text(primary, p, -1);
7729 } else {
7730 DNPRINTF(XT_D_CLIP, "primary got selection\n");
7731 s = gtk_clipboard_wait_for_text(clipboard);
7732 if (s) {
7734 * if s and p are the same the string was set by
7735 * clipb_clipboard_cb so do nothing in that case
7736 * to prevent endless loop
7738 if (!strcmp(s, p))
7739 goto done;
7741 gtk_clipboard_set_text(clipboard, p, -1);
7743 done:
7744 if (p)
7745 g_free(p);
7746 if (s)
7747 g_free(s);
7750 void
7751 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
7753 GtkClipboard *primary;
7754 gchar *p = NULL, *s = NULL;
7756 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
7758 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7759 p = gtk_clipboard_wait_for_text(clipboard);
7760 if (p) {
7761 s = gtk_clipboard_wait_for_text(primary);
7762 if (s) {
7764 * if s and p are the same the string was set by
7765 * clipb_primary_cb so do nothing in that case
7766 * to prevent endless loop and deselection of text
7768 if (!strcmp(s, p))
7769 goto done;
7771 gtk_clipboard_set_text(primary, p, -1);
7773 done:
7774 if (p)
7775 g_free(p);
7776 if (s)
7777 g_free(s);
7780 void
7781 create_canvas(void)
7783 GtkWidget *vbox;
7784 GList *l = NULL;
7785 GdkPixbuf *pb;
7786 char file[PATH_MAX];
7787 int i;
7789 vbox = gtk_vbox_new(FALSE, 0);
7790 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7791 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7792 #if !GTK_CHECK_VERSION(3, 0, 0)
7793 /* XXX seems to be needed with gtk+2 */
7794 gtk_notebook_set_tab_hborder(notebook, 0);
7795 gtk_notebook_set_tab_vborder(notebook, 0);
7796 #endif
7797 gtk_notebook_set_scrollable(notebook, TRUE);
7798 notebook_tab_set_visibility(notebook);
7799 gtk_notebook_set_show_border(notebook, FALSE);
7800 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7802 abtn = gtk_button_new();
7803 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7804 gtk_widget_set_size_request(arrow, -1, -1);
7805 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7806 gtk_widget_set_size_request(abtn, -1, 20);
7808 #if GTK_CHECK_VERSION(2, 20, 0)
7809 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7810 #endif
7811 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7812 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7813 gtk_widget_set_size_request(vbox, -1, -1);
7815 g_object_connect(G_OBJECT(notebook),
7816 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7817 (char *)NULL);
7818 g_object_connect(G_OBJECT(notebook),
7819 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
7820 (char *)NULL);
7821 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7822 G_CALLBACK(arrow_cb), NULL);
7824 main_window = create_window();
7825 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7826 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7828 /* icons */
7829 for (i = 0; i < LENGTH(icons); i++) {
7830 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7831 pb = gdk_pixbuf_new_from_file(file, NULL);
7832 l = g_list_append(l, pb);
7834 gtk_window_set_default_icon_list(l);
7836 /* clipboard */
7837 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
7838 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
7839 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
7840 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
7842 gtk_widget_show_all(abtn);
7843 gtk_widget_show_all(main_window);
7846 void
7847 set_hook(void **hook, char *name)
7849 if (hook == NULL)
7850 errx(1, "set_hook");
7852 if (*hook == NULL) {
7853 *hook = dlsym(RTLD_NEXT, name);
7854 if (*hook == NULL)
7855 errx(1, "can't hook %s", name);
7859 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7860 gboolean
7861 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7863 g_return_val_if_fail(cookie1, FALSE);
7864 g_return_val_if_fail(cookie2, FALSE);
7866 return (!strcmp (cookie1->name, cookie2->name) &&
7867 !strcmp (cookie1->value, cookie2->value) &&
7868 !strcmp (cookie1->path, cookie2->path) &&
7869 !strcmp (cookie1->domain, cookie2->domain));
7872 void
7873 transfer_cookies(void)
7875 GSList *cf;
7876 SoupCookie *sc, *pc;
7878 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7880 for (;cf; cf = cf->next) {
7881 pc = cf->data;
7882 sc = soup_cookie_copy(pc);
7883 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7886 soup_cookies_free(cf);
7889 void
7890 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7892 GSList *cf;
7893 SoupCookie *ci;
7895 print_cookie("soup_cookie_jar_delete_cookie", c);
7897 if (cookies_enabled == 0)
7898 return;
7900 if (jar == NULL || c == NULL)
7901 return;
7903 /* find and remove from persistent jar */
7904 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7906 for (;cf; cf = cf->next) {
7907 ci = cf->data;
7908 if (soup_cookie_equal(ci, c)) {
7909 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7910 break;
7914 soup_cookies_free(cf);
7916 /* delete from session jar */
7917 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7920 void
7921 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7923 struct domain *d = NULL;
7924 SoupCookie *c;
7925 FILE *r_cookie_f;
7927 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7928 jar, p_cookiejar, s_cookiejar);
7930 if (cookies_enabled == 0)
7931 return;
7933 /* see if we are up and running */
7934 if (p_cookiejar == NULL) {
7935 _soup_cookie_jar_add_cookie(jar, cookie);
7936 return;
7938 /* disallow p_cookiejar adds, shouldn't happen */
7939 if (jar == p_cookiejar)
7940 return;
7942 /* sanity */
7943 if (jar == NULL || cookie == NULL)
7944 return;
7946 if (enable_cookie_whitelist &&
7947 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7948 blocked_cookies++;
7949 DNPRINTF(XT_D_COOKIE,
7950 "soup_cookie_jar_add_cookie: reject %s\n",
7951 cookie->domain);
7952 if (save_rejected_cookies) {
7953 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7954 show_oops_s("can't open reject cookie file");
7955 return;
7957 fseek(r_cookie_f, 0, SEEK_END);
7958 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7959 cookie->http_only ? "#HttpOnly_" : "",
7960 cookie->domain,
7961 *cookie->domain == '.' ? "TRUE" : "FALSE",
7962 cookie->path,
7963 cookie->secure ? "TRUE" : "FALSE",
7964 cookie->expires ?
7965 (gulong)soup_date_to_time_t(cookie->expires) :
7967 cookie->name,
7968 cookie->value);
7969 fflush(r_cookie_f);
7970 fclose(r_cookie_f);
7972 if (!allow_volatile_cookies)
7973 return;
7976 if (cookie->expires == NULL && session_timeout) {
7977 soup_cookie_set_expires(cookie,
7978 soup_date_new_from_now(session_timeout));
7979 print_cookie("modified add cookie", cookie);
7982 /* see if we are white listed for persistence */
7983 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7984 /* add to persistent jar */
7985 c = soup_cookie_copy(cookie);
7986 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7987 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7990 /* add to session jar */
7991 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7992 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7995 void
7996 setup_cookies(void)
7998 char file[PATH_MAX];
8000 set_hook((void *)&_soup_cookie_jar_add_cookie,
8001 "soup_cookie_jar_add_cookie");
8002 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8003 "soup_cookie_jar_delete_cookie");
8005 if (cookies_enabled == 0)
8006 return;
8009 * the following code is intricate due to overriding several libsoup
8010 * functions.
8011 * do not alter order of these operations.
8014 /* rejected cookies */
8015 if (save_rejected_cookies)
8016 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8018 /* persistent cookies */
8019 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8020 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8022 /* session cookies */
8023 s_cookiejar = soup_cookie_jar_new();
8024 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8025 cookie_policy, (void *)NULL);
8026 transfer_cookies();
8028 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8031 void
8032 setup_proxy(char *uri)
8034 if (proxy_uri) {
8035 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8036 soup_uri_free(proxy_uri);
8037 proxy_uri = NULL;
8039 if (http_proxy) {
8040 if (http_proxy != uri) {
8041 g_free(http_proxy);
8042 http_proxy = NULL;
8046 if (uri) {
8047 http_proxy = g_strdup(uri);
8048 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8049 proxy_uri = soup_uri_new(http_proxy);
8050 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8055 send_cmd_to_socket(char *cmd)
8057 int s, len, rv = 1;
8058 struct sockaddr_un sa;
8060 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8061 warnx("%s: socket", __func__);
8062 return (rv);
8065 sa.sun_family = AF_UNIX;
8066 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8067 work_dir, XT_SOCKET_FILE);
8068 len = SUN_LEN(&sa);
8070 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8071 warnx("%s: connect", __func__);
8072 goto done;
8075 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8076 warnx("%s: send", __func__);
8077 goto done;
8080 rv = 0;
8081 done:
8082 close(s);
8083 return (rv);
8086 gboolean
8087 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8089 int s, n;
8090 char str[XT_MAX_URL_LENGTH];
8091 socklen_t t = sizeof(struct sockaddr_un);
8092 struct sockaddr_un sa;
8093 struct passwd *p;
8094 uid_t uid;
8095 gid_t gid;
8096 struct tab *tt;
8097 gint fd = g_io_channel_unix_get_fd(source);
8099 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8100 warn("accept");
8101 return (FALSE);
8104 if (getpeereid(s, &uid, &gid) == -1) {
8105 warn("getpeereid");
8106 return (FALSE);
8108 if (uid != getuid() || gid != getgid()) {
8109 warnx("unauthorized user");
8110 return (FALSE);
8113 p = getpwuid(uid);
8114 if (p == NULL) {
8115 warnx("not a valid user");
8116 return (FALSE);
8119 n = recv(s, str, sizeof(str), 0);
8120 if (n <= 0)
8121 return (FALSE);
8123 tt = TAILQ_LAST(&tabs, tab_list);
8124 cmd_execute(tt, str);
8125 return (TRUE);
8129 is_running(void)
8131 int s, len, rv = 1;
8132 struct sockaddr_un sa;
8134 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8135 warn("is_running: socket");
8136 return (-1);
8139 sa.sun_family = AF_UNIX;
8140 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8141 work_dir, XT_SOCKET_FILE);
8142 len = SUN_LEN(&sa);
8144 /* connect to see if there is a listener */
8145 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8146 rv = 0; /* not running */
8147 else
8148 rv = 1; /* already running */
8150 close(s);
8152 return (rv);
8156 build_socket(void)
8158 int s, len;
8159 struct sockaddr_un sa;
8161 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8162 warn("build_socket: socket");
8163 return (-1);
8166 sa.sun_family = AF_UNIX;
8167 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8168 work_dir, XT_SOCKET_FILE);
8169 len = SUN_LEN(&sa);
8171 /* connect to see if there is a listener */
8172 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8173 /* no listener so we will */
8174 unlink(sa.sun_path);
8176 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8177 warn("build_socket: bind");
8178 goto done;
8181 if (listen(s, 1) == -1) {
8182 warn("build_socket: listen");
8183 goto done;
8186 return (s);
8189 done:
8190 close(s);
8191 return (-1);
8194 gboolean
8195 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8196 GtkTreeIter *iter, struct tab *t)
8198 gchar *value;
8200 gtk_tree_model_get(model, iter, 0, &value, -1);
8201 load_uri(t, value);
8202 g_free(value);
8204 return (FALSE);
8207 gboolean
8208 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8209 GtkTreeIter *iter, struct tab *t)
8211 gchar *value;
8213 gtk_tree_model_get(model, iter, 0, &value, -1);
8214 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8215 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8216 g_free(value);
8218 return (TRUE);
8221 void
8222 completion_add_uri(const gchar *uri)
8224 GtkTreeIter iter;
8226 /* add uri to list_store */
8227 gtk_list_store_append(completion_model, &iter);
8228 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8231 gboolean
8232 completion_match(GtkEntryCompletion *completion, const gchar *key,
8233 GtkTreeIter *iter, gpointer user_data)
8235 gchar *value;
8236 gboolean match = FALSE;
8238 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8239 -1);
8241 if (value == NULL)
8242 return FALSE;
8244 match = match_uri(value, key);
8246 g_free(value);
8247 return (match);
8250 void
8251 completion_add(struct tab *t)
8253 /* enable completion for tab */
8254 t->completion = gtk_entry_completion_new();
8255 gtk_entry_completion_set_text_column(t->completion, 0);
8256 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8257 gtk_entry_completion_set_model(t->completion,
8258 GTK_TREE_MODEL(completion_model));
8259 gtk_entry_completion_set_match_func(t->completion, completion_match,
8260 NULL, NULL);
8261 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8262 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8263 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8264 G_CALLBACK(completion_select_cb), t);
8265 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8266 G_CALLBACK(completion_hover_cb), t);
8269 void
8270 xxx_dir(char *dir)
8272 struct stat sb;
8274 if (stat(dir, &sb)) {
8275 if (mkdir(dir, S_IRWXU) == -1)
8276 err(1, "mkdir %s", dir);
8277 if (stat(dir, &sb))
8278 err(1, "stat %s", dir);
8280 if (S_ISDIR(sb.st_mode) == 0)
8281 errx(1, "%s not a dir", dir);
8282 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8283 warnx("fixing invalid permissions on %s", dir);
8284 if (chmod(dir, S_IRWXU) == -1)
8285 err(1, "chmod %s", dir);
8289 void
8290 usage(void)
8292 fprintf(stderr,
8293 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8294 exit(0);
8299 main(int argc, char *argv[])
8301 struct stat sb;
8302 int c, s, optn = 0, opte = 0, focus = 1;
8303 char conf[PATH_MAX] = { '\0' };
8304 char file[PATH_MAX];
8305 char *env_proxy = NULL;
8306 FILE *f = NULL;
8307 struct karg a;
8308 struct sigaction sact;
8309 GIOChannel *channel;
8310 struct rlimit rlp;
8312 start_argv = argv;
8314 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8316 /* fiddle with ulimits */
8317 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8318 warn("getrlimit");
8319 else {
8320 /* just use them all */
8321 rlp.rlim_cur = rlp.rlim_max;
8322 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8323 warn("setrlimit");
8324 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8325 warn("getrlimit");
8326 else if (rlp.rlim_cur <= 256)
8327 warnx("%s requires at least 256 file descriptors",
8328 __progname);
8331 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8332 switch (c) {
8333 case 'S':
8334 show_url = 0;
8335 break;
8336 case 'T':
8337 show_tabs = 0;
8338 break;
8339 case 'V':
8340 errx(0 , "Version: %s", version);
8341 break;
8342 case 'f':
8343 strlcpy(conf, optarg, sizeof(conf));
8344 break;
8345 case 's':
8346 strlcpy(named_session, optarg, sizeof(named_session));
8347 break;
8348 case 't':
8349 tabless = 1;
8350 break;
8351 case 'n':
8352 optn = 1;
8353 break;
8354 case 'e':
8355 opte = 1;
8356 break;
8357 default:
8358 usage();
8359 /* NOTREACHED */
8362 argc -= optind;
8363 argv += optind;
8365 RB_INIT(&hl);
8366 RB_INIT(&js_wl);
8367 RB_INIT(&downloads);
8369 TAILQ_INIT(&tabs);
8370 TAILQ_INIT(&mtl);
8371 TAILQ_INIT(&aliases);
8372 TAILQ_INIT(&undos);
8373 TAILQ_INIT(&kbl);
8375 init_keybindings();
8377 gnutls_global_init();
8379 /* generate session keys for xtp pages */
8380 generate_xtp_session_key(&dl_session_key);
8381 generate_xtp_session_key(&hl_session_key);
8382 generate_xtp_session_key(&cl_session_key);
8383 generate_xtp_session_key(&fl_session_key);
8385 /* prepare gtk */
8386 gtk_init(&argc, &argv);
8387 if (!g_thread_supported())
8388 g_thread_init(NULL);
8390 /* signals */
8391 bzero(&sact, sizeof(sact));
8392 sigemptyset(&sact.sa_mask);
8393 sact.sa_handler = sigchild;
8394 sact.sa_flags = SA_NOCLDSTOP;
8395 sigaction(SIGCHLD, &sact, NULL);
8397 /* set download dir */
8398 pwd = getpwuid(getuid());
8399 if (pwd == NULL)
8400 errx(1, "invalid user %d", getuid());
8401 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8403 /* set default string settings */
8404 home = g_strdup("https://www.cyphertite.com");
8405 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8406 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8407 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
8408 cmd_font_name = g_strdup("monospace normal 9");
8409 statusbar_font_name = g_strdup("monospace normal 9");
8412 /* read config file */
8413 if (strlen(conf) == 0)
8414 snprintf(conf, sizeof conf, "%s/.%s",
8415 pwd->pw_dir, XT_CONF_FILE);
8416 config_parse(conf, 0);
8418 /* init fonts */
8419 cmd_font = pango_font_description_from_string(cmd_font_name);
8420 statusbar_font = pango_font_description_from_string(statusbar_font_name);
8422 /* working directory */
8423 if (strlen(work_dir) == 0)
8424 snprintf(work_dir, sizeof work_dir, "%s/%s",
8425 pwd->pw_dir, XT_DIR);
8426 xxx_dir(work_dir);
8428 /* icon cache dir */
8429 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8430 xxx_dir(cache_dir);
8432 /* certs dir */
8433 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8434 xxx_dir(certs_dir);
8436 /* sessions dir */
8437 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8438 work_dir, XT_SESSIONS_DIR);
8439 xxx_dir(sessions_dir);
8441 /* runtime settings that can override config file */
8442 if (runtime_settings[0] != '\0')
8443 config_parse(runtime_settings, 1);
8445 /* download dir */
8446 if (!strcmp(download_dir, pwd->pw_dir))
8447 strlcat(download_dir, "/downloads", sizeof download_dir);
8448 xxx_dir(download_dir);
8450 /* favorites file */
8451 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8452 if (stat(file, &sb)) {
8453 warnx("favorites file doesn't exist, creating it");
8454 if ((f = fopen(file, "w")) == NULL)
8455 err(1, "favorites");
8456 fclose(f);
8459 /* cookies */
8460 session = webkit_get_default_session();
8461 setup_cookies();
8463 /* certs */
8464 if (ssl_ca_file) {
8465 if (stat(ssl_ca_file, &sb)) {
8466 warnx("no CA file: %s", ssl_ca_file);
8467 g_free(ssl_ca_file);
8468 ssl_ca_file = NULL;
8469 } else
8470 g_object_set(session,
8471 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8472 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8473 (void *)NULL);
8476 /* proxy */
8477 env_proxy = getenv("http_proxy");
8478 if (env_proxy)
8479 setup_proxy(env_proxy);
8480 else
8481 setup_proxy(http_proxy);
8483 if (opte) {
8484 send_cmd_to_socket(argv[0]);
8485 exit(0);
8488 /* set some connection parameters */
8489 /* XXX webkit 1.4.X overwrites these values! */
8490 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8491 g_object_set(session, "max-conns-per-host", max_host_connections,
8492 (char *)NULL);
8494 /* see if there is already an xxxterm running */
8495 if (single_instance && is_running()) {
8496 optn = 1;
8497 warnx("already running");
8500 char *cmd = NULL;
8501 if (optn) {
8502 while (argc) {
8503 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8504 send_cmd_to_socket(cmd);
8505 if (cmd)
8506 g_free(cmd);
8508 argc--;
8509 argv++;
8511 exit(0);
8514 /* uri completion */
8515 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8517 /* go graphical */
8518 create_canvas();
8520 if (save_global_history)
8521 restore_global_history();
8523 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8524 restore_saved_tabs();
8525 else {
8526 a.s = named_session;
8527 a.i = XT_SES_DONOTHING;
8528 open_tabs(NULL, &a);
8531 while (argc) {
8532 create_new_tab(argv[0], NULL, focus, -1);
8533 focus = 0;
8535 argc--;
8536 argv++;
8539 if (TAILQ_EMPTY(&tabs))
8540 create_new_tab(home, NULL, 1, -1);
8542 if (enable_socket)
8543 if ((s = build_socket()) != -1) {
8544 channel = g_io_channel_unix_new(s);
8545 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8548 gtk_main();
8550 gnutls_global_deinit();
8552 return (0);