kill one more focus bug where one cant type while page is loading.
[xxxterm.git] / xxxterm.c
blob4a6a8377535566324e69a179d13c9a7a62650bdf
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>
62 #include <gtk/gtk.h>
63 #include <gdk/gdkkeysyms.h>
65 #if GTK_CHECK_VERSION(3,0,0)
66 /* we still use GDK_* instead of GDK_KEY_* */
67 #include <gdk/gdkkeysyms-compat.h>
68 #endif
70 #include <webkit/webkit.h>
71 #include <libsoup/soup.h>
72 #include <gnutls/gnutls.h>
73 #include <JavaScriptCore/JavaScript.h>
74 #include <gnutls/x509.h>
76 #include "javascript.h"
79 javascript.h borrowed from vimprobable2 under the following license:
81 Copyright (c) 2009 Leon Winter
82 Copyright (c) 2009 Hannes Schueller
83 Copyright (c) 2009 Matto Fransen
85 Permission is hereby granted, free of charge, to any person obtaining a copy
86 of this software and associated documentation files (the "Software"), to deal
87 in the Software without restriction, including without limitation the rights
88 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
89 copies of the Software, and to permit persons to whom the Software is
90 furnished to do so, subject to the following conditions:
92 The above copyright notice and this permission notice shall be included in
93 all copies or substantial portions of the Software.
95 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
96 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
97 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
98 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
99 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
100 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
101 THE SOFTWARE.
104 static char *version = "$xxxterm$";
106 /* hooked functions */
107 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
108 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
109 SoupCookie *);
111 /*#define XT_DEBUG*/
112 #ifdef XT_DEBUG
113 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
114 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
115 #define XT_D_MOVE 0x0001
116 #define XT_D_KEY 0x0002
117 #define XT_D_TAB 0x0004
118 #define XT_D_URL 0x0008
119 #define XT_D_CMD 0x0010
120 #define XT_D_NAV 0x0020
121 #define XT_D_DOWNLOAD 0x0040
122 #define XT_D_CONFIG 0x0080
123 #define XT_D_JS 0x0100
124 #define XT_D_FAVORITE 0x0200
125 #define XT_D_PRINTING 0x0400
126 #define XT_D_COOKIE 0x0800
127 #define XT_D_KEYBINDING 0x1000
128 #define XT_D_CLIP 0x2000
129 u_int32_t swm_debug = 0
130 | XT_D_MOVE
131 | XT_D_KEY
132 | XT_D_TAB
133 | XT_D_URL
134 | XT_D_CMD
135 | XT_D_NAV
136 | XT_D_DOWNLOAD
137 | XT_D_CONFIG
138 | XT_D_JS
139 | XT_D_FAVORITE
140 | XT_D_PRINTING
141 | XT_D_COOKIE
142 | XT_D_KEYBINDING
143 | XT_D_CLIP
145 #else
146 #define DPRINTF(x...)
147 #define DNPRINTF(n,x...)
148 #endif
150 #define LENGTH(x) (sizeof x / sizeof x[0])
151 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
152 ~(GDK_BUTTON1_MASK) & \
153 ~(GDK_BUTTON2_MASK) & \
154 ~(GDK_BUTTON3_MASK) & \
155 ~(GDK_BUTTON4_MASK) & \
156 ~(GDK_BUTTON5_MASK))
158 char *icons[] = {
159 "xxxtermicon16.png",
160 "xxxtermicon32.png",
161 "xxxtermicon48.png",
162 "xxxtermicon64.png",
163 "xxxtermicon128.png"
166 struct tab {
167 TAILQ_ENTRY(tab) entry;
168 GtkWidget *vbox;
169 GtkWidget *tab_content;
170 GtkWidget *label;
171 GtkWidget *spinner;
172 GtkWidget *uri_entry;
173 GtkWidget *search_entry;
174 GtkWidget *toolbar;
175 GtkWidget *browser_win;
176 GtkWidget *statusbar;
177 GtkWidget *cmd;
178 GtkWidget *oops;
179 GtkWidget *backward;
180 GtkWidget *forward;
181 GtkWidget *stop;
182 GtkWidget *gohome;
183 GtkWidget *js_toggle;
184 GtkEntryCompletion *completion;
185 guint tab_id;
186 WebKitWebView *wv;
188 WebKitWebHistoryItem *item;
189 WebKitWebBackForwardList *bfl;
191 /* favicon */
192 WebKitNetworkRequest *icon_request;
193 WebKitDownload *icon_download;
194 GdkPixbuf *icon_pixbuf;
195 gchar *icon_dest_uri;
197 /* adjustments for browser */
198 GtkScrollbar *sb_h;
199 GtkScrollbar *sb_v;
200 GtkAdjustment *adjust_h;
201 GtkAdjustment *adjust_v;
203 /* flags */
204 int focus_wv;
205 int ctrl_click;
206 gchar *status;
207 int xtp_meaning; /* identifies dls/favorites */
209 /* hints */
210 int hints_on;
211 int hint_mode;
212 #define XT_HINT_NONE (0)
213 #define XT_HINT_NUMERICAL (1)
214 #define XT_HINT_ALPHANUM (2)
215 char hint_buf[128];
216 char hint_num[128];
218 /* custom stylesheet */
219 int styled;
220 char *stylesheet;
222 /* search */
223 char *search_text;
224 int search_forward;
226 /* settings */
227 WebKitWebSettings *settings;
228 int font_size;
229 gchar *user_agent;
231 TAILQ_HEAD(tab_list, tab);
233 struct history {
234 RB_ENTRY(history) entry;
235 const gchar *uri;
236 const gchar *title;
238 RB_HEAD(history_list, history);
240 struct download {
241 RB_ENTRY(download) entry;
242 int id;
243 WebKitDownload *download;
244 struct tab *tab;
246 RB_HEAD(download_list, download);
248 struct domain {
249 RB_ENTRY(domain) entry;
250 gchar *d;
251 int handy; /* app use */
253 RB_HEAD(domain_list, domain);
255 struct undo {
256 TAILQ_ENTRY(undo) entry;
257 gchar *uri;
258 GList *history;
259 int back; /* Keeps track of how many back
260 * history items there are. */
262 TAILQ_HEAD(undo_tailq, undo);
264 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
265 int next_download_id = 1;
267 struct karg {
268 int i;
269 char *s;
270 int p;
273 /* defines */
274 #define XT_NAME ("XXXTerm")
275 #define XT_DIR (".xxxterm")
276 #define XT_CACHE_DIR ("cache")
277 #define XT_CERT_DIR ("certs/")
278 #define XT_SESSIONS_DIR ("sessions/")
279 #define XT_CONF_FILE ("xxxterm.conf")
280 #define XT_FAVS_FILE ("favorites")
281 #define XT_SAVED_TABS_FILE ("main_session")
282 #define XT_RESTART_TABS_FILE ("restart_tabs")
283 #define XT_SOCKET_FILE ("socket")
284 #define XT_HISTORY_FILE ("history")
285 #define XT_REJECT_FILE ("rejected.txt")
286 #define XT_COOKIE_FILE ("cookies.txt")
287 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
288 #define XT_CB_HANDLED (TRUE)
289 #define XT_CB_PASSTHROUGH (FALSE)
290 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
291 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
292 #define XT_DLMAN_REFRESH "10"
293 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
294 "td{overflow: hidden;" \
295 " padding: 2px 2px 2px 2px;" \
296 " border: 1px solid black;" \
297 " vertical-align:top;" \
298 " word-wrap: break-word}\n" \
299 "tr:hover{background: #ffff99}\n" \
300 "th{background-color: #cccccc;" \
301 " border: 1px solid black}\n" \
302 "table{width: 100%%;" \
303 " border: 1px black solid;" \
304 " border-collapse:collapse}\n" \
305 ".progress-outer{" \
306 "border: 1px solid black;" \
307 " height: 8px;" \
308 " width: 90%%}\n" \
309 ".progress-inner{float: left;" \
310 " height: 8px;" \
311 " background: green}\n" \
312 ".dlstatus{font-size: small;" \
313 " text-align: center}\n" \
314 "</style>\n"
315 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
316 #define XT_MAX_UNDO_CLOSE_TAB (32)
317 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
318 #define XT_PRINT_EXTRA_MARGIN 10
320 /* colors */
321 #define XT_COLOR_RED "#cc0000"
322 #define XT_COLOR_YELLOW "#ffff66"
323 #define XT_COLOR_BLUE "lightblue"
324 #define XT_COLOR_GREEN "#99ff66"
325 #define XT_COLOR_WHITE "white"
326 #define XT_COLOR_BLACK "black"
329 * xxxterm "protocol" (xtp)
330 * We use this for managing stuff like downloads and favorites. They
331 * make magical HTML pages in memory which have xxxt:// links in order
332 * to communicate with xxxterm's internals. These links take the format:
333 * xxxt://class/session_key/action/arg
335 * Don't begin xtp class/actions as 0. atoi returns that on error.
337 * Typically we have not put addition of items in this framework, as
338 * adding items is either done via an ex-command or via a keybinding instead.
341 #define XT_XTP_STR "xxxt://"
343 /* XTP classes (xxxt://<class>) */
344 #define XT_XTP_INVALID 0 /* invalid */
345 #define XT_XTP_DL 1 /* downloads */
346 #define XT_XTP_HL 2 /* history */
347 #define XT_XTP_CL 3 /* cookies */
348 #define XT_XTP_FL 4 /* favorites */
350 /* XTP download actions */
351 #define XT_XTP_DL_LIST 1
352 #define XT_XTP_DL_CANCEL 2
353 #define XT_XTP_DL_REMOVE 3
355 /* XTP history actions */
356 #define XT_XTP_HL_LIST 1
357 #define XT_XTP_HL_REMOVE 2
359 /* XTP cookie actions */
360 #define XT_XTP_CL_LIST 1
361 #define XT_XTP_CL_REMOVE 2
363 /* XTP cookie actions */
364 #define XT_XTP_FL_LIST 1
365 #define XT_XTP_FL_REMOVE 2
367 /* xtp tab meanings - identifies which tabs have xtp pages in */
368 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
369 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
370 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
371 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
372 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
374 /* actions */
375 #define XT_MOVE_INVALID (0)
376 #define XT_MOVE_DOWN (1)
377 #define XT_MOVE_UP (2)
378 #define XT_MOVE_BOTTOM (3)
379 #define XT_MOVE_TOP (4)
380 #define XT_MOVE_PAGEDOWN (5)
381 #define XT_MOVE_PAGEUP (6)
382 #define XT_MOVE_HALFDOWN (7)
383 #define XT_MOVE_HALFUP (8)
384 #define XT_MOVE_LEFT (9)
385 #define XT_MOVE_FARLEFT (10)
386 #define XT_MOVE_RIGHT (11)
387 #define XT_MOVE_FARRIGHT (12)
389 #define XT_TAB_LAST (-4)
390 #define XT_TAB_FIRST (-3)
391 #define XT_TAB_PREV (-2)
392 #define XT_TAB_NEXT (-1)
393 #define XT_TAB_INVALID (0)
394 #define XT_TAB_NEW (1)
395 #define XT_TAB_DELETE (2)
396 #define XT_TAB_DELQUIT (3)
397 #define XT_TAB_OPEN (4)
398 #define XT_TAB_UNDO_CLOSE (5)
399 #define XT_TAB_SHOW (6)
400 #define XT_TAB_HIDE (7)
402 #define XT_NAV_INVALID (0)
403 #define XT_NAV_BACK (1)
404 #define XT_NAV_FORWARD (2)
405 #define XT_NAV_RELOAD (3)
406 #define XT_NAV_RELOAD_CACHE (4)
408 #define XT_FOCUS_INVALID (0)
409 #define XT_FOCUS_URI (1)
410 #define XT_FOCUS_SEARCH (2)
412 #define XT_SEARCH_INVALID (0)
413 #define XT_SEARCH_NEXT (1)
414 #define XT_SEARCH_PREV (2)
416 #define XT_PASTE_CURRENT_TAB (0)
417 #define XT_PASTE_NEW_TAB (1)
419 #define XT_FONT_SET (0)
421 #define XT_URL_SHOW (1)
422 #define XT_URL_HIDE (2)
424 #define XT_STATUSBAR_SHOW (1)
425 #define XT_STATUSBAR_HIDE (2)
427 #define XT_WL_TOGGLE (1<<0)
428 #define XT_WL_ENABLE (1<<1)
429 #define XT_WL_DISABLE (1<<2)
430 #define XT_WL_FQDN (1<<3) /* default */
431 #define XT_WL_TOPLEVEL (1<<4)
432 #define XT_WL_PERSISTENT (1<<5)
433 #define XT_WL_SESSION (1<<6)
435 #define XT_SHOW (1<<7)
436 #define XT_DELETE (1<<8)
437 #define XT_SAVE (1<<9)
438 #define XT_OPEN (1<<10)
440 #define XT_CMD_OPEN (0)
441 #define XT_CMD_OPEN_CURRENT (1)
442 #define XT_CMD_TABNEW (2)
443 #define XT_CMD_TABNEW_CURRENT (3)
445 #define XT_STATUS_NOTHING (0)
446 #define XT_STATUS_LINK (1)
447 #define XT_STATUS_URI (2)
448 #define XT_STATUS_LOADING (3)
450 #define XT_SES_DONOTHING (0)
451 #define XT_SES_CLOSETABS (1)
453 #define XT_BM_NORMAL (0)
454 #define XT_BM_WHITELIST (1)
455 #define XT_BM_KIOSK (2)
457 #define XT_PREFIX (1<<0)
458 #define XT_USERARG (1<<1)
459 #define XT_URLARG (1<<2)
460 #define XT_INTARG (1<<3)
462 /* mime types */
463 struct mime_type {
464 char *mt_type;
465 char *mt_action;
466 int mt_default;
467 int mt_download;
468 TAILQ_ENTRY(mime_type) entry;
470 TAILQ_HEAD(mime_type_list, mime_type);
472 /* uri aliases */
473 struct alias {
474 char *a_name;
475 char *a_uri;
476 TAILQ_ENTRY(alias) entry;
478 TAILQ_HEAD(alias_list, alias);
480 /* settings that require restart */
481 int tabless = 0; /* allow only 1 tab */
482 int enable_socket = 0;
483 int single_instance = 0; /* only allow one xxxterm to run */
484 int fancy_bar = 1; /* fancy toolbar */
485 int browser_mode = XT_BM_NORMAL;
487 /* runtime settings */
488 int show_tabs = 1; /* show tabs on notebook */
489 int show_url = 1; /* show url toolbar on notebook */
490 int show_statusbar = 0; /* vimperator style status bar */
491 int ctrl_click_focus = 0; /* ctrl click gets focus */
492 int cookies_enabled = 1; /* enable cookies */
493 int read_only_cookies = 0; /* enable to not write cookies */
494 int enable_scripts = 1;
495 int enable_plugins = 0;
496 int default_font_size = 12;
497 gfloat default_zoom_level = 1.0;
498 int window_height = 768;
499 int window_width = 1024;
500 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
501 unsigned refresh_interval = 10; /* download refresh interval */
502 int enable_cookie_whitelist = 0;
503 int enable_js_whitelist = 0;
504 time_t session_timeout = 3600; /* cookie session timeout */
505 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
506 char *ssl_ca_file = NULL;
507 char *resource_dir = NULL;
508 gboolean ssl_strict_certs = FALSE;
509 int append_next = 1; /* append tab after current tab */
510 char *home = NULL;
511 char *search_string = NULL;
512 char *http_proxy = NULL;
513 char download_dir[PATH_MAX];
514 char runtime_settings[PATH_MAX]; /* override of settings */
515 int allow_volatile_cookies = 0;
516 int save_global_history = 0; /* save global history to disk */
517 char *user_agent = NULL;
518 int save_rejected_cookies = 0;
519 time_t session_autosave = 0;
520 int guess_search = 0;
521 int dns_prefetch = FALSE;
522 gint max_connections = 25;
523 gint max_host_connections = 5;
524 gint enable_spell_checking = 0;
525 char *spell_check_languages = NULL;
527 struct settings;
528 struct key_binding;
529 int set_download_dir(struct settings *, char *);
530 int set_work_dir(struct settings *, char *);
531 int set_runtime_dir(struct settings *, char *);
532 int set_browser_mode(struct settings *, char *);
533 int set_cookie_policy(struct settings *, char *);
534 int add_alias(struct settings *, char *);
535 int add_mime_type(struct settings *, char *);
536 int add_cookie_wl(struct settings *, char *);
537 int add_js_wl(struct settings *, char *);
538 int add_kb(struct settings *, char *);
539 void button_set_stockid(GtkWidget *, char *);
540 GtkWidget * create_button(char *, char *, int);
542 char *get_browser_mode(struct settings *);
543 char *get_cookie_policy(struct settings *);
545 char *get_download_dir(struct settings *);
546 char *get_work_dir(struct settings *);
547 char *get_runtime_dir(struct settings *);
549 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
550 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
551 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
552 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
553 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
555 void recalc_tabs(void);
557 struct special {
558 int (*set)(struct settings *, char *);
559 char *(*get)(struct settings *);
560 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
563 struct special s_browser_mode = {
564 set_browser_mode,
565 get_browser_mode,
566 NULL
569 struct special s_cookie = {
570 set_cookie_policy,
571 get_cookie_policy,
572 NULL
575 struct special s_alias = {
576 add_alias,
577 NULL,
578 walk_alias
581 struct special s_mime = {
582 add_mime_type,
583 NULL,
584 walk_mime_type
587 struct special s_js = {
588 add_js_wl,
589 NULL,
590 walk_js_wl
593 struct special s_kb = {
594 add_kb,
595 NULL,
596 walk_kb
599 struct special s_cookie_wl = {
600 add_cookie_wl,
601 NULL,
602 walk_cookie_wl
605 struct special s_download_dir = {
606 set_download_dir,
607 get_download_dir,
608 NULL
611 struct special s_work_dir = {
612 set_work_dir,
613 get_work_dir,
614 NULL
617 struct settings {
618 char *name;
619 int type;
620 #define XT_S_INVALID (0)
621 #define XT_S_INT (1)
622 #define XT_S_STR (2)
623 #define XT_S_FLOAT (3)
624 uint32_t flags;
625 #define XT_SF_RESTART (1<<0)
626 #define XT_SF_RUNTIME (1<<1)
627 int *ival;
628 char **sval;
629 struct special *s;
630 gfloat *fval;
631 } rs[] = {
632 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
633 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
634 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
635 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
636 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
637 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
638 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
639 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
640 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
641 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
642 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
643 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
644 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
645 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
646 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
647 { "home", XT_S_STR, 0, NULL, &home, NULL },
648 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
649 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
650 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
651 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
652 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
653 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
654 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
655 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
656 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
657 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
658 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
659 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
660 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
661 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
662 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
663 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
664 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
665 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
666 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
667 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
668 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
669 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
670 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
671 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
672 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
674 /* runtime settings */
675 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
676 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
677 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
678 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
679 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
682 int about(struct tab *, struct karg *);
683 int blank(struct tab *, struct karg *);
684 int ca_cmd(struct tab *, struct karg *);
685 int cookie_show_wl(struct tab *, struct karg *);
686 int js_show_wl(struct tab *, struct karg *);
687 int help(struct tab *, struct karg *);
688 int set(struct tab *, struct karg *);
689 int stats(struct tab *, struct karg *);
690 int marco(struct tab *, struct karg *);
691 const char * marco_message(int *);
692 int xtp_page_cl(struct tab *, struct karg *);
693 int xtp_page_dl(struct tab *, struct karg *);
694 int xtp_page_fl(struct tab *, struct karg *);
695 int xtp_page_hl(struct tab *, struct karg *);
697 #define XT_URI_ABOUT ("about:")
698 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
699 #define XT_URI_ABOUT_ABOUT ("about")
700 #define XT_URI_ABOUT_BLANK ("blank")
701 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
702 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
703 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
704 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
705 #define XT_URI_ABOUT_FAVORITES ("favorites")
706 #define XT_URI_ABOUT_HELP ("help")
707 #define XT_URI_ABOUT_HISTORY ("history")
708 #define XT_URI_ABOUT_JSWL ("jswl")
709 #define XT_URI_ABOUT_SET ("set")
710 #define XT_URI_ABOUT_STATS ("stats")
711 #define XT_URI_ABOUT_MARCO ("marco")
713 struct about_type {
714 char *name;
715 int (*func)(struct tab *, struct karg *);
716 } about_list[] = {
717 { XT_URI_ABOUT_ABOUT, about },
718 { XT_URI_ABOUT_BLANK, blank },
719 { XT_URI_ABOUT_CERTS, ca_cmd },
720 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
721 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
722 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
723 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
724 { XT_URI_ABOUT_HELP, help },
725 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
726 { XT_URI_ABOUT_JSWL, js_show_wl },
727 { XT_URI_ABOUT_SET, set },
728 { XT_URI_ABOUT_STATS, stats },
729 { XT_URI_ABOUT_MARCO, marco },
732 /* globals */
733 extern char *__progname;
734 char **start_argv;
735 struct passwd *pwd;
736 GtkWidget *main_window;
737 GtkNotebook *notebook;
738 GtkWidget *arrow, *abtn;
739 struct tab_list tabs;
740 struct history_list hl;
741 struct download_list downloads;
742 struct domain_list c_wl;
743 struct domain_list js_wl;
744 struct undo_tailq undos;
745 struct keybinding_list kbl;
746 int undo_count;
747 int updating_dl_tabs = 0;
748 int updating_hl_tabs = 0;
749 int updating_cl_tabs = 0;
750 int updating_fl_tabs = 0;
751 char *global_search;
752 uint64_t blocked_cookies = 0;
753 char named_session[PATH_MAX];
754 void update_favicon(struct tab *);
755 int icon_size_map(int);
757 GtkListStore *completion_model;
758 void completion_add(struct tab *);
759 void completion_add_uri(const gchar *);
760 void xxx_dir(char *);
762 void
763 sigchild(int sig)
765 int saved_errno, status;
766 pid_t pid;
768 saved_errno = errno;
770 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
771 if (pid == -1) {
772 if (errno == EINTR)
773 continue;
774 if (errno != ECHILD) {
776 clog_warn("sigchild: waitpid:");
779 break;
782 if (WIFEXITED(status)) {
783 if (WEXITSTATUS(status) != 0) {
785 clog_warnx("sigchild: child exit status: %d",
786 WEXITSTATUS(status));
789 } else {
791 clog_warnx("sigchild: child is terminated abnormally");
796 errno = saved_errno;
800 is_g_object_setting(GObject *o, char *str)
802 guint n_props = 0, i;
803 GParamSpec **proplist;
805 if (! G_IS_OBJECT(o))
806 return (0);
808 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
809 &n_props);
811 for (i=0; i < n_props; i++) {
812 if (! strcmp(proplist[i]->name, str))
813 return (1);
815 return (0);
818 gchar *
819 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
821 gchar *r;
823 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
824 "<head>\n"
825 "<title>%s</title>\n"
826 "%s"
827 "%s"
828 "</head>\n"
829 "<body>\n"
830 "<h1>%s</h1>\n"
831 "%s\n</body>\n"
832 "</html>",
833 title,
834 addstyles ? XT_PAGE_STYLE : "",
835 head,
836 title,
837 body);
839 return r;
843 * Display a web page from a HTML string in memory, rather than from a URL
845 void
846 load_webkit_string(struct tab *t, const char *str, gchar *title)
848 gchar *uri;
849 char file[PATH_MAX];
850 GdkPixbuf *pb;
852 /* we set this to indicate we want to manually do navaction */
853 if (t->bfl)
854 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
856 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
857 #if GTK_CHECK_VERSION(2, 20, 0)
858 gtk_spinner_stop(GTK_SPINNER(t->spinner));
859 gtk_widget_hide(t->spinner);
860 #endif
862 if (title) {
863 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
864 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
865 g_free(uri);
867 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
868 pb = gdk_pixbuf_new_from_file(file, NULL);
869 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
870 GTK_ENTRY_ICON_PRIMARY, pb);
871 gdk_pixbuf_unref(pb);
875 void
876 set_status(struct tab *t, gchar *s, int status)
878 gchar *type = NULL;
880 if (s == NULL)
881 return;
883 switch (status) {
884 case XT_STATUS_LOADING:
885 type = g_strdup_printf("Loading: %s", s);
886 s = type;
887 break;
888 case XT_STATUS_LINK:
889 type = g_strdup_printf("Link: %s", s);
890 if (!t->status)
891 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
892 s = type;
893 break;
894 case XT_STATUS_URI:
895 type = g_strdup_printf("%s", s);
896 if (!t->status) {
897 t->status = g_strdup(type);
899 s = type;
900 if (!t->status)
901 t->status = g_strdup(s);
902 break;
903 case XT_STATUS_NOTHING:
904 /* FALL THROUGH */
905 default:
906 break;
908 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
909 if (type)
910 g_free(type);
913 void
914 hide_oops(struct tab *t)
916 gtk_widget_hide(t->oops);
919 void
920 hide_cmd(struct tab *t)
922 gtk_widget_hide(t->cmd);
925 void
926 show_cmd(struct tab *t)
928 gtk_widget_hide(t->oops);
929 gtk_widget_show(t->cmd);
932 void
933 show_oops(struct tab *t, const char *fmt, ...)
935 va_list ap;
936 char *msg;
938 if (fmt == NULL)
939 return;
941 va_start(ap, fmt);
942 if (vasprintf(&msg, fmt, ap) == -1)
943 errx(1, "show_oops failed");
944 va_end(ap);
946 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
947 gtk_widget_hide(t->cmd);
948 gtk_widget_show(t->oops);
951 /* XXX collapse with show_oops */
952 void
953 show_oops_s(const char *fmt, ...)
955 va_list ap;
956 char *msg;
957 struct tab *ti, *t = NULL;
959 if (fmt == NULL)
960 return;
962 TAILQ_FOREACH(ti, &tabs, entry)
963 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
964 t = ti;
965 break;
967 if (t == NULL)
968 return;
970 va_start(ap, fmt);
971 if (vasprintf(&msg, fmt, ap) == -1)
972 errx(1, "show_oops_s failed");
973 va_end(ap);
975 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
976 gtk_widget_hide(t->cmd);
977 gtk_widget_show(t->oops);
980 char *
981 get_as_string(struct settings *s)
983 char *r = NULL;
985 if (s == NULL)
986 return (NULL);
988 if (s->s) {
989 if (s->s->get)
990 r = s->s->get(s);
991 else
992 warnx("get_as_string skip %s\n", s->name);
993 } else if (s->type == XT_S_INT)
994 r = g_strdup_printf("%d", *s->ival);
995 else if (s->type == XT_S_STR)
996 r = g_strdup(*s->sval);
997 else if (s->type == XT_S_FLOAT)
998 r = g_strdup_printf("%f", *s->fval);
999 else
1000 r = g_strdup_printf("INVALID TYPE");
1002 return (r);
1005 void
1006 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1008 int i;
1009 char *s;
1011 for (i = 0; i < LENGTH(rs); i++) {
1012 if (rs[i].s && rs[i].s->walk)
1013 rs[i].s->walk(&rs[i], cb, cb_args);
1014 else {
1015 s = get_as_string(&rs[i]);
1016 cb(&rs[i], s, cb_args);
1017 g_free(s);
1023 set_browser_mode(struct settings *s, char *val)
1025 if (!strcmp(val, "whitelist")) {
1026 browser_mode = XT_BM_WHITELIST;
1027 allow_volatile_cookies = 0;
1028 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1029 cookies_enabled = 1;
1030 enable_cookie_whitelist = 1;
1031 read_only_cookies = 0;
1032 save_rejected_cookies = 0;
1033 session_timeout = 3600;
1034 enable_scripts = 0;
1035 enable_js_whitelist = 1;
1036 } else if (!strcmp(val, "normal")) {
1037 browser_mode = XT_BM_NORMAL;
1038 allow_volatile_cookies = 0;
1039 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1040 cookies_enabled = 1;
1041 enable_cookie_whitelist = 0;
1042 read_only_cookies = 0;
1043 save_rejected_cookies = 0;
1044 session_timeout = 3600;
1045 enable_scripts = 1;
1046 enable_js_whitelist = 0;
1047 } else if (!strcmp(val, "kiosk")) {
1048 browser_mode = XT_BM_KIOSK;
1049 allow_volatile_cookies = 0;
1050 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1051 cookies_enabled = 1;
1052 enable_cookie_whitelist = 0;
1053 read_only_cookies = 0;
1054 save_rejected_cookies = 0;
1055 session_timeout = 3600;
1056 enable_scripts = 1;
1057 enable_js_whitelist = 0;
1058 show_tabs = 0;
1059 tabless = 1;
1060 } else
1061 return (1);
1063 return (0);
1066 char *
1067 get_browser_mode(struct settings *s)
1069 char *r = NULL;
1071 if (browser_mode == XT_BM_WHITELIST)
1072 r = g_strdup("whitelist");
1073 else if (browser_mode == XT_BM_NORMAL)
1074 r = g_strdup("normal");
1075 else if (browser_mode == XT_BM_KIOSK)
1076 r = g_strdup("kiosk");
1077 else
1078 return (NULL);
1080 return (r);
1084 set_cookie_policy(struct settings *s, char *val)
1086 if (!strcmp(val, "no3rdparty"))
1087 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1088 else if (!strcmp(val, "accept"))
1089 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1090 else if (!strcmp(val, "reject"))
1091 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1092 else
1093 return (1);
1095 return (0);
1098 char *
1099 get_cookie_policy(struct settings *s)
1101 char *r = NULL;
1103 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1104 r = g_strdup("no3rdparty");
1105 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1106 r = g_strdup("accept");
1107 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1108 r = g_strdup("reject");
1109 else
1110 return (NULL);
1112 return (r);
1115 char *
1116 get_download_dir(struct settings *s)
1118 if (download_dir[0] == '\0')
1119 return (0);
1120 return (g_strdup(download_dir));
1124 set_download_dir(struct settings *s, char *val)
1126 if (val[0] == '~')
1127 snprintf(download_dir, sizeof download_dir, "%s/%s",
1128 pwd->pw_dir, &val[1]);
1129 else
1130 strlcpy(download_dir, val, sizeof download_dir);
1132 return (0);
1136 * Session IDs.
1137 * We use these to prevent people putting xxxt:// URLs on
1138 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1140 #define XT_XTP_SES_KEY_SZ 8
1141 #define XT_XTP_SES_KEY_HEX_FMT \
1142 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1143 char *dl_session_key; /* downloads */
1144 char *hl_session_key; /* history list */
1145 char *cl_session_key; /* cookie list */
1146 char *fl_session_key; /* favorites list */
1148 char work_dir[PATH_MAX];
1149 char certs_dir[PATH_MAX];
1150 char cache_dir[PATH_MAX];
1151 char sessions_dir[PATH_MAX];
1152 char cookie_file[PATH_MAX];
1153 SoupURI *proxy_uri = NULL;
1154 SoupSession *session;
1155 SoupCookieJar *s_cookiejar;
1156 SoupCookieJar *p_cookiejar;
1157 char rc_fname[PATH_MAX];
1159 struct mime_type_list mtl;
1160 struct alias_list aliases;
1162 /* protos */
1163 struct tab *create_new_tab(char *, struct undo *, int, int);
1164 void delete_tab(struct tab *);
1165 void adjustfont_webkit(struct tab *, int);
1166 int run_script(struct tab *, char *);
1167 int download_rb_cmp(struct download *, struct download *);
1168 gboolean cmd_execute(struct tab *t, char *str);
1171 history_rb_cmp(struct history *h1, struct history *h2)
1173 return (strcmp(h1->uri, h2->uri));
1175 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1178 domain_rb_cmp(struct domain *d1, struct domain *d2)
1180 return (strcmp(d1->d, d2->d));
1182 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1184 char *
1185 get_work_dir(struct settings *s)
1187 if (work_dir[0] == '\0')
1188 return (0);
1189 return (g_strdup(work_dir));
1193 set_work_dir(struct settings *s, char *val)
1195 if (val[0] == '~')
1196 snprintf(work_dir, sizeof work_dir, "%s/%s",
1197 pwd->pw_dir, &val[1]);
1198 else
1199 strlcpy(work_dir, val, sizeof work_dir);
1201 return (0);
1205 * generate a session key to secure xtp commands.
1206 * pass in a ptr to the key in question and it will
1207 * be modified in place.
1209 void
1210 generate_xtp_session_key(char **key)
1212 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1214 /* free old key */
1215 if (*key)
1216 g_free(*key);
1218 /* make a new one */
1219 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1220 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1221 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1222 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1224 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1228 * validate a xtp session key.
1229 * return 1 if OK
1232 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1234 if (strcmp(trusted, untrusted) != 0) {
1235 show_oops(t, "%s: xtp session key mismatch possible spoof",
1236 __func__);
1237 return (0);
1240 return (1);
1244 download_rb_cmp(struct download *e1, struct download *e2)
1246 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1248 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1250 struct valid_url_types {
1251 char *type;
1252 } vut[] = {
1253 { "http://" },
1254 { "https://" },
1255 { "ftp://" },
1256 { "file://" },
1257 { XT_XTP_STR },
1261 valid_url_type(char *url)
1263 int i;
1265 for (i = 0; i < LENGTH(vut); i++)
1266 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1267 return (0);
1269 return (1);
1272 void
1273 print_cookie(char *msg, SoupCookie *c)
1275 if (c == NULL)
1276 return;
1278 if (msg)
1279 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1280 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1281 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1282 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1283 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1284 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1285 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1286 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1287 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1288 DNPRINTF(XT_D_COOKIE, "====================================\n");
1291 void
1292 walk_alias(struct settings *s,
1293 void (*cb)(struct settings *, char *, void *), void *cb_args)
1295 struct alias *a;
1296 char *str;
1298 if (s == NULL || cb == NULL) {
1299 show_oops_s("walk_alias invalid parameters");
1300 return;
1303 TAILQ_FOREACH(a, &aliases, entry) {
1304 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1305 cb(s, str, cb_args);
1306 g_free(str);
1310 char *
1311 match_alias(char *url_in)
1313 struct alias *a;
1314 char *arg;
1315 char *url_out = NULL, *search, *enc_arg;
1317 search = g_strdup(url_in);
1318 arg = search;
1319 if (strsep(&arg, " \t") == NULL) {
1320 show_oops_s("match_alias: NULL URL");
1321 goto done;
1324 TAILQ_FOREACH(a, &aliases, entry) {
1325 if (!strcmp(search, a->a_name))
1326 break;
1329 if (a != NULL) {
1330 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1331 a->a_name);
1332 if (arg != NULL) {
1333 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1334 url_out = g_strdup_printf(a->a_uri, enc_arg);
1335 g_free(enc_arg);
1336 } else
1337 url_out = g_strdup(a->a_uri);
1339 done:
1340 g_free(search);
1341 return (url_out);
1344 char *
1345 guess_url_type(char *url_in)
1347 struct stat sb;
1348 char *url_out = NULL, *enc_search = NULL;
1350 url_out = match_alias(url_in);
1351 if (url_out != NULL)
1352 return (url_out);
1354 if (guess_search) {
1356 * If there is no dot nor slash in the string and it isn't a
1357 * path to a local file and doesn't resolves to an IP, assume
1358 * that the user wants to search for the string.
1361 if (strchr(url_in, '.') == NULL &&
1362 strchr(url_in, '/') == NULL &&
1363 stat(url_in, &sb) != 0 &&
1364 gethostbyname(url_in) == NULL) {
1366 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1367 url_out = g_strdup_printf(search_string, enc_search);
1368 g_free(enc_search);
1369 return (url_out);
1373 /* XXX not sure about this heuristic */
1374 if (stat(url_in, &sb) == 0)
1375 url_out = g_strdup_printf("file://%s", url_in);
1376 else
1377 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1379 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1381 return (url_out);
1384 void
1385 load_uri(struct tab *t, gchar *uri)
1387 struct karg args;
1388 gchar *newuri = NULL;
1389 int i;
1391 if (uri == NULL)
1392 return;
1394 /* Strip leading spaces. */
1395 while(*uri && isspace(*uri))
1396 uri++;
1398 if (strlen(uri) == 0) {
1399 blank(t, NULL);
1400 return;
1403 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1404 for (i = 0; i < LENGTH(about_list); i++)
1405 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1406 bzero(&args, sizeof args);
1407 about_list[i].func(t, &args);
1408 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1409 FALSE);
1410 return;
1412 show_oops(t, "invalid about page");
1413 return;
1416 if (valid_url_type(uri)) {
1417 newuri = guess_url_type(uri);
1418 uri = newuri;
1421 set_status(t, (char *)uri, XT_STATUS_LOADING);
1422 webkit_web_view_load_uri(t->wv, uri);
1424 if (newuri)
1425 g_free(newuri);
1428 const gchar *
1429 get_uri(WebKitWebView *wv)
1431 const gchar *uri;
1433 uri = webkit_web_view_get_uri(wv);
1435 if (uri && strlen(uri) > 0)
1436 return (uri);
1437 else
1438 return (NULL);
1442 add_alias(struct settings *s, char *line)
1444 char *l, *alias;
1445 struct alias *a = NULL;
1447 if (s == NULL || line == NULL) {
1448 show_oops_s("add_alias invalid parameters");
1449 return (1);
1452 l = line;
1453 a = g_malloc(sizeof(*a));
1455 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1456 show_oops_s("add_alias: incomplete alias definition");
1457 goto bad;
1459 if (strlen(alias) == 0 || strlen(l) == 0) {
1460 show_oops_s("add_alias: invalid alias definition");
1461 goto bad;
1464 a->a_name = g_strdup(alias);
1465 a->a_uri = g_strdup(l);
1467 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1469 TAILQ_INSERT_TAIL(&aliases, a, entry);
1471 return (0);
1472 bad:
1473 if (a)
1474 g_free(a);
1475 return (1);
1479 add_mime_type(struct settings *s, char *line)
1481 char *mime_type;
1482 char *l;
1483 struct mime_type *m = NULL;
1484 int downloadfirst = 0;
1486 /* XXX this could be smarter */
1488 if (line == NULL && strlen(line) == 0) {
1489 show_oops_s("add_mime_type invalid parameters");
1490 return (1);
1493 l = line;
1494 if (*l == '@') {
1495 downloadfirst = 1;
1496 l++;
1498 m = g_malloc(sizeof(*m));
1500 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1501 show_oops_s("add_mime_type: invalid mime_type");
1502 goto bad;
1504 if (mime_type[strlen(mime_type) - 1] == '*') {
1505 mime_type[strlen(mime_type) - 1] = '\0';
1506 m->mt_default = 1;
1507 } else
1508 m->mt_default = 0;
1510 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1511 show_oops_s("add_mime_type: invalid mime_type");
1512 goto bad;
1515 m->mt_type = g_strdup(mime_type);
1516 m->mt_action = g_strdup(l);
1517 m->mt_download = downloadfirst;
1519 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1520 m->mt_type, m->mt_action, m->mt_default);
1522 TAILQ_INSERT_TAIL(&mtl, m, entry);
1524 return (0);
1525 bad:
1526 if (m)
1527 g_free(m);
1528 return (1);
1531 struct mime_type *
1532 find_mime_type(char *mime_type)
1534 struct mime_type *m, *def = NULL, *rv = NULL;
1536 TAILQ_FOREACH(m, &mtl, entry) {
1537 if (m->mt_default &&
1538 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1539 def = m;
1541 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1542 rv = m;
1543 break;
1547 if (rv == NULL)
1548 rv = def;
1550 return (rv);
1553 void
1554 walk_mime_type(struct settings *s,
1555 void (*cb)(struct settings *, char *, void *), void *cb_args)
1557 struct mime_type *m;
1558 char *str;
1560 if (s == NULL || cb == NULL)
1561 show_oops_s("walk_mime_type invalid parameters");
1563 TAILQ_FOREACH(m, &mtl, entry) {
1564 str = g_strdup_printf("%s%s --> %s",
1565 m->mt_type,
1566 m->mt_default ? "*" : "",
1567 m->mt_action);
1568 cb(s, str, cb_args);
1569 g_free(str);
1573 void
1574 wl_add(char *str, struct domain_list *wl, int handy)
1576 struct domain *d;
1577 int add_dot = 0;
1579 if (str == NULL || wl == NULL || strlen(str) < 2)
1580 return;
1582 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1584 /* treat *.moo.com the same as .moo.com */
1585 if (str[0] == '*' && str[1] == '.')
1586 str = &str[1];
1587 else if (str[0] == '.')
1588 str = &str[0];
1589 else
1590 add_dot = 1;
1592 d = g_malloc(sizeof *d);
1593 if (add_dot)
1594 d->d = g_strdup_printf(".%s", str);
1595 else
1596 d->d = g_strdup(str);
1597 d->handy = handy;
1599 if (RB_INSERT(domain_list, wl, d))
1600 goto unwind;
1602 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1603 return;
1604 unwind:
1605 if (d) {
1606 if (d->d)
1607 g_free(d->d);
1608 g_free(d);
1613 add_cookie_wl(struct settings *s, char *entry)
1615 wl_add(entry, &c_wl, 1);
1616 return (0);
1619 void
1620 walk_cookie_wl(struct settings *s,
1621 void (*cb)(struct settings *, char *, void *), void *cb_args)
1623 struct domain *d;
1625 if (s == NULL || cb == NULL) {
1626 show_oops_s("walk_cookie_wl invalid parameters");
1627 return;
1630 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1631 cb(s, d->d, cb_args);
1634 void
1635 walk_js_wl(struct settings *s,
1636 void (*cb)(struct settings *, char *, void *), void *cb_args)
1638 struct domain *d;
1640 if (s == NULL || cb == NULL) {
1641 show_oops_s("walk_js_wl invalid parameters");
1642 return;
1645 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1646 cb(s, d->d, cb_args);
1650 add_js_wl(struct settings *s, char *entry)
1652 wl_add(entry, &js_wl, 1 /* persistent */);
1653 return (0);
1656 struct domain *
1657 wl_find(const gchar *search, struct domain_list *wl)
1659 int i;
1660 struct domain *d = NULL, dfind;
1661 gchar *s = NULL;
1663 if (search == NULL || wl == NULL)
1664 return (NULL);
1665 if (strlen(search) < 2)
1666 return (NULL);
1668 if (search[0] != '.')
1669 s = g_strdup_printf(".%s", search);
1670 else
1671 s = g_strdup(search);
1673 for (i = strlen(s) - 1; i >= 0; i--) {
1674 if (s[i] == '.') {
1675 dfind.d = &s[i];
1676 d = RB_FIND(domain_list, wl, &dfind);
1677 if (d)
1678 goto done;
1682 done:
1683 if (s)
1684 g_free(s);
1686 return (d);
1689 struct domain *
1690 wl_find_uri(const gchar *s, struct domain_list *wl)
1692 int i;
1693 char *ss;
1694 struct domain *r;
1696 if (s == NULL || wl == NULL)
1697 return (NULL);
1699 if (!strncmp(s, "http://", strlen("http://")))
1700 s = &s[strlen("http://")];
1701 else if (!strncmp(s, "https://", strlen("https://")))
1702 s = &s[strlen("https://")];
1704 if (strlen(s) < 2)
1705 return (NULL);
1707 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1708 /* chop string at first slash */
1709 if (s[i] == '/' || s[i] == '\0') {
1710 ss = g_strdup(s);
1711 ss[i] = '\0';
1712 r = wl_find(ss, wl);
1713 g_free(ss);
1714 return (r);
1717 return (NULL);
1720 char *
1721 get_toplevel_domain(char *domain)
1723 char *s;
1724 int found = 0;
1726 if (domain == NULL)
1727 return (NULL);
1728 if (strlen(domain) < 2)
1729 return (NULL);
1731 s = &domain[strlen(domain) - 1];
1732 while (s != domain) {
1733 if (*s == '.') {
1734 found++;
1735 if (found == 2)
1736 return (s);
1738 s--;
1741 if (found)
1742 return (domain);
1744 return (NULL);
1748 settings_add(char *var, char *val)
1750 int i, rv, *p;
1751 gfloat *f;
1752 char **s;
1754 /* get settings */
1755 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1756 if (strcmp(var, rs[i].name))
1757 continue;
1759 if (rs[i].s) {
1760 if (rs[i].s->set(&rs[i], val))
1761 errx(1, "invalid value for %s: %s", var, val);
1762 rv = 1;
1763 break;
1764 } else
1765 switch (rs[i].type) {
1766 case XT_S_INT:
1767 p = rs[i].ival;
1768 *p = atoi(val);
1769 rv = 1;
1770 break;
1771 case XT_S_STR:
1772 s = rs[i].sval;
1773 if (s == NULL)
1774 errx(1, "invalid sval for %s",
1775 rs[i].name);
1776 if (*s)
1777 g_free(*s);
1778 *s = g_strdup(val);
1779 rv = 1;
1780 break;
1781 case XT_S_FLOAT:
1782 f = rs[i].fval;
1783 *f = atof(val);
1784 rv = 1;
1785 break;
1786 case XT_S_INVALID:
1787 default:
1788 errx(1, "invalid type for %s", var);
1790 break;
1792 return (rv);
1795 #define WS "\n= \t"
1796 void
1797 config_parse(char *filename, int runtime)
1799 FILE *config, *f;
1800 char *line, *cp, *var, *val;
1801 size_t len, lineno = 0;
1802 int handled;
1803 char file[PATH_MAX];
1804 struct stat sb;
1806 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1808 if (filename == NULL)
1809 return;
1811 if (runtime && runtime_settings[0] != '\0') {
1812 snprintf(file, sizeof file, "%s/%s",
1813 work_dir, runtime_settings);
1814 if (stat(file, &sb)) {
1815 warnx("runtime file doesn't exist, creating it");
1816 if ((f = fopen(file, "w")) == NULL)
1817 err(1, "runtime");
1818 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1819 fclose(f);
1821 } else
1822 strlcpy(file, filename, sizeof file);
1824 if ((config = fopen(file, "r")) == NULL) {
1825 warn("config_parse: cannot open %s", filename);
1826 return;
1829 for (;;) {
1830 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1831 if (feof(config) || ferror(config))
1832 break;
1834 cp = line;
1835 cp += (long)strspn(cp, WS);
1836 if (cp[0] == '\0') {
1837 /* empty line */
1838 free(line);
1839 continue;
1842 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1843 errx(1, "invalid config file entry: %s", line);
1845 cp += (long)strspn(cp, WS);
1847 if ((val = strsep(&cp, "\0")) == NULL)
1848 break;
1850 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1851 handled = settings_add(var, val);
1852 if (handled == 0)
1853 errx(1, "invalid conf file entry: %s=%s", var, val);
1855 free(line);
1858 fclose(config);
1861 char *
1862 js_ref_to_string(JSContextRef context, JSValueRef ref)
1864 char *s = NULL;
1865 size_t l;
1866 JSStringRef jsref;
1868 jsref = JSValueToStringCopy(context, ref, NULL);
1869 if (jsref == NULL)
1870 return (NULL);
1872 l = JSStringGetMaximumUTF8CStringSize(jsref);
1873 s = g_malloc(l);
1874 if (s)
1875 JSStringGetUTF8CString(jsref, s, l);
1876 JSStringRelease(jsref);
1878 return (s);
1881 void
1882 disable_hints(struct tab *t)
1884 bzero(t->hint_buf, sizeof t->hint_buf);
1885 bzero(t->hint_num, sizeof t->hint_num);
1886 run_script(t, "vimprobable_clear()");
1887 t->hints_on = 0;
1888 t->hint_mode = XT_HINT_NONE;
1891 void
1892 enable_hints(struct tab *t)
1894 bzero(t->hint_buf, sizeof t->hint_buf);
1895 run_script(t, "vimprobable_show_hints()");
1896 t->hints_on = 1;
1897 t->hint_mode = XT_HINT_NONE;
1900 #define XT_JS_OPEN ("open;")
1901 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1902 #define XT_JS_FIRE ("fire;")
1903 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1904 #define XT_JS_FOUND ("found;")
1905 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1908 run_script(struct tab *t, char *s)
1910 JSGlobalContextRef ctx;
1911 WebKitWebFrame *frame;
1912 JSStringRef str;
1913 JSValueRef val, exception;
1914 char *es, buf[128];
1916 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1917 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1919 frame = webkit_web_view_get_main_frame(t->wv);
1920 ctx = webkit_web_frame_get_global_context(frame);
1922 str = JSStringCreateWithUTF8CString(s);
1923 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1924 NULL, 0, &exception);
1925 JSStringRelease(str);
1927 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1928 if (val == NULL) {
1929 es = js_ref_to_string(ctx, exception);
1930 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1931 g_free(es);
1932 return (1);
1933 } else {
1934 es = js_ref_to_string(ctx, val);
1935 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1937 /* handle return value right here */
1938 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1939 disable_hints(t);
1940 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1943 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1944 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1945 &es[XT_JS_FIRE_LEN]);
1946 run_script(t, buf);
1947 disable_hints(t);
1950 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1951 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1952 disable_hints(t);
1955 g_free(es);
1958 return (0);
1962 hint(struct tab *t, struct karg *args)
1965 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1967 if (t->hints_on == 0)
1968 enable_hints(t);
1969 else
1970 disable_hints(t);
1972 return (0);
1975 void
1976 apply_style(struct tab *t)
1978 g_object_set(G_OBJECT(t->settings),
1979 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1983 userstyle(struct tab *t, struct karg *args)
1985 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1987 if (t->styled) {
1988 t->styled = 0;
1989 g_object_set(G_OBJECT(t->settings),
1990 "user-stylesheet-uri", NULL, (char *)NULL);
1991 } else {
1992 t->styled = 1;
1993 apply_style(t);
1995 return (0);
1999 * Doesn't work fully, due to the following bug:
2000 * https://bugs.webkit.org/show_bug.cgi?id=51747
2003 restore_global_history(void)
2005 char file[PATH_MAX];
2006 FILE *f;
2007 struct history *h;
2008 gchar *uri;
2009 gchar *title;
2010 const char delim[3] = {'\\', '\\', '\0'};
2012 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2014 if ((f = fopen(file, "r")) == NULL) {
2015 warnx("%s: fopen", __func__);
2016 return (1);
2019 for (;;) {
2020 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2021 if (feof(f) || ferror(f))
2022 break;
2024 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2025 if (feof(f) || ferror(f)) {
2026 free(uri);
2027 warnx("%s: broken history file\n", __func__);
2028 return (1);
2031 if (uri && strlen(uri) && title && strlen(title)) {
2032 webkit_web_history_item_new_with_data(uri, title);
2033 h = g_malloc(sizeof(struct history));
2034 h->uri = g_strdup(uri);
2035 h->title = g_strdup(title);
2036 RB_INSERT(history_list, &hl, h);
2037 completion_add_uri(h->uri);
2038 } else {
2039 warnx("%s: failed to restore history\n", __func__);
2040 free(uri);
2041 free(title);
2042 return (1);
2045 free(uri);
2046 free(title);
2047 uri = NULL;
2048 title = NULL;
2051 return (0);
2055 save_global_history_to_disk(struct tab *t)
2057 char file[PATH_MAX];
2058 FILE *f;
2059 struct history *h;
2061 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2063 if ((f = fopen(file, "w")) == NULL) {
2064 show_oops(t, "%s: global history file: %s",
2065 __func__, strerror(errno));
2066 return (1);
2069 RB_FOREACH_REVERSE(h, history_list, &hl) {
2070 if (h->uri && h->title)
2071 fprintf(f, "%s\n%s\n", h->uri, h->title);
2074 fclose(f);
2076 return (0);
2080 quit(struct tab *t, struct karg *args)
2082 if (save_global_history)
2083 save_global_history_to_disk(t);
2085 gtk_main_quit();
2087 return (1);
2091 open_tabs(struct tab *t, struct karg *a)
2093 char file[PATH_MAX];
2094 FILE *f = NULL;
2095 char *uri = NULL;
2096 int rv = 1;
2097 struct tab *ti, *tt;
2099 if (a == NULL)
2100 goto done;
2102 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2103 if ((f = fopen(file, "r")) == NULL)
2104 goto done;
2106 ti = TAILQ_LAST(&tabs, tab_list);
2108 for (;;) {
2109 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2110 if (feof(f) || ferror(f))
2111 break;
2113 /* retrieve session name */
2114 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2115 strlcpy(named_session,
2116 &uri[strlen(XT_SAVE_SESSION_ID)],
2117 sizeof named_session);
2118 continue;
2121 if (uri && strlen(uri))
2122 create_new_tab(uri, NULL, 1, -1);
2124 free(uri);
2125 uri = NULL;
2128 /* close open tabs */
2129 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2130 for (;;) {
2131 tt = TAILQ_FIRST(&tabs);
2132 if (tt != ti) {
2133 delete_tab(tt);
2134 continue;
2136 delete_tab(tt);
2137 break;
2141 rv = 0;
2142 done:
2143 if (f)
2144 fclose(f);
2146 return (rv);
2150 restore_saved_tabs(void)
2152 char file[PATH_MAX];
2153 int unlink_file = 0;
2154 struct stat sb;
2155 struct karg a;
2156 int rv = 0;
2158 snprintf(file, sizeof file, "%s/%s",
2159 sessions_dir, XT_RESTART_TABS_FILE);
2160 if (stat(file, &sb) == -1)
2161 a.s = XT_SAVED_TABS_FILE;
2162 else {
2163 unlink_file = 1;
2164 a.s = XT_RESTART_TABS_FILE;
2167 a.i = XT_SES_DONOTHING;
2168 rv = open_tabs(NULL, &a);
2170 if (unlink_file)
2171 unlink(file);
2173 return (rv);
2177 save_tabs(struct tab *t, struct karg *a)
2179 char file[PATH_MAX];
2180 FILE *f;
2181 struct tab *ti;
2182 const gchar *uri;
2183 int len = 0, i;
2184 const gchar **arr = NULL;
2186 if (a == NULL)
2187 return (1);
2188 if (a->s == NULL)
2189 snprintf(file, sizeof file, "%s/%s",
2190 sessions_dir, named_session);
2191 else
2192 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2194 if ((f = fopen(file, "w")) == NULL) {
2195 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2196 return (1);
2199 /* save session name */
2200 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2202 /* save tabs, in the order they are arranged in the notebook */
2203 TAILQ_FOREACH(ti, &tabs, entry)
2204 len++;
2206 arr = g_malloc0(len * sizeof(gchar *));
2208 TAILQ_FOREACH(ti, &tabs, entry) {
2209 if ((uri = get_uri(ti->wv)) != NULL)
2210 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2213 for (i = 0; i < len; i++)
2214 if (arr[i])
2215 fprintf(f, "%s\n", arr[i]);
2217 g_free(arr);
2218 /* try and make sure this gets to disk NOW. XXX Backup first? */
2219 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2220 show_oops(t, "May not have managed to save session: %s",
2221 strerror(errno));
2224 fclose(f);
2226 return (0);
2230 save_tabs_and_quit(struct tab *t, struct karg *args)
2232 struct karg a;
2234 a.s = NULL;
2235 save_tabs(t, &a);
2236 quit(t, NULL);
2238 return (1);
2242 yank_uri(struct tab *t, struct karg *args)
2244 const gchar *uri;
2245 GtkClipboard *clipboard;
2247 if ((uri = get_uri(t->wv)) == NULL)
2248 return (1);
2250 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2251 gtk_clipboard_set_text(clipboard, uri, -1);
2253 return (0);
2257 paste_uri(struct tab *t, struct karg *args)
2259 GtkClipboard *clipboard;
2260 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2261 gint len;
2262 gchar *p = NULL, *uri;
2264 /* try primary clipboard first */
2265 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2266 p = gtk_clipboard_wait_for_text(clipboard);
2268 /* if it failed get whatever text is in cut_buffer0 */
2269 if (p == NULL)
2270 if (gdk_property_get(gdk_get_default_root_window(),
2271 atom,
2272 gdk_atom_intern("STRING", FALSE),
2274 65536 /* picked out of my butt */,
2275 FALSE,
2276 NULL,
2277 NULL,
2278 &len,
2279 (guchar **)&p)) {
2280 /* yes sir, we need to NUL the string */
2281 p[len] = '\0';
2284 if (p) {
2285 uri = p;
2286 while(*uri && isspace(*uri))
2287 uri++;
2288 if (strlen(uri) == 0) {
2289 show_oops(t, "empty paste buffer");
2290 goto done;
2292 if (valid_url_type(uri)) {
2293 /* we can be clever and paste this in search box */
2294 show_oops(t, "not a valid URL");
2295 goto done;
2298 if (args->i == XT_PASTE_CURRENT_TAB)
2299 load_uri(t, uri);
2300 else if (args->i == XT_PASTE_NEW_TAB)
2301 create_new_tab(uri, NULL, 1, -1);
2304 done:
2305 if (p)
2306 g_free(p);
2308 return (0);
2311 char *
2312 find_domain(const gchar *s, int add_dot)
2314 int i;
2315 char *r = NULL, *ss = NULL;
2317 if (s == NULL)
2318 return (NULL);
2320 if (!strncmp(s, "http://", strlen("http://")))
2321 s = &s[strlen("http://")];
2322 else if (!strncmp(s, "https://", strlen("https://")))
2323 s = &s[strlen("https://")];
2325 if (strlen(s) < 2)
2326 return (NULL);
2328 ss = g_strdup(s);
2329 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2330 /* chop string at first slash */
2331 if (ss[i] == '/' || ss[i] == '\0') {
2332 ss[i] = '\0';
2333 if (add_dot)
2334 r = g_strdup_printf(".%s", ss);
2335 else
2336 r = g_strdup(ss);
2337 break;
2339 g_free(ss);
2341 return (r);
2345 toggle_cwl(struct tab *t, struct karg *args)
2347 struct domain *d;
2348 const gchar *uri;
2349 char *dom = NULL, *dom_toggle = NULL;
2350 int es;
2352 if (args == NULL)
2353 return (1);
2355 uri = get_uri(t->wv);
2356 dom = find_domain(uri, 1);
2357 d = wl_find(dom, &c_wl);
2359 if (d == NULL)
2360 es = 0;
2361 else
2362 es = 1;
2364 if (args->i & XT_WL_TOGGLE)
2365 es = !es;
2366 else if ((args->i & XT_WL_ENABLE) && es != 1)
2367 es = 1;
2368 else if ((args->i & XT_WL_DISABLE) && es != 0)
2369 es = 0;
2371 if (args->i & XT_WL_TOPLEVEL)
2372 dom_toggle = get_toplevel_domain(dom);
2373 else
2374 dom_toggle = dom;
2376 if (es)
2377 /* enable cookies for domain */
2378 wl_add(dom_toggle, &c_wl, 0);
2379 else
2380 /* disable cookies for domain */
2381 RB_REMOVE(domain_list, &c_wl, d);
2383 webkit_web_view_reload(t->wv);
2385 g_free(dom);
2386 return (0);
2390 toggle_js(struct tab *t, struct karg *args)
2392 int es;
2393 const gchar *uri;
2394 struct domain *d;
2395 char *dom = NULL, *dom_toggle = NULL;
2397 if (args == NULL)
2398 return (1);
2400 g_object_get(G_OBJECT(t->settings),
2401 "enable-scripts", &es, (char *)NULL);
2402 if (args->i & XT_WL_TOGGLE)
2403 es = !es;
2404 else if ((args->i & XT_WL_ENABLE) && es != 1)
2405 es = 1;
2406 else if ((args->i & XT_WL_DISABLE) && es != 0)
2407 es = 0;
2408 else
2409 return (1);
2411 uri = get_uri(t->wv);
2412 dom = find_domain(uri, 1);
2414 if (uri == NULL || dom == NULL) {
2415 show_oops(t, "Can't toggle domain in JavaScript white list");
2416 goto done;
2419 if (args->i & XT_WL_TOPLEVEL)
2420 dom_toggle = get_toplevel_domain(dom);
2421 else
2422 dom_toggle = dom;
2424 if (es) {
2425 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2426 wl_add(dom_toggle, &js_wl, 0 /* session */);
2427 } else {
2428 d = wl_find(dom_toggle, &js_wl);
2429 if (d)
2430 RB_REMOVE(domain_list, &js_wl, d);
2431 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2433 g_object_set(G_OBJECT(t->settings),
2434 "enable-scripts", es, (char *)NULL);
2435 g_object_set(G_OBJECT(t->settings),
2436 "javascript-can-open-windows-automatically", es, (char *)NULL);
2437 webkit_web_view_set_settings(t->wv, t->settings);
2438 webkit_web_view_reload(t->wv);
2439 done:
2440 if (dom)
2441 g_free(dom);
2442 return (0);
2445 void
2446 js_toggle_cb(GtkWidget *w, struct tab *t)
2448 struct karg a;
2450 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2451 toggle_js(t, &a);
2455 toggle_src(struct tab *t, struct karg *args)
2457 gboolean mode;
2459 if (t == NULL)
2460 return (0);
2462 mode = webkit_web_view_get_view_source_mode(t->wv);
2463 webkit_web_view_set_view_source_mode(t->wv, !mode);
2464 webkit_web_view_reload(t->wv);
2466 return (0);
2469 void
2470 focus_webview(struct tab *t)
2472 if (t == NULL)
2473 return;
2475 /* only grab focus if we are visible */
2476 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2477 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2481 focus(struct tab *t, struct karg *args)
2483 if (t == NULL || args == NULL)
2484 return (1);
2486 if (show_url == 0)
2487 return (0);
2489 if (args->i == XT_FOCUS_URI)
2490 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2491 else if (args->i == XT_FOCUS_SEARCH)
2492 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2494 return (0);
2498 stats(struct tab *t, struct karg *args)
2500 char *page, *body, *s, line[64 * 1024];
2501 uint64_t line_count = 0;
2502 FILE *r_cookie_f;
2504 if (t == NULL)
2505 show_oops_s("stats invalid parameters");
2507 line[0] = '\0';
2508 if (save_rejected_cookies) {
2509 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2510 for (;;) {
2511 s = fgets(line, sizeof line, r_cookie_f);
2512 if (s == NULL || feof(r_cookie_f) ||
2513 ferror(r_cookie_f))
2514 break;
2515 line_count++;
2517 fclose(r_cookie_f);
2518 snprintf(line, sizeof line,
2519 "<br/>Cookies blocked(*) total: %llu", line_count);
2520 } else
2521 show_oops(t, "Can't open blocked cookies file: %s",
2522 strerror(errno));
2525 body = g_strdup_printf(
2526 "Cookies blocked(*) this session: %llu"
2527 "%s"
2528 "<p><small><b>*</b> results vary based on settings</small></p>",
2529 blocked_cookies,
2530 line);
2532 page = get_html_page("Statistics", body, "", 0);
2533 g_free(body);
2535 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2536 g_free(page);
2538 return (0);
2542 marco(struct tab *t, struct karg *args)
2544 char *page, line[64 * 1024];
2545 int len;
2547 if (t == NULL)
2548 show_oops_s("marco invalid parameters");
2550 line[0] = '\0';
2551 snprintf(line, sizeof line, "%s", marco_message(&len));
2553 page = get_html_page("Marco Sez...", line, "", 0);
2555 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2556 g_free(page);
2558 return (0);
2562 blank(struct tab *t, struct karg *args)
2564 if (t == NULL)
2565 show_oops_s("blank invalid parameters");
2567 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2569 return (0);
2572 about(struct tab *t, struct karg *args)
2574 char *page, *body;
2576 if (t == NULL)
2577 show_oops_s("about invalid parameters");
2579 body = g_strdup_printf("<b>Version: %s</b><p>"
2580 "Authors:"
2581 "<ul>"
2582 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2583 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2584 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2585 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2586 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2587 "</ul>"
2588 "Copyrights and licenses can be found on the XXXterm "
2589 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2590 version
2593 page = get_html_page("About", body, "", 0);
2594 g_free(body);
2596 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2597 g_free(page);
2599 return (0);
2603 help(struct tab *t, struct karg *args)
2605 char *page, *head, *body;
2607 if (t == NULL)
2608 show_oops_s("help invalid parameters");
2610 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2611 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2612 "</head>\n";
2613 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2614 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2615 "cgi-bin/man-cgi?xxxterm</a>";
2617 page = get_html_page("XXXterm", body, head, FALSE);
2619 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2620 g_free(page);
2622 return (0);
2626 * update all favorite tabs apart from one. Pass NULL if
2627 * you want to update all.
2629 void
2630 update_favorite_tabs(struct tab *apart_from)
2632 struct tab *t;
2633 if (!updating_fl_tabs) {
2634 updating_fl_tabs = 1; /* stop infinite recursion */
2635 TAILQ_FOREACH(t, &tabs, entry)
2636 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2637 && (t != apart_from))
2638 xtp_page_fl(t, NULL);
2639 updating_fl_tabs = 0;
2643 /* show a list of favorites (bookmarks) */
2645 xtp_page_fl(struct tab *t, struct karg *args)
2647 char file[PATH_MAX];
2648 FILE *f;
2649 char *uri = NULL, *title = NULL;
2650 size_t len, lineno = 0;
2651 int i, failed = 0;
2652 char *body, *tmp, *page = NULL;
2653 const char delim[3] = {'\\', '\\', '\0'};
2655 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2657 if (t == NULL)
2658 warn("%s: bad param", __func__);
2660 /* mark tab as favorite list */
2661 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2663 /* new session key */
2664 if (!updating_fl_tabs)
2665 generate_xtp_session_key(&fl_session_key);
2667 /* open favorites */
2668 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2669 if ((f = fopen(file, "r")) == NULL) {
2670 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2671 return (1);
2674 /* body */
2675 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2676 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2677 "<th style='width: 40px'>Rm</th></tr>\n");
2679 for (i = 1;;) {
2680 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2681 if (feof(f) || ferror(f))
2682 break;
2683 if (len == 0) {
2684 free(title);
2685 title = NULL;
2686 continue;
2689 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2690 if (feof(f) || ferror(f)) {
2691 show_oops(t, "favorites file corrupt");
2692 failed = 1;
2693 break;
2696 tmp = body;
2697 body = g_strdup_printf("%s<tr>"
2698 "<td>%d</td>"
2699 "<td><a href='%s'>%s</a></td>"
2700 "<td style='text-align: center'>"
2701 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2702 "</tr>\n",
2703 body, i, uri, title,
2704 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2706 g_free(tmp);
2708 free(uri);
2709 uri = NULL;
2710 free(title);
2711 title = NULL;
2712 i++;
2714 fclose(f);
2716 /* if none, say so */
2717 if (i == 1) {
2718 tmp = body;
2719 body = g_strdup_printf("%s<tr>"
2720 "<td colspan='3' style='text-align: center'>"
2721 "No favorites - To add one use the 'favadd' command."
2722 "</td></tr>", body);
2723 g_free(tmp);
2726 tmp = body;
2727 body = g_strdup_printf("%s</table>", body);
2728 g_free(tmp);
2730 if (uri)
2731 free(uri);
2732 if (title)
2733 free(title);
2735 /* render */
2736 if (!failed) {
2737 page = get_html_page("Favorites", body, "", 1);
2738 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2739 g_free(page);
2742 update_favorite_tabs(t);
2744 if (body)
2745 g_free(body);
2747 return (failed);
2750 void
2751 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2752 size_t cert_count, char *title)
2754 gnutls_datum_t cinfo;
2755 char *tmp, *body;
2756 int i;
2758 body = g_strdup("");
2760 for (i = 0; i < cert_count; i++) {
2761 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2762 &cinfo))
2763 return;
2765 tmp = body;
2766 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2767 body, i, cinfo.data);
2768 gnutls_free(cinfo.data);
2769 g_free(tmp);
2772 tmp = get_html_page(title, body, "", 0);
2773 g_free(body);
2775 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2776 g_free(tmp);
2780 ca_cmd(struct tab *t, struct karg *args)
2782 FILE *f = NULL;
2783 int rv = 1, certs = 0, certs_read;
2784 struct stat sb;
2785 gnutls_datum dt;
2786 gnutls_x509_crt_t *c = NULL;
2787 char *certs_buf = NULL, *s;
2789 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2790 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2791 return (1);
2794 if (fstat(fileno(f), &sb) == -1) {
2795 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2796 goto done;
2799 certs_buf = g_malloc(sb.st_size + 1);
2800 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2801 show_oops(t, "Can't read CA file: %s", strerror(errno));
2802 goto done;
2804 certs_buf[sb.st_size] = '\0';
2806 s = certs_buf;
2807 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2808 certs++;
2809 s += strlen("BEGIN CERTIFICATE");
2812 bzero(&dt, sizeof dt);
2813 dt.data = certs_buf;
2814 dt.size = sb.st_size;
2815 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2816 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2817 GNUTLS_X509_FMT_PEM, 0);
2818 if (certs_read <= 0) {
2819 show_oops(t, "No cert(s) available");
2820 goto done;
2822 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2823 done:
2824 if (c)
2825 g_free(c);
2826 if (certs_buf)
2827 g_free(certs_buf);
2828 if (f)
2829 fclose(f);
2831 return (rv);
2835 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2837 SoupURI *su = NULL;
2838 struct addrinfo hints, *res = NULL, *ai;
2839 int s = -1, on;
2840 char port[8];
2842 if (uri && !g_str_has_prefix(uri, "https://"))
2843 goto done;
2845 su = soup_uri_new(uri);
2846 if (su == NULL)
2847 goto done;
2848 if (!SOUP_URI_VALID_FOR_HTTP(su))
2849 goto done;
2851 snprintf(port, sizeof port, "%d", su->port);
2852 bzero(&hints, sizeof(struct addrinfo));
2853 hints.ai_flags = AI_CANONNAME;
2854 hints.ai_family = AF_UNSPEC;
2855 hints.ai_socktype = SOCK_STREAM;
2857 if (getaddrinfo(su->host, port, &hints, &res))
2858 goto done;
2860 for (ai = res; ai; ai = ai->ai_next) {
2861 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2862 continue;
2864 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2865 if (s < 0)
2866 goto done;
2867 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2868 sizeof(on)) == -1)
2869 goto done;
2871 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2872 goto done;
2875 if (domain)
2876 strlcpy(domain, su->host, domain_sz);
2877 done:
2878 if (su)
2879 soup_uri_free(su);
2880 if (res)
2881 freeaddrinfo(res);
2883 return (s);
2887 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2889 if (gsession)
2890 gnutls_deinit(gsession);
2891 if (xcred)
2892 gnutls_certificate_free_credentials(xcred);
2894 return (0);
2898 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2899 gnutls_certificate_credentials_t *xc)
2901 gnutls_certificate_credentials_t xcred;
2902 gnutls_session_t gsession;
2903 int rv = 1;
2905 if (gs == NULL || xc == NULL)
2906 goto done;
2908 *gs = NULL;
2909 *xc = NULL;
2911 gnutls_certificate_allocate_credentials(&xcred);
2912 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2913 GNUTLS_X509_FMT_PEM);
2914 gnutls_init(&gsession, GNUTLS_CLIENT);
2915 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2916 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2917 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2918 if ((rv = gnutls_handshake(gsession)) < 0) {
2919 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2921 gnutls_error_is_fatal(rv),
2922 gnutls_strerror_name(rv));
2923 stop_tls(gsession, xcred);
2924 goto done;
2927 gnutls_credentials_type_t cred;
2928 cred = gnutls_auth_get_type(gsession);
2929 if (cred != GNUTLS_CRD_CERTIFICATE) {
2930 stop_tls(gsession, xcred);
2931 goto done;
2934 *gs = gsession;
2935 *xc = xcred;
2936 rv = 0;
2937 done:
2938 return (rv);
2942 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2943 size_t *cert_count)
2945 unsigned int len;
2946 const gnutls_datum_t *cl;
2947 gnutls_x509_crt_t *all_certs;
2948 int i, rv = 1;
2950 if (certs == NULL || cert_count == NULL)
2951 goto done;
2952 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2953 goto done;
2954 cl = gnutls_certificate_get_peers(gsession, &len);
2955 if (len == 0)
2956 goto done;
2958 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2959 for (i = 0; i < len; i++) {
2960 gnutls_x509_crt_init(&all_certs[i]);
2961 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2962 GNUTLS_X509_FMT_PEM < 0)) {
2963 g_free(all_certs);
2964 goto done;
2968 *certs = all_certs;
2969 *cert_count = len;
2970 rv = 0;
2971 done:
2972 return (rv);
2975 void
2976 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2978 int i;
2980 for (i = 0; i < cert_count; i++)
2981 gnutls_x509_crt_deinit(certs[i]);
2982 g_free(certs);
2985 void
2986 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2987 size_t cert_count, char *domain)
2989 size_t cert_buf_sz;
2990 char cert_buf[64 * 1024], file[PATH_MAX];
2991 int i;
2992 FILE *f;
2993 GdkColor color;
2995 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2996 return;
2998 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2999 if ((f = fopen(file, "w")) == NULL) {
3000 show_oops(t, "Can't create cert file %s %s",
3001 file, strerror(errno));
3002 return;
3005 for (i = 0; i < cert_count; i++) {
3006 cert_buf_sz = sizeof cert_buf;
3007 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3008 cert_buf, &cert_buf_sz)) {
3009 show_oops(t, "gnutls_x509_crt_export failed");
3010 goto done;
3012 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3013 show_oops(t, "Can't write certs: %s", strerror(errno));
3014 goto done;
3018 /* not the best spot but oh well */
3019 gdk_color_parse("lightblue", &color);
3020 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3021 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3022 gdk_color_parse(XT_COLOR_BLACK, &color);
3023 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3024 done:
3025 fclose(f);
3029 load_compare_cert(struct tab *t, struct karg *args)
3031 const gchar *uri;
3032 char domain[8182], file[PATH_MAX];
3033 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3034 int s = -1, rv = 1, i;
3035 size_t cert_count;
3036 FILE *f = NULL;
3037 size_t cert_buf_sz;
3038 gnutls_session_t gsession;
3039 gnutls_x509_crt_t *certs;
3040 gnutls_certificate_credentials_t xcred;
3042 if (t == NULL)
3043 return (1);
3045 if ((uri = get_uri(t->wv)) == NULL)
3046 return (1);
3048 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3049 return (1);
3051 /* go ssl/tls */
3052 if (start_tls(t, s, &gsession, &xcred)) {
3053 show_oops(t, "Start TLS failed");
3054 goto done;
3057 /* get certs */
3058 if (get_connection_certs(gsession, &certs, &cert_count)) {
3059 show_oops(t, "Can't get connection certificates");
3060 goto done;
3063 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3064 if ((f = fopen(file, "r")) == NULL)
3065 goto freeit;
3067 for (i = 0; i < cert_count; i++) {
3068 cert_buf_sz = sizeof cert_buf;
3069 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3070 cert_buf, &cert_buf_sz)) {
3071 goto freeit;
3073 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3074 rv = -1; /* critical */
3075 goto freeit;
3077 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3078 rv = -1; /* critical */
3079 goto freeit;
3083 rv = 0;
3084 freeit:
3085 if (f)
3086 fclose(f);
3087 free_connection_certs(certs, cert_count);
3088 done:
3089 /* we close the socket first for speed */
3090 if (s != -1)
3091 close(s);
3092 stop_tls(gsession, xcred);
3094 return (rv);
3098 cert_cmd(struct tab *t, struct karg *args)
3100 const gchar *uri;
3101 char domain[8182];
3102 int s = -1;
3103 size_t cert_count;
3104 gnutls_session_t gsession;
3105 gnutls_x509_crt_t *certs;
3106 gnutls_certificate_credentials_t xcred;
3108 if (t == NULL)
3109 return (1);
3111 if (ssl_ca_file == NULL) {
3112 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3113 return (1);
3116 if ((uri = get_uri(t->wv)) == NULL) {
3117 show_oops(t, "Invalid URI");
3118 return (1);
3121 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3122 show_oops(t, "Invalid certificate URI: %s", uri);
3123 return (1);
3126 /* go ssl/tls */
3127 if (start_tls(t, s, &gsession, &xcred)) {
3128 show_oops(t, "Start TLS failed");
3129 goto done;
3132 /* get certs */
3133 if (get_connection_certs(gsession, &certs, &cert_count)) {
3134 show_oops(t, "get_connection_certs failed");
3135 goto done;
3138 if (args->i & XT_SHOW)
3139 show_certs(t, certs, cert_count, "Certificate Chain");
3140 else if (args->i & XT_SAVE)
3141 save_certs(t, certs, cert_count, domain);
3143 free_connection_certs(certs, cert_count);
3144 done:
3145 /* we close the socket first for speed */
3146 if (s != -1)
3147 close(s);
3148 stop_tls(gsession, xcred);
3150 return (0);
3154 remove_cookie(int index)
3156 int i, rv = 1;
3157 GSList *cf;
3158 SoupCookie *c;
3160 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3162 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3164 for (i = 1; cf; cf = cf->next, i++) {
3165 if (i != index)
3166 continue;
3167 c = cf->data;
3168 print_cookie("remove cookie", c);
3169 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3170 rv = 0;
3171 break;
3174 soup_cookies_free(cf);
3176 return (rv);
3180 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3182 struct domain *d;
3183 char *tmp, *body;
3185 body = g_strdup("");
3187 /* p list */
3188 if (args->i & XT_WL_PERSISTENT) {
3189 tmp = body;
3190 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3191 g_free(tmp);
3192 RB_FOREACH(d, domain_list, wl) {
3193 if (d->handy == 0)
3194 continue;
3195 tmp = body;
3196 body = g_strdup_printf("%s%s<br/>", body, d->d);
3197 g_free(tmp);
3201 /* s list */
3202 if (args->i & XT_WL_SESSION) {
3203 tmp = body;
3204 body = g_strdup_printf("%s<h2>Session</h2>", body);
3205 g_free(tmp);
3206 RB_FOREACH(d, domain_list, wl) {
3207 if (d->handy == 1)
3208 continue;
3209 tmp = body;
3210 body = g_strdup_printf("%s%s<br/>", body, d->d);
3211 g_free(tmp);
3215 tmp = get_html_page(title, body, "", 0);
3216 g_free(body);
3217 if (wl == &js_wl)
3218 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3219 else
3220 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3221 g_free(tmp);
3222 return (0);
3226 wl_save(struct tab *t, struct karg *args, int js)
3228 char file[PATH_MAX];
3229 FILE *f;
3230 char *line = NULL, *lt = NULL;
3231 size_t linelen;
3232 const gchar *uri;
3233 char *dom = NULL, *dom_save = NULL;
3234 struct karg a;
3235 struct domain *d;
3236 GSList *cf;
3237 SoupCookie *ci, *c;
3239 if (t == NULL || args == NULL)
3240 return (1);
3242 if (runtime_settings[0] == '\0')
3243 return (1);
3245 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3246 if ((f = fopen(file, "r+")) == NULL)
3247 return (1);
3249 uri = get_uri(t->wv);
3250 dom = find_domain(uri, 1);
3251 if (uri == NULL || dom == NULL) {
3252 show_oops(t, "Can't add domain to %s white list",
3253 js ? "JavaScript" : "cookie");
3254 goto done;
3257 if (args->i & XT_WL_TOPLEVEL) {
3258 /* save domain */
3259 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3260 show_oops(t, "invalid domain: %s", dom);
3261 goto done;
3263 } else if (args->i & XT_WL_FQDN) {
3264 /* save fqdn */
3265 dom_save = dom;
3266 } else
3267 goto done;
3269 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3271 while (!feof(f)) {
3272 line = fparseln(f, &linelen, NULL, NULL, 0);
3273 if (line == NULL)
3274 continue;
3275 if (!strcmp(line, lt))
3276 goto done;
3277 free(line);
3278 line = NULL;
3281 fprintf(f, "%s\n", lt);
3283 a.i = XT_WL_ENABLE;
3284 a.i |= args->i;
3285 if (js) {
3286 d = wl_find(dom_save, &js_wl);
3287 if (!d) {
3288 settings_add("js_wl", dom_save);
3289 d = wl_find(dom_save, &js_wl);
3291 toggle_js(t, &a);
3292 } else {
3293 d = wl_find(dom_save, &c_wl);
3294 if (!d) {
3295 settings_add("cookie_wl", dom_save);
3296 d = wl_find(dom_save, &c_wl);
3298 toggle_cwl(t, &a);
3300 /* find and add to persistent jar */
3301 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3302 for (;cf; cf = cf->next) {
3303 ci = cf->data;
3304 if (!strcmp(dom_save, ci->domain) ||
3305 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3306 c = soup_cookie_copy(ci);
3307 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3310 soup_cookies_free(cf);
3312 if (d)
3313 d->handy = 1;
3315 done:
3316 if (line)
3317 free(line);
3318 if (dom)
3319 g_free(dom);
3320 if (lt)
3321 g_free(lt);
3322 fclose(f);
3324 return (0);
3328 js_show_wl(struct tab *t, struct karg *args)
3330 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3331 wl_show(t, args, "JavaScript White List", &js_wl);
3333 return (0);
3337 cookie_show_wl(struct tab *t, struct karg *args)
3339 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3340 wl_show(t, args, "Cookie White List", &c_wl);
3342 return (0);
3346 cookie_cmd(struct tab *t, struct karg *args)
3348 if (args->i & XT_SHOW)
3349 wl_show(t, args, "Cookie White List", &c_wl);
3350 else if (args->i & XT_WL_TOGGLE)
3351 toggle_cwl(t, args);
3352 else if (args->i & XT_SAVE)
3353 wl_save(t, args, 0);
3354 else if (args->i & XT_DELETE)
3355 show_oops(t, "'cookie delete' currently unimplemented");
3357 return (0);
3361 js_cmd(struct tab *t, struct karg *args)
3363 if (args->i & XT_SHOW)
3364 wl_show(t, args, "JavaScript White List", &js_wl);
3365 else if (args->i & XT_SAVE)
3366 wl_save(t, args, 1);
3367 else if (args->i & XT_WL_TOGGLE)
3368 toggle_js(t, args);
3369 else if (args->i & XT_DELETE)
3370 show_oops(t, "'js delete' currently unimplemented");
3372 return (0);
3376 add_favorite(struct tab *t, struct karg *args)
3378 char file[PATH_MAX];
3379 FILE *f;
3380 char *line = NULL;
3381 size_t urilen, linelen;
3382 const gchar *uri, *title;
3384 if (t == NULL)
3385 return (1);
3387 /* don't allow adding of xtp pages to favorites */
3388 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3389 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3390 return (1);
3393 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3394 if ((f = fopen(file, "r+")) == NULL) {
3395 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3396 return (1);
3399 title = webkit_web_view_get_title(t->wv);
3400 uri = get_uri(t->wv);
3402 if (title == NULL)
3403 title = uri;
3405 if (title == NULL || uri == NULL) {
3406 show_oops(t, "can't add page to favorites");
3407 goto done;
3410 urilen = strlen(uri);
3412 for (;;) {
3413 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3414 if (feof(f) || ferror(f))
3415 break;
3417 if (linelen == urilen && !strcmp(line, uri))
3418 goto done;
3420 free(line);
3421 line = NULL;
3424 fprintf(f, "\n%s\n%s", title, uri);
3425 done:
3426 if (line)
3427 free(line);
3428 fclose(f);
3430 update_favorite_tabs(NULL);
3432 return (0);
3436 navaction(struct tab *t, struct karg *args)
3438 WebKitWebHistoryItem *item;
3440 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3441 t->tab_id, args->i);
3443 if (t->item) {
3444 if (args->i == XT_NAV_BACK)
3445 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3446 else
3447 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3448 if (item == NULL)
3449 return (XT_CB_PASSTHROUGH);
3450 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3451 t->item = NULL;
3452 return (XT_CB_PASSTHROUGH);
3455 switch (args->i) {
3456 case XT_NAV_BACK:
3457 webkit_web_view_go_back(t->wv);
3458 break;
3459 case XT_NAV_FORWARD:
3460 webkit_web_view_go_forward(t->wv);
3461 break;
3462 case XT_NAV_RELOAD:
3463 webkit_web_view_reload(t->wv);
3464 break;
3465 case XT_NAV_RELOAD_CACHE:
3466 webkit_web_view_reload_bypass_cache(t->wv);
3467 break;
3469 return (XT_CB_PASSTHROUGH);
3473 move(struct tab *t, struct karg *args)
3475 GtkAdjustment *adjust;
3476 double pi, si, pos, ps, upper, lower, max;
3478 switch (args->i) {
3479 case XT_MOVE_DOWN:
3480 case XT_MOVE_UP:
3481 case XT_MOVE_BOTTOM:
3482 case XT_MOVE_TOP:
3483 case XT_MOVE_PAGEDOWN:
3484 case XT_MOVE_PAGEUP:
3485 case XT_MOVE_HALFDOWN:
3486 case XT_MOVE_HALFUP:
3487 adjust = t->adjust_v;
3488 break;
3489 default:
3490 adjust = t->adjust_h;
3491 break;
3494 pos = gtk_adjustment_get_value(adjust);
3495 ps = gtk_adjustment_get_page_size(adjust);
3496 upper = gtk_adjustment_get_upper(adjust);
3497 lower = gtk_adjustment_get_lower(adjust);
3498 si = gtk_adjustment_get_step_increment(adjust);
3499 pi = gtk_adjustment_get_page_increment(adjust);
3500 max = upper - ps;
3502 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3503 "max %f si %f pi %f\n",
3504 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3505 pos, ps, upper, lower, max, si, pi);
3507 switch (args->i) {
3508 case XT_MOVE_DOWN:
3509 case XT_MOVE_RIGHT:
3510 pos += si;
3511 gtk_adjustment_set_value(adjust, MIN(pos, max));
3512 break;
3513 case XT_MOVE_UP:
3514 case XT_MOVE_LEFT:
3515 pos -= si;
3516 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3517 break;
3518 case XT_MOVE_BOTTOM:
3519 case XT_MOVE_FARRIGHT:
3520 gtk_adjustment_set_value(adjust, max);
3521 break;
3522 case XT_MOVE_TOP:
3523 case XT_MOVE_FARLEFT:
3524 gtk_adjustment_set_value(adjust, lower);
3525 break;
3526 case XT_MOVE_PAGEDOWN:
3527 pos += pi;
3528 gtk_adjustment_set_value(adjust, MIN(pos, max));
3529 break;
3530 case XT_MOVE_PAGEUP:
3531 pos -= pi;
3532 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3533 break;
3534 case XT_MOVE_HALFDOWN:
3535 pos += pi / 2;
3536 gtk_adjustment_set_value(adjust, MIN(pos, max));
3537 break;
3538 case XT_MOVE_HALFUP:
3539 pos -= pi / 2;
3540 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3541 break;
3542 default:
3543 return (XT_CB_PASSTHROUGH);
3546 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3548 return (XT_CB_HANDLED);
3551 void
3552 url_set_visibility(void)
3554 struct tab *t;
3556 TAILQ_FOREACH(t, &tabs, entry) {
3557 if (show_url == 0) {
3558 gtk_widget_hide(t->toolbar);
3559 focus_webview(t);
3560 } else
3561 gtk_widget_show(t->toolbar);
3565 void
3566 notebook_tab_set_visibility(GtkNotebook *notebook)
3568 if (show_tabs == 0)
3569 gtk_notebook_set_show_tabs(notebook, FALSE);
3570 else
3571 gtk_notebook_set_show_tabs(notebook, TRUE);
3574 void
3575 statusbar_set_visibility(void)
3577 struct tab *t;
3579 TAILQ_FOREACH(t, &tabs, entry) {
3580 if (show_statusbar == 0) {
3581 gtk_widget_hide(t->statusbar);
3582 focus_webview(t);
3583 } else
3584 gtk_widget_show(t->statusbar);
3588 void
3589 url_set(struct tab *t, int enable_url_entry)
3591 GdkPixbuf *pixbuf;
3592 int progress;
3594 show_url = enable_url_entry;
3596 if (enable_url_entry) {
3597 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3598 GTK_ENTRY_ICON_PRIMARY, NULL);
3599 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3600 } else {
3601 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3602 GTK_ENTRY_ICON_PRIMARY);
3603 progress =
3604 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3605 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3606 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3607 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3608 progress);
3613 fullscreen(struct tab *t, struct karg *args)
3615 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3617 if (t == NULL)
3618 return (XT_CB_PASSTHROUGH);
3620 if (show_url == 0) {
3621 url_set(t, 1);
3622 show_tabs = 1;
3623 } else {
3624 url_set(t, 0);
3625 show_tabs = 0;
3628 url_set_visibility();
3629 notebook_tab_set_visibility(notebook);
3631 return (XT_CB_HANDLED);
3635 statusaction(struct tab *t, struct karg *args)
3637 int rv = XT_CB_HANDLED;
3639 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3641 if (t == NULL)
3642 return (XT_CB_PASSTHROUGH);
3644 switch (args->i) {
3645 case XT_STATUSBAR_SHOW:
3646 if (show_statusbar == 0) {
3647 show_statusbar = 1;
3648 statusbar_set_visibility();
3650 break;
3651 case XT_STATUSBAR_HIDE:
3652 if (show_statusbar == 1) {
3653 show_statusbar = 0;
3654 statusbar_set_visibility();
3656 break;
3658 return (rv);
3662 urlaction(struct tab *t, struct karg *args)
3664 int rv = XT_CB_HANDLED;
3666 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3668 if (t == NULL)
3669 return (XT_CB_PASSTHROUGH);
3671 switch (args->i) {
3672 case XT_URL_SHOW:
3673 if (show_url == 0) {
3674 url_set(t, 1);
3675 url_set_visibility();
3677 break;
3678 case XT_URL_HIDE:
3679 if (show_url == 1) {
3680 url_set(t, 0);
3681 url_set_visibility();
3683 break;
3685 return (rv);
3689 tabaction(struct tab *t, struct karg *args)
3691 int rv = XT_CB_HANDLED;
3692 char *url = args->s;
3693 struct undo *u;
3694 struct tab *tt;
3696 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3698 if (t == NULL)
3699 return (XT_CB_PASSTHROUGH);
3701 switch (args->i) {
3702 case XT_TAB_NEW:
3703 if (strlen(url) > 0)
3704 create_new_tab(url, NULL, 1, args->p);
3705 else
3706 create_new_tab(NULL, NULL, 1, args->p);
3707 break;
3708 case XT_TAB_DELETE:
3709 if (args->p < 0)
3710 delete_tab(t);
3711 else
3712 TAILQ_FOREACH(tt, &tabs, entry)
3713 if (tt->tab_id == args->p - 1) {
3714 delete_tab(tt);
3715 recalc_tabs();
3716 break;
3718 break;
3719 case XT_TAB_DELQUIT:
3720 if (gtk_notebook_get_n_pages(notebook) > 1)
3721 delete_tab(t);
3722 else
3723 quit(t, args);
3724 break;
3725 case XT_TAB_OPEN:
3726 if (strlen(url) > 0)
3728 else {
3729 rv = XT_CB_PASSTHROUGH;
3730 goto done;
3732 load_uri(t, url);
3733 break;
3734 case XT_TAB_SHOW:
3735 if (show_tabs == 0) {
3736 show_tabs = 1;
3737 notebook_tab_set_visibility(notebook);
3739 break;
3740 case XT_TAB_HIDE:
3741 if (show_tabs == 1) {
3742 show_tabs = 0;
3743 notebook_tab_set_visibility(notebook);
3745 break;
3746 case XT_TAB_UNDO_CLOSE:
3747 if (undo_count == 0) {
3748 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3749 goto done;
3750 } else {
3751 undo_count--;
3752 u = TAILQ_FIRST(&undos);
3753 create_new_tab(u->uri, u, 1, -1);
3755 TAILQ_REMOVE(&undos, u, entry);
3756 g_free(u->uri);
3757 /* u->history is freed in create_new_tab() */
3758 g_free(u);
3760 break;
3761 default:
3762 rv = XT_CB_PASSTHROUGH;
3763 goto done;
3766 done:
3767 if (args->s) {
3768 g_free(args->s);
3769 args->s = NULL;
3772 return (rv);
3776 resizetab(struct tab *t, struct karg *args)
3778 if (t == NULL || args == NULL) {
3779 show_oops_s("resizetab invalid parameters");
3780 return (XT_CB_PASSTHROUGH);
3783 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3784 t->tab_id, args->i);
3786 adjustfont_webkit(t, args->i);
3788 return (XT_CB_HANDLED);
3792 movetab(struct tab *t, struct karg *args)
3794 int n, dest;
3796 if (t == NULL || args == NULL) {
3797 show_oops_s("movetab invalid parameters");
3798 return (XT_CB_PASSTHROUGH);
3801 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3802 t->tab_id, args->i);
3804 if (args->i >= XT_TAB_INVALID)
3805 return (XT_CB_PASSTHROUGH);
3807 if (TAILQ_EMPTY(&tabs))
3808 return (XT_CB_PASSTHROUGH);
3810 n = gtk_notebook_get_n_pages(notebook);
3811 dest = gtk_notebook_get_current_page(notebook);
3813 switch (args->i) {
3814 case XT_TAB_NEXT:
3815 if (args->p < 0)
3816 dest = dest == n - 1 ? 0 : dest + 1;
3817 else
3818 dest = args->p - 1;
3820 break;
3821 case XT_TAB_PREV:
3822 if (args->p < 0)
3823 dest -= 1;
3824 else
3825 dest -= args->p % n;
3827 if (dest < 0)
3828 dest += n;
3830 break;
3831 case XT_TAB_FIRST:
3832 dest = 0;
3833 break;
3834 case XT_TAB_LAST:
3835 dest = n - 1;
3836 break;
3837 default:
3838 return (XT_CB_PASSTHROUGH);
3841 if (dest < 0 || dest >= n)
3842 return (XT_CB_PASSTHROUGH);
3843 if (t->tab_id == dest) {
3844 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3845 return (XT_CB_HANDLED);
3848 gtk_notebook_set_current_page(notebook, dest);
3850 return (XT_CB_HANDLED);
3853 int cmd_prefix = 0;
3856 command(struct tab *t, struct karg *args)
3858 char *s = NULL, *ss = NULL;
3859 GdkColor color;
3860 const gchar *uri;
3862 if (t == NULL || args == NULL) {
3863 show_oops_s("command invalid parameters");
3864 return (XT_CB_PASSTHROUGH);
3867 switch (args->i) {
3868 case '/':
3869 s = "/";
3870 break;
3871 case '?':
3872 s = "?";
3873 break;
3874 case ':':
3875 if (cmd_prefix == 0)
3876 s = ":";
3877 else {
3878 ss = g_strdup_printf(":%d", cmd_prefix);
3879 s = ss;
3880 cmd_prefix = 0;
3882 break;
3883 case XT_CMD_OPEN:
3884 s = ":open ";
3885 break;
3886 case XT_CMD_TABNEW:
3887 s = ":tabnew ";
3888 break;
3889 case XT_CMD_OPEN_CURRENT:
3890 s = ":open ";
3891 /* FALL THROUGH */
3892 case XT_CMD_TABNEW_CURRENT:
3893 if (!s) /* FALL THROUGH? */
3894 s = ":tabnew ";
3895 if ((uri = get_uri(t->wv)) != NULL) {
3896 ss = g_strdup_printf("%s%s", s, uri);
3897 s = ss;
3899 break;
3900 default:
3901 show_oops(t, "command: invalid opcode %d", args->i);
3902 return (XT_CB_PASSTHROUGH);
3905 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3907 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3908 gdk_color_parse(XT_COLOR_WHITE, &color);
3909 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3910 show_cmd(t);
3911 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3912 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3914 if (ss)
3915 g_free(ss);
3917 return (XT_CB_HANDLED);
3921 * Return a new string with a download row (in html)
3922 * appended. Old string is freed.
3924 char *
3925 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3928 WebKitDownloadStatus stat;
3929 char *status_html = NULL, *cmd_html = NULL, *new_html;
3930 gdouble progress;
3931 char cur_sz[FMT_SCALED_STRSIZE];
3932 char tot_sz[FMT_SCALED_STRSIZE];
3933 char *xtp_prefix;
3935 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3937 /* All actions wil take this form:
3938 * xxxt://class/seskey
3940 xtp_prefix = g_strdup_printf("%s%d/%s/",
3941 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3943 stat = webkit_download_get_status(dl->download);
3945 switch (stat) {
3946 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3947 status_html = g_strdup_printf("Finished");
3948 cmd_html = g_strdup_printf(
3949 "<a href='%s%d/%d'>Remove</a>",
3950 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3951 break;
3952 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3953 /* gather size info */
3954 progress = 100 * webkit_download_get_progress(dl->download);
3956 fmt_scaled(
3957 webkit_download_get_current_size(dl->download), cur_sz);
3958 fmt_scaled(
3959 webkit_download_get_total_size(dl->download), tot_sz);
3961 status_html = g_strdup_printf(
3962 "<div style='width: 100%%' align='center'>"
3963 "<div class='progress-outer'>"
3964 "<div class='progress-inner' style='width: %.2f%%'>"
3965 "</div></div></div>"
3966 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3967 progress, cur_sz, tot_sz, progress);
3969 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3970 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3972 break;
3973 /* LLL */
3974 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3975 status_html = g_strdup_printf("Cancelled");
3976 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3977 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3978 break;
3979 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3980 status_html = g_strdup_printf("Error!");
3981 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3982 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3983 break;
3984 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3985 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3986 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3987 status_html = g_strdup_printf("Starting");
3988 break;
3989 default:
3990 show_oops(t, "%s: unknown download status", __func__);
3993 new_html = g_strdup_printf(
3994 "%s\n<tr><td>%s</td><td>%s</td>"
3995 "<td style='text-align:center'>%s</td></tr>\n",
3996 html, basename(webkit_download_get_destination_uri(dl->download)),
3997 status_html, cmd_html);
3998 g_free(html);
4000 if (status_html)
4001 g_free(status_html);
4003 if (cmd_html)
4004 g_free(cmd_html);
4006 g_free(xtp_prefix);
4008 return new_html;
4012 * update all download tabs apart from one. Pass NULL if
4013 * you want to update all.
4015 void
4016 update_download_tabs(struct tab *apart_from)
4018 struct tab *t;
4019 if (!updating_dl_tabs) {
4020 updating_dl_tabs = 1; /* stop infinite recursion */
4021 TAILQ_FOREACH(t, &tabs, entry)
4022 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4023 && (t != apart_from))
4024 xtp_page_dl(t, NULL);
4025 updating_dl_tabs = 0;
4030 * update all cookie tabs apart from one. Pass NULL if
4031 * you want to update all.
4033 void
4034 update_cookie_tabs(struct tab *apart_from)
4036 struct tab *t;
4037 if (!updating_cl_tabs) {
4038 updating_cl_tabs = 1; /* stop infinite recursion */
4039 TAILQ_FOREACH(t, &tabs, entry)
4040 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4041 && (t != apart_from))
4042 xtp_page_cl(t, NULL);
4043 updating_cl_tabs = 0;
4048 * update all history tabs apart from one. Pass NULL if
4049 * you want to update all.
4051 void
4052 update_history_tabs(struct tab *apart_from)
4054 struct tab *t;
4056 if (!updating_hl_tabs) {
4057 updating_hl_tabs = 1; /* stop infinite recursion */
4058 TAILQ_FOREACH(t, &tabs, entry)
4059 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4060 && (t != apart_from))
4061 xtp_page_hl(t, NULL);
4062 updating_hl_tabs = 0;
4066 /* cookie management XTP page */
4068 xtp_page_cl(struct tab *t, struct karg *args)
4070 char *body, *page, *tmp;
4071 int i = 1; /* all ids start 1 */
4072 GSList *sc, *pc, *pc_start;
4073 SoupCookie *c;
4074 char *type, *table_headers;
4075 char *last_domain = strdup("");
4077 DNPRINTF(XT_D_CMD, "%s", __func__);
4079 if (t == NULL) {
4080 show_oops_s("%s invalid parameters", __func__);
4081 return (1);
4083 /* mark this tab as cookie jar */
4084 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4086 /* Generate a new session key */
4087 if (!updating_cl_tabs)
4088 generate_xtp_session_key(&cl_session_key);
4090 /* table headers */
4091 table_headers = g_strdup_printf("<table><tr>"
4092 "<th>Type</th>"
4093 "<th>Name</th>"
4094 "<th style='width:200px'>Value</th>"
4095 "<th>Path</th>"
4096 "<th>Expires</th>"
4097 "<th>Secure</th>"
4098 "<th>HTTP<br />only</th>"
4099 "<th style='width:40px'>Rm</th></tr>\n");
4101 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4102 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4103 pc_start = pc;
4105 body = NULL;
4106 for (; sc; sc = sc->next) {
4107 c = sc->data;
4109 if (strcmp(last_domain, c->domain) != 0) {
4110 /* new domain */
4111 free(last_domain);
4112 last_domain = strdup(c->domain);
4114 if (body != NULL) {
4115 tmp = body;
4116 body = g_strdup_printf("%s</table>"
4117 "<h2>%s</h2>%s\n",
4118 body, c->domain, table_headers);
4119 g_free(tmp);
4120 } else {
4121 /* first domain */
4122 body = g_strdup_printf("<h2>%s</h2>%s\n",
4123 c->domain, table_headers);
4127 type = "Session";
4128 for (pc = pc_start; pc; pc = pc->next)
4129 if (soup_cookie_equal(pc->data, c)) {
4130 type = "Session + Persistent";
4131 break;
4134 tmp = body;
4135 body = g_strdup_printf(
4136 "%s\n<tr>"
4137 "<td>%s</td>"
4138 "<td style='word-wrap:normal'>%s</td>"
4139 "<td>"
4140 " <textarea rows='4'>%s</textarea>"
4141 "</td>"
4142 "<td>%s</td>"
4143 "<td>%s</td>"
4144 "<td>%d</td>"
4145 "<td>%d</td>"
4146 "<td style='text-align:center'>"
4147 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4148 body,
4149 type,
4150 c->name,
4151 c->value,
4152 c->path,
4153 c->expires ?
4154 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4155 c->secure,
4156 c->http_only,
4158 XT_XTP_STR,
4159 XT_XTP_CL,
4160 cl_session_key,
4161 XT_XTP_CL_REMOVE,
4165 g_free(tmp);
4166 i++;
4169 soup_cookies_free(sc);
4170 soup_cookies_free(pc);
4172 /* small message if there are none */
4173 if (i == 1) {
4174 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4175 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4177 tmp = body;
4178 body = g_strdup_printf("%s</table>", body);
4179 g_free(tmp);
4181 page = get_html_page("Cookie Jar", body, "", TRUE);
4182 g_free(body);
4183 g_free(table_headers);
4184 g_free(last_domain);
4186 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4187 update_cookie_tabs(t);
4189 g_free(page);
4191 return (0);
4195 xtp_page_hl(struct tab *t, struct karg *args)
4197 char *body, *page, *tmp;
4198 struct history *h;
4199 int i = 1; /* all ids start 1 */
4201 DNPRINTF(XT_D_CMD, "%s", __func__);
4203 if (t == NULL) {
4204 show_oops_s("%s invalid parameters", __func__);
4205 return (1);
4208 /* mark this tab as history manager */
4209 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4211 /* Generate a new session key */
4212 if (!updating_hl_tabs)
4213 generate_xtp_session_key(&hl_session_key);
4215 /* body */
4216 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4217 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4219 RB_FOREACH_REVERSE(h, history_list, &hl) {
4220 tmp = body;
4221 body = g_strdup_printf(
4222 "%s\n<tr>"
4223 "<td><a href='%s'>%s</a></td>"
4224 "<td>%s</td>"
4225 "<td style='text-align: center'>"
4226 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4227 body, h->uri, h->uri, h->title,
4228 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4229 XT_XTP_HL_REMOVE, i);
4231 g_free(tmp);
4232 i++;
4235 /* small message if there are none */
4236 if (i == 1) {
4237 tmp = body;
4238 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4239 "colspan='3'>No History</td></tr>\n", body);
4240 g_free(tmp);
4243 tmp = body;
4244 body = g_strdup_printf("%s</table>", body);
4245 g_free(tmp);
4247 page = get_html_page("History", body, "", TRUE);
4248 g_free(body);
4251 * update all history manager tabs as the xtp session
4252 * key has now changed. No need to update the current tab.
4253 * Already did that above.
4255 update_history_tabs(t);
4257 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4258 g_free(page);
4260 return (0);
4264 * Generate a web page detailing the status of any downloads
4267 xtp_page_dl(struct tab *t, struct karg *args)
4269 struct download *dl;
4270 char *body, *page, *tmp;
4271 char *ref;
4272 int n_dl = 1;
4274 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4276 if (t == NULL) {
4277 show_oops_s("%s invalid parameters", __func__);
4278 return (1);
4280 /* mark as a download manager tab */
4281 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4284 * Generate a new session key for next page instance.
4285 * This only happens for the top level call to xtp_page_dl()
4286 * in which case updating_dl_tabs is 0.
4288 if (!updating_dl_tabs)
4289 generate_xtp_session_key(&dl_session_key);
4291 /* header - with refresh so as to update */
4292 if (refresh_interval >= 1)
4293 ref = g_strdup_printf(
4294 "<meta http-equiv='refresh' content='%u"
4295 ";url=%s%d/%s/%d' />\n",
4296 refresh_interval,
4297 XT_XTP_STR,
4298 XT_XTP_DL,
4299 dl_session_key,
4300 XT_XTP_DL_LIST);
4301 else
4302 ref = g_strdup("");
4304 body = g_strdup_printf("<div align='center'>"
4305 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4306 "</p><table><tr><th style='width: 60%%'>"
4307 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4308 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4310 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4311 body = xtp_page_dl_row(t, body, dl);
4312 n_dl++;
4315 /* message if no downloads in list */
4316 if (n_dl == 1) {
4317 tmp = body;
4318 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4319 " style='text-align: center'>"
4320 "No downloads</td></tr>\n", body);
4321 g_free(tmp);
4324 tmp = body;
4325 body = g_strdup_printf("%s</table></div>", body);
4326 g_free(tmp);
4328 page = get_html_page("Downloads", body, ref, 1);
4329 g_free(ref);
4330 g_free(body);
4333 * update all download manager tabs as the xtp session
4334 * key has now changed. No need to update the current tab.
4335 * Already did that above.
4337 update_download_tabs(t);
4339 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4340 g_free(page);
4342 return (0);
4346 search(struct tab *t, struct karg *args)
4348 gboolean d;
4350 if (t == NULL || args == NULL) {
4351 show_oops_s("search invalid parameters");
4352 return (1);
4354 if (t->search_text == NULL) {
4355 if (global_search == NULL)
4356 return (XT_CB_PASSTHROUGH);
4357 else {
4358 t->search_text = g_strdup(global_search);
4359 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4360 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4364 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4365 t->tab_id, args->i, t->search_forward, t->search_text);
4367 switch (args->i) {
4368 case XT_SEARCH_NEXT:
4369 d = t->search_forward;
4370 break;
4371 case XT_SEARCH_PREV:
4372 d = !t->search_forward;
4373 break;
4374 default:
4375 return (XT_CB_PASSTHROUGH);
4378 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4380 return (XT_CB_HANDLED);
4383 struct settings_args {
4384 char **body;
4385 int i;
4388 void
4389 print_setting(struct settings *s, char *val, void *cb_args)
4391 char *tmp, *color;
4392 struct settings_args *sa = cb_args;
4394 if (sa == NULL)
4395 return;
4397 if (s->flags & XT_SF_RUNTIME)
4398 color = "#22cc22";
4399 else
4400 color = "#cccccc";
4402 tmp = *sa->body;
4403 *sa->body = g_strdup_printf(
4404 "%s\n<tr>"
4405 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4406 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4407 *sa->body,
4408 color,
4409 s->name,
4410 color,
4413 g_free(tmp);
4414 sa->i++;
4418 set(struct tab *t, struct karg *args)
4420 char *body, *page, *tmp;
4421 int i = 1;
4422 struct settings_args sa;
4424 bzero(&sa, sizeof sa);
4425 sa.body = &body;
4427 /* body */
4428 body = g_strdup_printf("<div align='center'><table><tr>"
4429 "<th align='left'>Setting</th>"
4430 "<th align='left'>Value</th></tr>\n");
4432 settings_walk(print_setting, &sa);
4433 i = sa.i;
4435 /* small message if there are none */
4436 if (i == 1) {
4437 tmp = body;
4438 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4439 "colspan='2'>No settings</td></tr>\n", body);
4440 g_free(tmp);
4443 tmp = body;
4444 body = g_strdup_printf("%s</table></div>", body);
4445 g_free(tmp);
4447 page = get_html_page("Settings", body, "", 0);
4449 g_free(body);
4451 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4453 g_free(page);
4455 return (XT_CB_PASSTHROUGH);
4459 session_save(struct tab *t, char *filename)
4461 struct karg a;
4462 int rv = 1;
4464 if (strlen(filename) == 0)
4465 goto done;
4467 if (filename[0] == '.' || filename[0] == '/')
4468 goto done;
4470 a.s = filename;
4471 if (save_tabs(t, &a))
4472 goto done;
4473 strlcpy(named_session, filename, sizeof named_session);
4475 rv = 0;
4476 done:
4477 return (rv);
4481 session_open(struct tab *t, char *filename)
4483 struct karg a;
4484 int rv = 1;
4486 if (strlen(filename) == 0)
4487 goto done;
4489 if (filename[0] == '.' || filename[0] == '/')
4490 goto done;
4492 a.s = filename;
4493 a.i = XT_SES_CLOSETABS;
4494 if (open_tabs(t, &a))
4495 goto done;
4497 strlcpy(named_session, filename, sizeof named_session);
4499 rv = 0;
4500 done:
4501 return (rv);
4505 session_delete(struct tab *t, char *filename)
4507 char file[PATH_MAX];
4508 int rv = 1;
4510 if (strlen(filename) == 0)
4511 goto done;
4513 if (filename[0] == '.' || filename[0] == '/')
4514 goto done;
4516 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4517 if (unlink(file))
4518 goto done;
4520 if (!strcmp(filename, named_session))
4521 strlcpy(named_session, XT_SAVED_TABS_FILE,
4522 sizeof named_session);
4524 rv = 0;
4525 done:
4526 return (rv);
4530 session_cmd(struct tab *t, struct karg *args)
4532 char *filename = args->s;
4534 if (t == NULL)
4535 return (1);
4537 if (args->i & XT_SHOW)
4538 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4539 XT_SAVED_TABS_FILE : named_session);
4540 else if (args->i & XT_SAVE) {
4541 if (session_save(t, filename)) {
4542 show_oops(t, "Can't save session: %s",
4543 filename ? filename : "INVALID");
4544 goto done;
4546 } else if (args->i & XT_OPEN) {
4547 if (session_open(t, filename)) {
4548 show_oops(t, "Can't open session: %s",
4549 filename ? filename : "INVALID");
4550 goto done;
4552 } else if (args->i & XT_DELETE) {
4553 if (session_delete(t, filename)) {
4554 show_oops(t, "Can't delete session: %s",
4555 filename ? filename : "INVALID");
4556 goto done;
4559 done:
4560 return (XT_CB_PASSTHROUGH);
4564 * Make a hardcopy of the page
4567 print_page(struct tab *t, struct karg *args)
4569 WebKitWebFrame *frame;
4570 GtkPageSetup *ps;
4571 GtkPrintOperation *op;
4572 GtkPrintOperationAction action;
4573 GtkPrintOperationResult print_res;
4574 GError *g_err = NULL;
4575 int marg_l, marg_r, marg_t, marg_b;
4577 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4579 ps = gtk_page_setup_new();
4580 op = gtk_print_operation_new();
4581 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4582 frame = webkit_web_view_get_main_frame(t->wv);
4584 /* the default margins are too small, so we will bump them */
4585 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4586 XT_PRINT_EXTRA_MARGIN;
4587 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4588 XT_PRINT_EXTRA_MARGIN;
4589 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4590 XT_PRINT_EXTRA_MARGIN;
4591 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4592 XT_PRINT_EXTRA_MARGIN;
4594 /* set margins */
4595 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4596 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4597 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4598 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4600 gtk_print_operation_set_default_page_setup(op, ps);
4602 /* this appears to free 'op' and 'ps' */
4603 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4605 /* check it worked */
4606 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4607 show_oops_s("can't print: %s", g_err->message);
4608 g_error_free (g_err);
4609 return (1);
4612 return (0);
4616 go_home(struct tab *t, struct karg *args)
4618 load_uri(t, home);
4619 return (0);
4623 restart(struct tab *t, struct karg *args)
4625 struct karg a;
4627 a.s = XT_RESTART_TABS_FILE;
4628 save_tabs(t, &a);
4629 execvp(start_argv[0], start_argv);
4630 /* NOTREACHED */
4632 return (0);
4635 #define CTRL GDK_CONTROL_MASK
4636 #define MOD1 GDK_MOD1_MASK
4637 #define SHFT GDK_SHIFT_MASK
4639 /* inherent to GTK not all keys will be caught at all times */
4640 /* XXX sort key bindings */
4641 struct key_binding {
4642 char *cmd;
4643 guint mask;
4644 guint use_in_entry;
4645 guint key;
4646 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4647 } keys[] = {
4648 { "cookiejar", MOD1, 0, GDK_j },
4649 { "downloadmgr", MOD1, 0, GDK_d },
4650 { "history", MOD1, 0, GDK_h },
4651 { "print", CTRL, 0, GDK_p },
4652 { "search", 0, 0, GDK_slash },
4653 { "searchb", 0, 0, GDK_question },
4654 { "command", 0, 0, GDK_colon },
4655 { "qa", CTRL, 0, GDK_q },
4656 { "restart", MOD1, 0, GDK_q },
4657 { "js toggle", CTRL, 0, GDK_j },
4658 { "cookie toggle", MOD1, 0, GDK_c },
4659 { "togglesrc", CTRL, 0, GDK_s },
4660 { "yankuri", 0, 0, GDK_y },
4661 { "pasteuricur", 0, 0, GDK_p },
4662 { "pasteurinew", 0, 0, GDK_P },
4664 /* search */
4665 { "searchnext", 0, 0, GDK_n },
4666 { "searchprevious", 0, 0, GDK_N },
4668 /* focus */
4669 { "focusaddress", 0, 0, GDK_F6 },
4670 { "focussearch", 0, 0, GDK_F7 },
4672 /* hinting */
4673 { "hinting", 0, 0, GDK_f },
4675 /* custom stylesheet */
4676 { "userstyle", 0, 0, GDK_i },
4678 /* navigation */
4679 { "goback", 0, 0, GDK_BackSpace },
4680 { "goback", MOD1, 0, GDK_Left },
4681 { "goforward", SHFT, 0, GDK_BackSpace },
4682 { "goforward", MOD1, 0, GDK_Right },
4683 { "reload", 0, 0, GDK_F5 },
4684 { "reload", CTRL, 0, GDK_r },
4685 { "reloadforce", CTRL, 0, GDK_R },
4686 { "reload", CTRL, 0, GDK_l },
4687 { "favorites", MOD1, 1, GDK_f },
4689 /* vertical movement */
4690 { "scrolldown", 0, 0, GDK_j },
4691 { "scrolldown", 0, 0, GDK_Down },
4692 { "scrollup", 0, 0, GDK_Up },
4693 { "scrollup", 0, 0, GDK_k },
4694 { "scrollbottom", 0, 0, GDK_G },
4695 { "scrollbottom", 0, 0, GDK_End },
4696 { "scrolltop", 0, 0, GDK_Home },
4697 { "scrolltop", 0, 0, GDK_g },
4698 { "scrollpagedown", 0, 0, GDK_space },
4699 { "scrollpagedown", CTRL, 0, GDK_f },
4700 { "scrollhalfdown", CTRL, 0, GDK_d },
4701 { "scrollpagedown", 0, 0, GDK_Page_Down },
4702 { "scrollpageup", 0, 0, GDK_Page_Up },
4703 { "scrollpageup", CTRL, 0, GDK_b },
4704 { "scrollhalfup", CTRL, 0, GDK_u },
4705 /* horizontal movement */
4706 { "scrollright", 0, 0, GDK_l },
4707 { "scrollright", 0, 0, GDK_Right },
4708 { "scrollleft", 0, 0, GDK_Left },
4709 { "scrollleft", 0, 0, GDK_h },
4710 { "scrollfarright", 0, 0, GDK_dollar },
4711 { "scrollfarleft", 0, 0, GDK_0 },
4713 /* tabs */
4714 { "tabnew", CTRL, 0, GDK_t },
4715 { "999tabnew", CTRL, 0, GDK_T },
4716 { "tabclose", CTRL, 1, GDK_w },
4717 { "tabundoclose", 0, 0, GDK_U },
4718 { "tabnext 1", CTRL, 0, GDK_1 },
4719 { "tabnext 2", CTRL, 0, GDK_2 },
4720 { "tabnext 3", CTRL, 0, GDK_3 },
4721 { "tabnext 4", CTRL, 0, GDK_4 },
4722 { "tabnext 5", CTRL, 0, GDK_5 },
4723 { "tabnext 6", CTRL, 0, GDK_6 },
4724 { "tabnext 7", CTRL, 0, GDK_7 },
4725 { "tabnext 8", CTRL, 0, GDK_8 },
4726 { "tabnext 9", CTRL, 0, GDK_9 },
4727 { "tabnext 10", CTRL, 0, GDK_0 },
4728 { "tabfirst", CTRL, 0, GDK_less },
4729 { "tablast", CTRL, 0, GDK_greater },
4730 { "tabprevious", CTRL, 0, GDK_Left },
4731 { "tabnext", CTRL, 0, GDK_Right },
4732 { "focusout", CTRL, 0, GDK_minus },
4733 { "focusin", CTRL, 0, GDK_plus },
4734 { "focusin", CTRL, 0, GDK_equal },
4736 /* command aliases (handy when -S flag is used) */
4737 { "promptopen", 0, 0, GDK_F9 },
4738 { "promptopencurrent", 0, 0, GDK_F10 },
4739 { "prompttabnew", 0, 0, GDK_F11 },
4740 { "prompttabnewcurrent",0, 0, GDK_F12 },
4742 TAILQ_HEAD(keybinding_list, key_binding);
4744 void
4745 walk_kb(struct settings *s,
4746 void (*cb)(struct settings *, char *, void *), void *cb_args)
4748 struct key_binding *k;
4749 char str[1024];
4751 if (s == NULL || cb == NULL) {
4752 show_oops_s("walk_kb invalid parameters");
4753 return;
4756 TAILQ_FOREACH(k, &kbl, entry) {
4757 if (k->cmd == NULL)
4758 continue;
4759 str[0] = '\0';
4761 /* sanity */
4762 if (gdk_keyval_name(k->key) == NULL)
4763 continue;
4765 strlcat(str, k->cmd, sizeof str);
4766 strlcat(str, ",", sizeof str);
4768 if (k->mask & GDK_SHIFT_MASK)
4769 strlcat(str, "S-", sizeof str);
4770 if (k->mask & GDK_CONTROL_MASK)
4771 strlcat(str, "C-", sizeof str);
4772 if (k->mask & GDK_MOD1_MASK)
4773 strlcat(str, "M1-", sizeof str);
4774 if (k->mask & GDK_MOD2_MASK)
4775 strlcat(str, "M2-", sizeof str);
4776 if (k->mask & GDK_MOD3_MASK)
4777 strlcat(str, "M3-", sizeof str);
4778 if (k->mask & GDK_MOD4_MASK)
4779 strlcat(str, "M4-", sizeof str);
4780 if (k->mask & GDK_MOD5_MASK)
4781 strlcat(str, "M5-", sizeof str);
4783 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4784 cb(s, str, cb_args);
4788 void
4789 init_keybindings(void)
4791 int i;
4792 struct key_binding *k;
4794 for (i = 0; i < LENGTH(keys); i++) {
4795 k = g_malloc0(sizeof *k);
4796 k->cmd = keys[i].cmd;
4797 k->mask = keys[i].mask;
4798 k->use_in_entry = keys[i].use_in_entry;
4799 k->key = keys[i].key;
4800 TAILQ_INSERT_HEAD(&kbl, k, entry);
4802 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4803 k->cmd ? k->cmd : "unnamed key");
4807 void
4808 keybinding_clearall(void)
4810 struct key_binding *k, *next;
4812 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4813 next = TAILQ_NEXT(k, entry);
4814 if (k->cmd == NULL)
4815 continue;
4817 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4818 k->cmd ? k->cmd : "unnamed key");
4819 TAILQ_REMOVE(&kbl, k, entry);
4820 g_free(k);
4825 keybinding_add(char *cmd, char *key, int use_in_entry)
4827 struct key_binding *k;
4828 guint keyval, mask = 0;
4829 int i;
4831 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4833 /* Keys which are to be used in entry have been prefixed with an
4834 * exclamation mark. */
4835 if (use_in_entry)
4836 key++;
4838 /* find modifier keys */
4839 if (strstr(key, "S-"))
4840 mask |= GDK_SHIFT_MASK;
4841 if (strstr(key, "C-"))
4842 mask |= GDK_CONTROL_MASK;
4843 if (strstr(key, "M1-"))
4844 mask |= GDK_MOD1_MASK;
4845 if (strstr(key, "M2-"))
4846 mask |= GDK_MOD2_MASK;
4847 if (strstr(key, "M3-"))
4848 mask |= GDK_MOD3_MASK;
4849 if (strstr(key, "M4-"))
4850 mask |= GDK_MOD4_MASK;
4851 if (strstr(key, "M5-"))
4852 mask |= GDK_MOD5_MASK;
4854 /* find keyname */
4855 for (i = strlen(key) - 1; i > 0; i--)
4856 if (key[i] == '-')
4857 key = &key[i + 1];
4859 /* validate keyname */
4860 keyval = gdk_keyval_from_name(key);
4861 if (keyval == GDK_VoidSymbol) {
4862 warnx("invalid keybinding name %s", key);
4863 return (1);
4865 /* must run this test too, gtk+ doesn't handle 10 for example */
4866 if (gdk_keyval_name(keyval) == NULL) {
4867 warnx("invalid keybinding name %s", key);
4868 return (1);
4871 /* Remove eventual dupes. */
4872 TAILQ_FOREACH(k, &kbl, entry)
4873 if (k->key == keyval && k->mask == mask) {
4874 TAILQ_REMOVE(&kbl, k, entry);
4875 g_free(k);
4876 break;
4879 /* add keyname */
4880 k = g_malloc0(sizeof *k);
4881 k->cmd = g_strdup(cmd);
4882 k->mask = mask;
4883 k->use_in_entry = use_in_entry;
4884 k->key = keyval;
4886 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4887 k->cmd,
4888 k->mask,
4889 k->use_in_entry,
4890 k->key);
4891 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4892 k->cmd, gdk_keyval_name(keyval));
4894 TAILQ_INSERT_HEAD(&kbl, k, entry);
4896 return (0);
4900 add_kb(struct settings *s, char *entry)
4902 char *kb, *key;
4904 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4906 /* clearall is special */
4907 if (!strcmp(entry, "clearall")) {
4908 keybinding_clearall();
4909 return (0);
4912 kb = strstr(entry, ",");
4913 if (kb == NULL)
4914 return (1);
4915 *kb = '\0';
4916 key = kb + 1;
4918 return (keybinding_add(entry, key, key[0] == '!'));
4921 struct cmd {
4922 char *cmd;
4923 int level;
4924 int (*func)(struct tab *, struct karg *);
4925 int arg;
4926 int type;
4927 } cmds[] = {
4928 { "command", 0, command, ':', 0 },
4929 { "search", 0, command, '/', 0 },
4930 { "searchb", 0, command, '?', 0 },
4931 { "togglesrc", 0, toggle_src, 0, 0 },
4933 /* yanking and pasting */
4934 { "yankuri", 0, yank_uri, 0, 0 },
4935 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4936 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
4937 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
4939 /* search */
4940 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
4941 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
4943 /* focus */
4944 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
4945 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
4947 /* hinting */
4948 { "hinting", 0, hint, 0, 0 },
4950 /* custom stylesheet */
4951 { "userstyle", 0, userstyle, 0, 0 },
4953 /* navigation */
4954 { "goback", 0, navaction, XT_NAV_BACK, 0 },
4955 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
4956 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
4957 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
4959 /* vertical movement */
4960 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
4961 { "scrollup", 0, move, XT_MOVE_UP, 0 },
4962 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
4963 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
4964 { "1", 0, move, XT_MOVE_TOP, 0 },
4965 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
4966 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
4967 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
4968 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
4969 /* horizontal movement */
4970 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
4971 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
4972 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
4973 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
4976 { "favorites", 0, xtp_page_fl, 0, 0 },
4977 { "fav", 0, xtp_page_fl, 0, 0 },
4978 { "favadd", 0, add_favorite, 0, 0 },
4980 { "qall", 0, quit, 0, 0 },
4981 { "quitall", 0, quit, 0, 0 },
4982 { "w", 0, save_tabs, 0, 0 },
4983 { "wq", 0, save_tabs_and_quit, 0, 0 },
4984 { "help", 0, help, 0, 0 },
4985 { "about", 0, about, 0, 0 },
4986 { "stats", 0, stats, 0, 0 },
4987 { "version", 0, about, 0, 0 },
4989 /* js command */
4990 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
4991 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
4992 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
4993 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
4994 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
4995 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
4996 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
4997 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
4998 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
4999 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5000 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5002 /* cookie command */
5003 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5004 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5005 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5006 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5007 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5008 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5009 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5010 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5011 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5012 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5013 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5015 /* cookie jar */
5016 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5018 /* cert command */
5019 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5020 { "save", 1, cert_cmd, XT_SAVE, 0 },
5021 { "show", 1, cert_cmd, XT_SHOW, 0 },
5023 { "ca", 0, ca_cmd, 0, 0 },
5024 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5025 { "dl", 0, xtp_page_dl, 0, 0 },
5026 { "h", 0, xtp_page_hl, 0, 0 },
5027 { "history", 0, xtp_page_hl, 0, 0 },
5028 { "home", 0, go_home, 0, 0 },
5029 { "restart", 0, restart, 0, 0 },
5030 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5031 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5032 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5033 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5035 { "print", 0, print_page, 0, 0 },
5037 /* tabs */
5038 { "focusin", 0, resizetab, 1, 0 },
5039 { "focusout", 0, resizetab, -1, 0 },
5040 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5041 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5042 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5043 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5044 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5045 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5046 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5047 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5048 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5049 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5050 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5051 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5052 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5053 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5055 /* command aliases (handy when -S flag is used) */
5056 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5057 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5058 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5059 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5061 /* settings */
5062 { "set", 0, set, 0, 0 },
5063 { "fullscreen", 0, fullscreen, 0, 0 },
5064 { "f", 0, fullscreen, 0, 0 },
5066 /* sessions */
5067 { "session", 0, session_cmd, XT_SHOW, 0 },
5068 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5069 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5070 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5071 { "show", 1, session_cmd, XT_SHOW, 0 },
5074 struct {
5075 int index;
5076 int len;
5077 gchar *list[256];
5078 } cmd_status = {-1, 0};
5080 gboolean
5081 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5083 struct karg a;
5085 hide_oops(t);
5087 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5088 /* go backward */
5089 a.i = XT_NAV_BACK;
5090 navaction(t, &a);
5092 return (TRUE);
5093 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5094 /* go forward */
5095 a.i = XT_NAV_FORWARD;
5096 navaction(t, &a);
5098 return (TRUE);
5101 return (FALSE);
5104 gboolean
5105 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5107 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5109 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5110 delete_tab(t);
5112 return (FALSE);
5116 * cancel, remove, etc. downloads
5118 void
5119 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5121 struct download find, *d = NULL;
5123 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5125 /* some commands require a valid download id */
5126 if (cmd != XT_XTP_DL_LIST) {
5127 /* lookup download in question */
5128 find.id = id;
5129 d = RB_FIND(download_list, &downloads, &find);
5131 if (d == NULL) {
5132 show_oops(t, "%s: no such download", __func__);
5133 return;
5137 /* decide what to do */
5138 switch (cmd) {
5139 case XT_XTP_DL_CANCEL:
5140 webkit_download_cancel(d->download);
5141 break;
5142 case XT_XTP_DL_REMOVE:
5143 webkit_download_cancel(d->download); /* just incase */
5144 g_object_unref(d->download);
5145 RB_REMOVE(download_list, &downloads, d);
5146 break;
5147 case XT_XTP_DL_LIST:
5148 /* Nothing */
5149 break;
5150 default:
5151 show_oops(t, "%s: unknown command", __func__);
5152 break;
5154 xtp_page_dl(t, NULL);
5158 * Actions on history, only does one thing for now, but
5159 * we provide the function for future actions
5161 void
5162 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5164 struct history *h, *next;
5165 int i = 1;
5167 switch (cmd) {
5168 case XT_XTP_HL_REMOVE:
5169 /* walk backwards, as listed in reverse */
5170 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5171 next = RB_PREV(history_list, &hl, h);
5172 if (id == i) {
5173 RB_REMOVE(history_list, &hl, h);
5174 g_free((gpointer) h->title);
5175 g_free((gpointer) h->uri);
5176 g_free(h);
5177 break;
5179 i++;
5181 break;
5182 case XT_XTP_HL_LIST:
5183 /* Nothing - just xtp_page_hl() below */
5184 break;
5185 default:
5186 show_oops(t, "%s: unknown command", __func__);
5187 break;
5190 xtp_page_hl(t, NULL);
5193 /* remove a favorite */
5194 void
5195 remove_favorite(struct tab *t, int index)
5197 char file[PATH_MAX], *title, *uri = NULL;
5198 char *new_favs, *tmp;
5199 FILE *f;
5200 int i;
5201 size_t len, lineno;
5203 /* open favorites */
5204 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5206 if ((f = fopen(file, "r")) == NULL) {
5207 show_oops(t, "%s: can't open favorites: %s",
5208 __func__, strerror(errno));
5209 return;
5212 /* build a string which will become the new favroites file */
5213 new_favs = g_strdup("");
5215 for (i = 1;;) {
5216 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5217 if (feof(f) || ferror(f))
5218 break;
5219 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5220 if (len == 0) {
5221 free(title);
5222 title = NULL;
5223 continue;
5226 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5227 if (feof(f) || ferror(f)) {
5228 show_oops(t, "%s: can't parse favorites %s",
5229 __func__, strerror(errno));
5230 goto clean;
5234 /* as long as this isn't the one we are deleting add to file */
5235 if (i != index) {
5236 tmp = new_favs;
5237 new_favs = g_strdup_printf("%s%s\n%s\n",
5238 new_favs, title, uri);
5239 g_free(tmp);
5242 free(uri);
5243 uri = NULL;
5244 free(title);
5245 title = NULL;
5246 i++;
5248 fclose(f);
5250 /* write back new favorites file */
5251 if ((f = fopen(file, "w")) == NULL) {
5252 show_oops(t, "%s: can't open favorites: %s",
5253 __func__, strerror(errno));
5254 goto clean;
5257 fwrite(new_favs, strlen(new_favs), 1, f);
5258 fclose(f);
5260 clean:
5261 if (uri)
5262 free(uri);
5263 if (title)
5264 free(title);
5266 g_free(new_favs);
5269 void
5270 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5272 switch (cmd) {
5273 case XT_XTP_FL_LIST:
5274 /* nothing, just the below call to xtp_page_fl() */
5275 break;
5276 case XT_XTP_FL_REMOVE:
5277 remove_favorite(t, arg);
5278 break;
5279 default:
5280 show_oops(t, "%s: invalid favorites command", __func__);
5281 break;
5284 xtp_page_fl(t, NULL);
5287 void
5288 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5290 switch (cmd) {
5291 case XT_XTP_CL_LIST:
5292 /* nothing, just xtp_page_cl() */
5293 break;
5294 case XT_XTP_CL_REMOVE:
5295 remove_cookie(arg);
5296 break;
5297 default:
5298 show_oops(t, "%s: unknown cookie xtp command", __func__);
5299 break;
5302 xtp_page_cl(t, NULL);
5305 /* link an XTP class to it's session key and handler function */
5306 struct xtp_despatch {
5307 uint8_t xtp_class;
5308 char **session_key;
5309 void (*handle_func)(struct tab *, uint8_t, int);
5312 struct xtp_despatch xtp_despatches[] = {
5313 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5314 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5315 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5316 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5317 { XT_XTP_INVALID, NULL, NULL }
5321 * is the url xtp protocol? (xxxt://)
5322 * if so, parse and despatch correct bahvior
5325 parse_xtp_url(struct tab *t, const char *url)
5327 char *dup = NULL, *p, *last;
5328 uint8_t n_tokens = 0;
5329 char *tokens[4] = {NULL, NULL, NULL, ""};
5330 struct xtp_despatch *dsp, *dsp_match = NULL;
5331 uint8_t req_class;
5332 int ret = FALSE;
5335 * tokens array meaning:
5336 * tokens[0] = class
5337 * tokens[1] = session key
5338 * tokens[2] = action
5339 * tokens[3] = optional argument
5342 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5344 /*xtp tab meaning is normal unless proven special */
5345 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5347 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5348 goto clean;
5350 dup = g_strdup(url + strlen(XT_XTP_STR));
5352 /* split out the url */
5353 for ((p = strtok_r(dup, "/", &last)); p;
5354 (p = strtok_r(NULL, "/", &last))) {
5355 if (n_tokens < 4)
5356 tokens[n_tokens++] = p;
5359 /* should be atleast three fields 'class/seskey/command/arg' */
5360 if (n_tokens < 3)
5361 goto clean;
5363 dsp = xtp_despatches;
5364 req_class = atoi(tokens[0]);
5365 while (dsp->xtp_class) {
5366 if (dsp->xtp_class == req_class) {
5367 dsp_match = dsp;
5368 break;
5370 dsp++;
5373 /* did we find one atall? */
5374 if (dsp_match == NULL) {
5375 show_oops(t, "%s: no matching xtp despatch found", __func__);
5376 goto clean;
5379 /* check session key and call despatch function */
5380 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5381 ret = TRUE; /* all is well, this was a valid xtp request */
5382 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5385 clean:
5386 if (dup)
5387 g_free(dup);
5389 return (ret);
5394 void
5395 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5397 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5399 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5401 if (t == NULL) {
5402 show_oops_s("activate_uri_entry_cb invalid parameters");
5403 return;
5406 if (uri == NULL) {
5407 show_oops(t, "activate_uri_entry_cb no uri");
5408 return;
5411 uri += strspn(uri, "\t ");
5413 /* if xxxt:// treat specially */
5414 if (parse_xtp_url(t, uri))
5415 return;
5417 /* otherwise continue to load page normally */
5418 load_uri(t, (gchar *)uri);
5419 focus_webview(t);
5422 void
5423 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5425 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5426 char *newuri = NULL;
5427 gchar *enc_search;
5429 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5431 if (t == NULL) {
5432 show_oops_s("activate_search_entry_cb invalid parameters");
5433 return;
5436 if (search_string == NULL) {
5437 show_oops(t, "no search_string");
5438 return;
5441 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5442 newuri = g_strdup_printf(search_string, enc_search);
5443 g_free(enc_search);
5445 webkit_web_view_load_uri(t->wv, newuri);
5446 focus_webview(t);
5448 if (newuri)
5449 g_free(newuri);
5452 void
5453 check_and_set_js(const gchar *uri, struct tab *t)
5455 struct domain *d = NULL;
5456 int es = 0;
5458 if (uri == NULL || t == NULL)
5459 return;
5461 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5462 es = 0;
5463 else
5464 es = 1;
5466 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5467 es ? "enable" : "disable", uri);
5469 g_object_set(G_OBJECT(t->settings),
5470 "enable-scripts", es, (char *)NULL);
5471 g_object_set(G_OBJECT(t->settings),
5472 "javascript-can-open-windows-automatically", es, (char *)NULL);
5473 webkit_web_view_set_settings(t->wv, t->settings);
5475 button_set_stockid(t->js_toggle,
5476 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5479 void
5480 show_ca_status(struct tab *t, const char *uri)
5482 WebKitWebFrame *frame;
5483 WebKitWebDataSource *source;
5484 WebKitNetworkRequest *request;
5485 SoupMessage *message;
5486 GdkColor color;
5487 gchar *col_str = XT_COLOR_WHITE;
5488 int r;
5490 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5491 ssl_strict_certs, ssl_ca_file, uri);
5493 if (uri == NULL)
5494 goto done;
5495 if (ssl_ca_file == NULL) {
5496 if (g_str_has_prefix(uri, "http://"))
5497 goto done;
5498 if (g_str_has_prefix(uri, "https://")) {
5499 col_str = XT_COLOR_RED;
5500 goto done;
5502 return;
5504 if (g_str_has_prefix(uri, "http://") ||
5505 !g_str_has_prefix(uri, "https://"))
5506 goto done;
5508 frame = webkit_web_view_get_main_frame(t->wv);
5509 source = webkit_web_frame_get_data_source(frame);
5510 request = webkit_web_data_source_get_request(source);
5511 message = webkit_network_request_get_message(request);
5513 if (message && (soup_message_get_flags(message) &
5514 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5515 col_str = XT_COLOR_GREEN;
5516 goto done;
5517 } else {
5518 r = load_compare_cert(t, NULL);
5519 if (r == 0)
5520 col_str = XT_COLOR_BLUE;
5521 else if (r == 1)
5522 col_str = XT_COLOR_YELLOW;
5523 else
5524 col_str = XT_COLOR_RED;
5525 goto done;
5527 done:
5528 if (col_str) {
5529 gdk_color_parse(col_str, &color);
5530 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5532 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5533 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5534 &color);
5535 gdk_color_parse(XT_COLOR_BLACK, &color);
5536 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5537 &color);
5538 } else {
5539 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5540 &color);
5541 gdk_color_parse(XT_COLOR_BLACK, &color);
5542 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5543 &color);
5548 void
5549 free_favicon(struct tab *t)
5551 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5552 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5554 if (t->icon_request)
5555 g_object_unref(t->icon_request);
5556 if (t->icon_pixbuf)
5557 g_object_unref(t->icon_pixbuf);
5558 if (t->icon_dest_uri)
5559 g_free(t->icon_dest_uri);
5561 t->icon_pixbuf = NULL;
5562 t->icon_request = NULL;
5563 t->icon_dest_uri = NULL;
5566 void
5567 xt_icon_from_name(struct tab *t, gchar *name)
5569 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5570 GTK_ENTRY_ICON_PRIMARY, "text-html");
5571 if (show_url == 0)
5572 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5573 GTK_ENTRY_ICON_PRIMARY, "text-html");
5574 else
5575 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5576 GTK_ENTRY_ICON_PRIMARY, NULL);
5579 void
5580 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5582 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5583 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5584 if (show_url == 0)
5585 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5586 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5587 else
5588 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5589 GTK_ENTRY_ICON_PRIMARY, NULL);
5592 gboolean
5593 is_valid_icon(char *file)
5595 gboolean valid = 0;
5596 const char *mime_type;
5597 GFileInfo *fi;
5598 GFile *gf;
5600 gf = g_file_new_for_path(file);
5601 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5602 NULL, NULL);
5603 mime_type = g_file_info_get_content_type(fi);
5604 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5605 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5606 g_strcmp0(mime_type, "image/png") == 0 ||
5607 g_strcmp0(mime_type, "image/gif") == 0 ||
5608 g_strcmp0(mime_type, "application/octet-stream") == 0;
5609 g_object_unref(fi);
5610 g_object_unref(gf);
5612 return (valid);
5615 void
5616 set_favicon_from_file(struct tab *t, char *file)
5618 gint width, height;
5619 GdkPixbuf *pixbuf, *scaled;
5620 struct stat sb;
5622 if (t == NULL || file == NULL)
5623 return;
5624 if (t->icon_pixbuf) {
5625 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5626 return;
5629 if (g_str_has_prefix(file, "file://"))
5630 file += strlen("file://");
5631 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5633 if (!stat(file, &sb)) {
5634 if (sb.st_size == 0 || !is_valid_icon(file)) {
5635 /* corrupt icon so trash it */
5636 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5637 __func__, file);
5638 unlink(file);
5639 /* no need to set icon to default here */
5640 return;
5644 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5645 if (pixbuf == NULL) {
5646 xt_icon_from_name(t, "text-html");
5647 return;
5650 g_object_get(pixbuf, "width", &width, "height", &height,
5651 (char *)NULL);
5652 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5653 __func__, t->tab_id, width, height);
5655 if (width > 16 || height > 16) {
5656 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5657 GDK_INTERP_BILINEAR);
5658 g_object_unref(pixbuf);
5659 } else
5660 scaled = pixbuf;
5662 if (scaled == NULL) {
5663 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5664 GDK_INTERP_BILINEAR);
5665 return;
5668 t->icon_pixbuf = scaled;
5669 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5672 void
5673 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5674 WebKitWebView *wv)
5676 WebKitDownloadStatus status = webkit_download_get_status(download);
5677 struct tab *tt = NULL, *t = NULL;
5680 * find the webview instead of passing in the tab as it could have been
5681 * deleted from underneath us.
5683 TAILQ_FOREACH(tt, &tabs, entry) {
5684 if (tt->wv == wv) {
5685 t = tt;
5686 break;
5689 if (t == NULL)
5690 return;
5692 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5693 __func__, t->tab_id, status);
5695 switch (status) {
5696 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5697 /* -1 */
5698 t->icon_download = NULL;
5699 free_favicon(t);
5700 break;
5701 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5702 /* 0 */
5703 break;
5704 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5705 /* 1 */
5706 break;
5707 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5708 /* 2 */
5709 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5710 __func__, t->tab_id);
5711 t->icon_download = NULL;
5712 free_favicon(t);
5713 break;
5714 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5715 /* 3 */
5717 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5718 __func__, t->icon_dest_uri);
5719 set_favicon_from_file(t, t->icon_dest_uri);
5720 /* these will be freed post callback */
5721 t->icon_request = NULL;
5722 t->icon_download = NULL;
5723 break;
5724 default:
5725 break;
5729 void
5730 abort_favicon_download(struct tab *t)
5732 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5734 if (t->icon_download) {
5735 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5736 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5737 webkit_download_cancel(t->icon_download);
5738 t->icon_download = NULL;
5739 } else
5740 free_favicon(t);
5742 xt_icon_from_name(t, "text-html");
5745 void
5746 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5748 gchar *name_hash, file[PATH_MAX];
5749 struct stat sb;
5751 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5753 if (uri == NULL || t == NULL)
5754 return;
5756 if (t->icon_request) {
5757 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5758 return;
5761 /* check to see if we got the icon in cache */
5762 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5763 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5764 g_free(name_hash);
5766 if (!stat(file, &sb)) {
5767 if (sb.st_size > 0) {
5768 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5769 __func__, file);
5770 set_favicon_from_file(t, file);
5771 return;
5774 /* corrupt icon so trash it */
5775 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5776 __func__, file);
5777 unlink(file);
5780 /* create download for icon */
5781 t->icon_request = webkit_network_request_new(uri);
5782 if (t->icon_request == NULL) {
5783 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5784 __func__, uri);
5785 return;
5788 t->icon_download = webkit_download_new(t->icon_request);
5789 if (t->icon_download == NULL) {
5790 fprintf(stderr, "%s: icon_download", __func__);
5791 return;
5794 /* we have to free icon_dest_uri later */
5795 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5796 webkit_download_set_destination_uri(t->icon_download,
5797 t->icon_dest_uri);
5799 if (webkit_download_get_status(t->icon_download) ==
5800 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5801 fprintf(stderr, "%s: download failed to start", __func__);
5802 g_object_unref(t->icon_request);
5803 g_free(t->icon_dest_uri);
5804 t->icon_request = NULL;
5805 t->icon_dest_uri = NULL;
5806 return;
5809 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5810 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5812 webkit_download_start(t->icon_download);
5815 void
5816 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5818 const gchar *set = NULL, *uri = NULL, *title = NULL;
5819 struct history *h, find;
5820 const gchar *s_loading;
5821 struct karg a;
5823 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5824 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5826 if (t == NULL) {
5827 show_oops_s("notify_load_status_cb invalid paramters");
5828 return;
5831 switch (webkit_web_view_get_load_status(wview)) {
5832 case WEBKIT_LOAD_PROVISIONAL:
5833 /* 0 */
5834 abort_favicon_download(t);
5835 #if GTK_CHECK_VERSION(2, 20, 0)
5836 gtk_widget_show(t->spinner);
5837 gtk_spinner_start(GTK_SPINNER(t->spinner));
5838 #endif
5839 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5841 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5843 /* take focus if we are visible */
5844 focus_webview(t);
5845 t->focus_wv = 1;
5847 break;
5849 case WEBKIT_LOAD_COMMITTED:
5850 /* 1 */
5851 if ((uri = get_uri(wview)) != NULL) {
5852 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5853 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5855 if (t->status) {
5856 g_free(t->status);
5857 t->status = NULL;
5859 set_status(t, (char *)uri, XT_STATUS_LOADING);
5862 /* check if js white listing is enabled */
5863 if (enable_js_whitelist) {
5864 uri = get_uri(wview);
5865 check_and_set_js(uri, t);
5868 if (t->styled)
5869 apply_style(t);
5871 show_ca_status(t, uri);
5873 /* we know enough to autosave the session */
5874 if (session_autosave) {
5875 a.s = NULL;
5876 save_tabs(t, &a);
5878 break;
5880 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5881 /* 3 */
5882 break;
5884 case WEBKIT_LOAD_FINISHED:
5885 /* 2 */
5886 uri = get_uri(wview);
5887 if (uri == NULL)
5888 return;
5890 if (!strncmp(uri, "http://", strlen("http://")) ||
5891 !strncmp(uri, "https://", strlen("https://")) ||
5892 !strncmp(uri, "file://", strlen("file://"))) {
5893 find.uri = uri;
5894 h = RB_FIND(history_list, &hl, &find);
5895 if (!h) {
5896 title = webkit_web_view_get_title(wview);
5897 set = title ? title: uri;
5898 h = g_malloc(sizeof *h);
5899 h->uri = g_strdup(uri);
5900 h->title = g_strdup(set);
5901 RB_INSERT(history_list, &hl, h);
5902 completion_add_uri(h->uri);
5903 update_history_tabs(NULL);
5907 set_status(t, (char *)uri, XT_STATUS_URI);
5908 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5909 case WEBKIT_LOAD_FAILED:
5910 /* 4 */
5911 #endif
5912 #if GTK_CHECK_VERSION(2, 20, 0)
5913 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5914 gtk_widget_hide(t->spinner);
5915 #endif
5916 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5917 if (s_loading && !strcmp(s_loading, "Loading"))
5918 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5919 default:
5920 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5921 break;
5924 if (t->item)
5925 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5926 else
5927 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5928 webkit_web_view_can_go_back(wview));
5930 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5931 webkit_web_view_can_go_forward(wview));
5934 void
5935 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5937 const gchar *set = NULL, *title = NULL;
5939 title = webkit_web_view_get_title(wview);
5940 set = title ? title: get_uri(wview);
5941 if (set) {
5942 gtk_label_set_text(GTK_LABEL(t->label), set);
5943 gtk_window_set_title(GTK_WINDOW(main_window), set);
5944 } else {
5945 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5946 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5950 void
5951 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5953 run_script(t, JS_HINTING);
5956 void
5957 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5959 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5960 progress == 100 ? 0 : (double)progress / 100);
5961 if (show_url == 0) {
5962 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5963 progress == 100 ? 0 : (double)progress / 100);
5968 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5969 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5970 WebKitWebPolicyDecision *pd, struct tab *t)
5972 char *uri;
5973 WebKitWebNavigationReason reason;
5974 struct domain *d = NULL;
5976 if (t == NULL) {
5977 show_oops_s("webview_npd_cb invalid parameters");
5978 return (FALSE);
5981 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5982 t->ctrl_click,
5983 webkit_network_request_get_uri(request));
5985 uri = (char *)webkit_network_request_get_uri(request);
5987 /* if this is an xtp url, we don't load anything else */
5988 if (parse_xtp_url(t, uri))
5989 return (TRUE);
5991 if (t->ctrl_click) {
5992 t->ctrl_click = 0;
5993 create_new_tab(uri, NULL, ctrl_click_focus, -1);
5994 webkit_web_policy_decision_ignore(pd);
5995 return (TRUE); /* we made the decission */
5999 * This is a little hairy but it comes down to this:
6000 * when we run in whitelist mode we have to assist the browser in
6001 * opening the URL that it would have opened in a new tab.
6003 reason = webkit_web_navigation_action_get_reason(na);
6004 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6005 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6006 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6007 load_uri(t, uri);
6009 webkit_web_policy_decision_use(pd);
6010 return (TRUE); /* we made the decission */
6013 return (FALSE);
6016 WebKitWebView *
6017 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6019 struct tab *tt;
6020 struct domain *d = NULL;
6021 const gchar *uri;
6022 WebKitWebView *webview = NULL;
6024 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6025 webkit_web_view_get_uri(wv));
6027 if (tabless) {
6028 /* open in current tab */
6029 webview = t->wv;
6030 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6031 uri = webkit_web_view_get_uri(wv);
6032 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6033 return (NULL);
6035 tt = create_new_tab(NULL, NULL, 1, -1);
6036 webview = tt->wv;
6037 } else if (enable_scripts == 1) {
6038 tt = create_new_tab(NULL, NULL, 1, -1);
6039 webview = tt->wv;
6042 return (webview);
6045 gboolean
6046 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6048 const gchar *uri;
6049 struct domain *d = NULL;
6051 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6053 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6054 uri = webkit_web_view_get_uri(wv);
6055 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6056 return (FALSE);
6058 delete_tab(t);
6059 } else if (enable_scripts == 1)
6060 delete_tab(t);
6062 return (TRUE);
6066 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6068 /* we can not eat the event without throwing gtk off so defer it */
6070 /* catch middle click */
6071 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6072 t->ctrl_click = 1;
6073 goto done;
6076 /* catch ctrl click */
6077 if (e->type == GDK_BUTTON_RELEASE &&
6078 CLEAN(e->state) == GDK_CONTROL_MASK)
6079 t->ctrl_click = 1;
6080 else
6081 t->ctrl_click = 0;
6082 done:
6083 return (XT_CB_PASSTHROUGH);
6087 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6089 struct mime_type *m;
6091 m = find_mime_type(mime_type);
6092 if (m == NULL)
6093 return (1);
6094 if (m->mt_download)
6095 return (1);
6097 switch (fork()) {
6098 case -1:
6099 show_oops(t, "can't fork mime handler");
6100 /* NOTREACHED */
6101 case 0:
6102 break;
6103 default:
6104 return (0);
6107 /* child */
6108 execlp(m->mt_action, m->mt_action,
6109 webkit_network_request_get_uri(request), (void *)NULL);
6111 _exit(0);
6113 /* NOTREACHED */
6114 return (0);
6117 const gchar *
6118 get_mime_type(char *file)
6120 const char *mime_type;
6121 GFileInfo *fi;
6122 GFile *gf;
6124 if (g_str_has_prefix(file, "file://"))
6125 file += strlen("file://");
6127 gf = g_file_new_for_path(file);
6128 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6129 NULL, NULL);
6130 mime_type = g_file_info_get_content_type(fi);
6131 g_object_unref(fi);
6132 g_object_unref(gf);
6134 return (mime_type);
6138 run_download_mimehandler(char *mime_type, char *file)
6140 struct mime_type *m;
6142 m = find_mime_type(mime_type);
6143 if (m == NULL)
6144 return (1);
6146 switch (fork()) {
6147 case -1:
6148 show_oops_s("can't fork download mime handler");
6149 /* NOTREACHED */
6150 case 0:
6151 break;
6152 default:
6153 return (0);
6156 /* child */
6157 if (g_str_has_prefix(file, "file://"))
6158 file += strlen("file://");
6159 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6161 _exit(0);
6163 /* NOTREACHED */
6164 return (0);
6167 void
6168 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6169 WebKitWebView *wv)
6171 WebKitDownloadStatus status;
6172 const gchar *file = NULL, *mime = NULL;
6174 if (download == NULL)
6175 return;
6176 status = webkit_download_get_status(download);
6177 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6178 return;
6180 file = webkit_download_get_destination_uri(download);
6181 if (file == NULL)
6182 return;
6183 mime = get_mime_type((char *)file);
6184 if (mime == NULL)
6185 return;
6187 run_download_mimehandler((char *)mime, (char *)file);
6191 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6192 WebKitNetworkRequest *request, char *mime_type,
6193 WebKitWebPolicyDecision *decision, struct tab *t)
6195 if (t == NULL) {
6196 show_oops_s("webview_mimetype_cb invalid parameters");
6197 return (FALSE);
6200 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6201 t->tab_id, mime_type);
6203 if (run_mimehandler(t, mime_type, request) == 0) {
6204 webkit_web_policy_decision_ignore(decision);
6205 focus_webview(t);
6206 return (TRUE);
6209 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6210 webkit_web_policy_decision_download(decision);
6211 return (TRUE);
6214 return (FALSE);
6218 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6219 struct tab *t)
6221 const gchar *filename;
6222 char *uri = NULL;
6223 struct download *download_entry;
6224 int ret = TRUE;
6226 if (wk_download == NULL || t == NULL) {
6227 show_oops_s("%s invalid parameters", __func__);
6228 return (FALSE);
6231 filename = webkit_download_get_suggested_filename(wk_download);
6232 if (filename == NULL)
6233 return (FALSE); /* abort download */
6235 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6237 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6238 "local %s\n", __func__, t->tab_id, filename, uri);
6240 webkit_download_set_destination_uri(wk_download, uri);
6242 if (webkit_download_get_status(wk_download) ==
6243 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6244 show_oops(t, "%s: download failed to start", __func__);
6245 ret = FALSE;
6246 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6247 } else {
6248 /* connect "download first" mime handler */
6249 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6250 G_CALLBACK(download_status_changed_cb), NULL);
6252 download_entry = g_malloc(sizeof(struct download));
6253 download_entry->download = wk_download;
6254 download_entry->tab = t;
6255 download_entry->id = next_download_id++;
6256 RB_INSERT(download_list, &downloads, download_entry);
6257 /* get from history */
6258 g_object_ref(wk_download);
6259 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6260 show_oops(t, "Download of '%s' started...",
6261 basename(webkit_download_get_destination_uri(wk_download)));
6264 if (uri)
6265 g_free(uri);
6267 /* sync other download manager tabs */
6268 update_download_tabs(NULL);
6271 * NOTE: never redirect/render the current tab before this
6272 * function returns. This will cause the download to never start.
6274 return (ret); /* start download */
6277 void
6278 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6280 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6282 if (t == NULL) {
6283 show_oops_s("webview_hover_cb");
6284 return;
6287 if (uri)
6288 set_status(t, uri, XT_STATUS_LINK);
6289 else {
6290 if (t->status)
6291 set_status(t, t->status, XT_STATUS_NOTHING);
6295 gboolean
6296 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6298 struct key_binding *k;
6300 TAILQ_FOREACH(k, &kbl, entry)
6301 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6302 if (k->mask == 0) {
6303 if ((e->state & (CTRL | MOD1)) == 0)
6304 return (cmd_execute(t, k->cmd));
6305 } else if ((e->state & k->mask) == k->mask) {
6306 return (cmd_execute(t, k->cmd));
6310 return (XT_CB_PASSTHROUGH);
6314 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6316 char s[2], buf[128];
6317 const char *errstr = NULL;
6318 long long link;
6320 /* don't use w directly; use t->whatever instead */
6322 if (t == NULL) {
6323 show_oops_s("wv_keypress_after_cb");
6324 return (XT_CB_PASSTHROUGH);
6327 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6328 e->keyval, e->state, t);
6330 if (t->hints_on) {
6331 /* ESC */
6332 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6333 disable_hints(t);
6334 return (XT_CB_HANDLED);
6337 /* RETURN */
6338 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6339 link = strtonum(t->hint_num, 1, 1000, &errstr);
6340 if (errstr) {
6341 /* we have a string */
6342 } else {
6343 /* we have a number */
6344 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6345 t->hint_num);
6346 run_script(t, buf);
6348 disable_hints(t);
6351 /* BACKSPACE */
6352 /* XXX unfuck this */
6353 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6354 if (t->hint_mode == XT_HINT_NUMERICAL) {
6355 /* last input was numerical */
6356 int l;
6357 l = strlen(t->hint_num);
6358 if (l > 0) {
6359 l--;
6360 if (l == 0) {
6361 disable_hints(t);
6362 enable_hints(t);
6363 } else {
6364 t->hint_num[l] = '\0';
6365 goto num;
6368 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6369 /* last input was alphanumerical */
6370 int l;
6371 l = strlen(t->hint_buf);
6372 if (l > 0) {
6373 l--;
6374 if (l == 0) {
6375 disable_hints(t);
6376 enable_hints(t);
6377 } else {
6378 t->hint_buf[l] = '\0';
6379 goto anum;
6382 } else {
6383 /* bogus */
6384 disable_hints(t);
6388 /* numerical input */
6389 if (CLEAN(e->state) == 0 &&
6390 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6391 snprintf(s, sizeof s, "%c", e->keyval);
6392 strlcat(t->hint_num, s, sizeof t->hint_num);
6393 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6394 t->hint_num);
6395 num:
6396 link = strtonum(t->hint_num, 1, 1000, &errstr);
6397 if (errstr) {
6398 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6399 disable_hints(t);
6400 } else {
6401 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6402 t->hint_num);
6403 t->hint_mode = XT_HINT_NUMERICAL;
6404 run_script(t, buf);
6407 /* empty the counter buffer */
6408 bzero(t->hint_buf, sizeof t->hint_buf);
6409 return (XT_CB_HANDLED);
6412 /* alphanumerical input */
6413 if (
6414 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6415 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6416 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6417 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6418 snprintf(s, sizeof s, "%c", e->keyval);
6419 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6420 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6421 t->hint_buf);
6422 anum:
6423 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6424 run_script(t, buf);
6426 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6427 t->hint_buf);
6428 t->hint_mode = XT_HINT_ALPHANUM;
6429 run_script(t, buf);
6431 /* empty the counter buffer */
6432 bzero(t->hint_num, sizeof t->hint_num);
6433 return (XT_CB_HANDLED);
6436 return (XT_CB_HANDLED);
6437 } else {
6438 /* prefix input*/
6439 snprintf(s, sizeof s, "%c", e->keyval);
6440 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6441 cmd_prefix = 10 * cmd_prefix + atoi(s);
6445 return (handle_keypress(t, e, 0));
6449 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6451 hide_oops(t);
6453 return (XT_CB_PASSTHROUGH);
6457 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6459 const gchar *c = gtk_entry_get_text(w);
6460 GdkColor color;
6461 int forward = TRUE;
6463 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6464 e->keyval, e->state, t);
6466 if (t == NULL) {
6467 show_oops_s("cmd_keyrelease_cb invalid parameters");
6468 return (XT_CB_PASSTHROUGH);
6471 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6472 e->keyval, e->state, t);
6474 if (c[0] == ':')
6475 goto done;
6476 if (strlen(c) == 1) {
6477 webkit_web_view_unmark_text_matches(t->wv);
6478 goto done;
6481 if (c[0] == '/')
6482 forward = TRUE;
6483 else if (c[0] == '?')
6484 forward = FALSE;
6485 else
6486 goto done;
6488 /* search */
6489 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6490 FALSE) {
6491 /* not found, mark red */
6492 gdk_color_parse(XT_COLOR_RED, &color);
6493 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6494 /* unmark and remove selection */
6495 webkit_web_view_unmark_text_matches(t->wv);
6496 /* my kingdom for a way to unselect text in webview */
6497 } else {
6498 /* found, highlight all */
6499 webkit_web_view_unmark_text_matches(t->wv);
6500 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6501 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6502 gdk_color_parse(XT_COLOR_WHITE, &color);
6503 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6505 done:
6506 return (XT_CB_PASSTHROUGH);
6509 gboolean
6510 match_uri(const gchar *uri, const gchar *key) {
6511 gchar *voffset;
6512 size_t len;
6513 gboolean match = FALSE;
6515 len = strlen(key);
6517 if (!strncmp(key, uri, len))
6518 match = TRUE;
6519 else {
6520 voffset = strstr(uri, "/") + 2;
6521 if (!strncmp(key, voffset, len))
6522 match = TRUE;
6523 else if (g_str_has_prefix(voffset, "www.")) {
6524 voffset = voffset + strlen("www.");
6525 if (!strncmp(key, voffset, len))
6526 match = TRUE;
6530 return (match);
6533 void
6534 cmd_getlist(int id, char *key)
6536 int i, dep, c = 0;
6537 struct history *h;
6539 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6540 RB_FOREACH_REVERSE(h, history_list, &hl)
6541 if (match_uri(h->uri, key)) {
6542 cmd_status.list[c] = (char *)h->uri;
6543 if (++c > 255)
6544 break;
6547 cmd_status.len = c;
6548 return;
6551 dep = (id == -1) ? 0 : cmds[id].level + 1;
6553 for (i = id + 1; i < LENGTH(cmds); i++) {
6554 if(cmds[i].level < dep)
6555 break;
6556 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6557 cmd_status.list[c++] = cmds[i].cmd;
6561 cmd_status.len = c;
6564 char *
6565 cmd_getnext(int dir)
6567 cmd_status.index += dir;
6569 if (cmd_status.index < 0)
6570 cmd_status.index = cmd_status.len - 1;
6571 else if (cmd_status.index >= cmd_status.len)
6572 cmd_status.index = 0;
6574 return cmd_status.list[cmd_status.index];
6578 cmd_tokenize(char *s, char *tokens[])
6580 int i = 0;
6581 char *tok, *last;
6582 size_t len = strlen(s);
6583 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6585 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6586 tokens[i] = tok;
6588 if (blank && i < 3)
6589 tokens[i++] = "";
6591 return (i);
6594 void
6595 cmd_complete(struct tab *t, char *str, int dir)
6597 GtkEntry *w = GTK_ENTRY(t->cmd);
6598 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6599 char *tok, *match, *s = g_strdup(str);
6600 char *tokens[3];
6601 char res[XT_MAX_URL_LENGTH + 32] = ":";
6602 char *sc = s;
6604 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6606 /* copy prefix*/
6607 for (i = 0; isdigit(s[i]); i++)
6608 res[i + 1] = s[i];
6610 for (; isspace(s[i]); i++)
6611 res[i + 1] = s[i];
6613 s += i;
6615 levels = cmd_tokenize(s, tokens);
6617 for (i = 0; i < levels - 1; i++) {
6618 tok = tokens[i];
6619 matchcount = 0;
6620 for (j = c; j < LENGTH(cmds); j++) {
6621 if (cmds[j].level < dep)
6622 break;
6623 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6624 matchcount++;
6625 c = j + 1;
6626 if (strlen(tok) == strlen(cmds[j].cmd)) {
6627 matchcount = 1;
6628 break;
6633 if (matchcount == 1) {
6634 strlcat(res, tok, sizeof res);
6635 strlcat(res, " ", sizeof res);
6636 dep++;
6637 } else {
6638 g_free(sc);
6639 return;
6642 parent = c - 1;
6645 if (cmd_status.index == -1)
6646 cmd_getlist(parent, tokens[i]);
6648 if (cmd_status.len > 0) {
6649 match = cmd_getnext(dir);
6650 strlcat(res, match, sizeof res);
6651 gtk_entry_set_text(w, res);
6652 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6655 g_free(sc);
6658 gboolean
6659 cmd_execute(struct tab *t, char *str)
6661 struct cmd *cmd = NULL;
6662 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6663 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6664 struct karg arg = {0, NULL, -1};
6665 int rv = XT_CB_PASSTHROUGH;
6667 sc = s;
6669 /* copy prefix*/
6670 for (j = 0; j<3 && isdigit(s[j]); j++)
6671 prefixstr[j]=s[j];
6673 prefixstr[j]='\0';
6675 s += j;
6676 while (isspace(s[0]))
6677 s++;
6679 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6680 prefix = atoi(prefixstr);
6681 else
6682 s = sc;
6684 for (tok = strtok_r(s, " ", &last); tok;
6685 tok = strtok_r(NULL, " ", &last)) {
6686 matchcount = 0;
6687 for (j = c; j < LENGTH(cmds); j++) {
6688 if (cmds[j].level < dep)
6689 break;
6690 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6691 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6692 matchcount++;
6693 c = j + 1;
6694 cmd = &cmds[j];
6695 if (len == strlen(cmds[j].cmd)) {
6696 matchcount = 1;
6697 break;
6701 if (matchcount == 1) {
6702 if (cmd->type > 0)
6703 goto execute_cmd;
6704 dep++;
6705 } else {
6706 show_oops(t, "Invalid command: %s", str);
6707 goto done;
6710 execute_cmd:
6711 arg.i = cmd->arg;
6713 if (prefix != -1)
6714 arg.p = prefix;
6715 else if (cmd_prefix > 0)
6716 arg.p = cmd_prefix;
6718 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6719 show_oops(t, "No prefix allowed: %s", str);
6720 goto done;
6722 if (cmd->type > 1)
6723 arg.s = last ? g_strdup(last) : g_strdup("");
6724 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6725 arg.p = atoi(arg.s);
6726 if (arg.p <= 0) {
6727 if (arg.s[0]=='0')
6728 show_oops(t, "Zero count");
6729 else
6730 show_oops(t, "Trailing characters");
6731 goto done;
6735 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6737 cmd->func(t, &arg);
6739 rv = XT_CB_HANDLED;
6740 done:
6741 if (j > 0)
6742 cmd_prefix = 0;
6743 g_free(sc);
6744 if (arg.s)
6745 g_free(arg.s);
6747 return (rv);
6751 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6753 if (t == NULL) {
6754 show_oops_s("entry_key_cb invalid parameters");
6755 return (XT_CB_PASSTHROUGH);
6758 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6759 e->keyval, e->state, t);
6761 hide_oops(t);
6763 if (e->keyval == GDK_Escape) {
6764 /* don't use focus_webview(t) because we want to type :cmds */
6765 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6768 return (handle_keypress(t, e, 1));
6772 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6774 int rv = XT_CB_HANDLED;
6775 const gchar *c = gtk_entry_get_text(w);
6777 if (t == NULL) {
6778 show_oops_s("cmd_keypress_cb parameters");
6779 return (XT_CB_PASSTHROUGH);
6782 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6783 e->keyval, e->state, t);
6785 /* sanity */
6786 if (c == NULL)
6787 e->keyval = GDK_Escape;
6788 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6789 e->keyval = GDK_Escape;
6791 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6792 cmd_status.index = -1;
6794 switch (e->keyval) {
6795 case GDK_Tab:
6796 if (c[0] == ':')
6797 cmd_complete(t, (char *)&c[1], 1);
6798 goto done;
6799 case GDK_ISO_Left_Tab:
6800 if (c[0] == ':')
6801 cmd_complete(t, (char *)&c[1], -1);
6803 goto done;
6804 case GDK_BackSpace:
6805 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6806 break;
6807 /* FALLTHROUGH */
6808 case GDK_Escape:
6809 hide_cmd(t);
6810 focus_webview(t);
6812 /* cancel search */
6813 if (c[0] == '/' || c[0] == '?')
6814 webkit_web_view_unmark_text_matches(t->wv);
6815 goto done;
6818 rv = XT_CB_PASSTHROUGH;
6819 done:
6820 return (rv);
6824 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6826 if (t == NULL) {
6827 show_oops_s("cmd_focusout_cb invalid parameters");
6828 return (XT_CB_PASSTHROUGH);
6830 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6832 hide_cmd(t);
6833 hide_oops(t);
6835 if (show_url == 0 || t->focus_wv)
6836 focus_webview(t);
6837 else
6838 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6840 return (XT_CB_PASSTHROUGH);
6843 void
6844 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6846 char *s;
6847 const gchar *c = gtk_entry_get_text(entry);
6849 if (t == NULL) {
6850 show_oops_s("cmd_activate_cb invalid parameters");
6851 return;
6854 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6856 hide_cmd(t);
6858 /* sanity */
6859 if (c == NULL)
6860 goto done;
6861 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6862 goto done;
6863 if (strlen(c) < 2)
6864 goto done;
6865 s = (char *)&c[1];
6867 if (c[0] == '/' || c[0] == '?') {
6868 if (t->search_text) {
6869 g_free(t->search_text);
6870 t->search_text = NULL;
6873 t->search_text = g_strdup(s);
6874 if (global_search)
6875 g_free(global_search);
6876 global_search = g_strdup(s);
6877 t->search_forward = c[0] == '/';
6879 goto done;
6882 cmd_execute(t, s);
6884 done:
6885 return;
6888 void
6889 backward_cb(GtkWidget *w, struct tab *t)
6891 struct karg a;
6893 if (t == NULL) {
6894 show_oops_s("backward_cb invalid parameters");
6895 return;
6898 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6900 a.i = XT_NAV_BACK;
6901 navaction(t, &a);
6904 void
6905 forward_cb(GtkWidget *w, struct tab *t)
6907 struct karg a;
6909 if (t == NULL) {
6910 show_oops_s("forward_cb invalid parameters");
6911 return;
6914 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6916 a.i = XT_NAV_FORWARD;
6917 navaction(t, &a);
6920 void
6921 home_cb(GtkWidget *w, struct tab *t)
6923 if (t == NULL) {
6924 show_oops_s("home_cb invalid parameters");
6925 return;
6928 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6930 load_uri(t, home);
6933 void
6934 stop_cb(GtkWidget *w, struct tab *t)
6936 WebKitWebFrame *frame;
6938 if (t == NULL) {
6939 show_oops_s("stop_cb invalid parameters");
6940 return;
6943 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6945 frame = webkit_web_view_get_main_frame(t->wv);
6946 if (frame == NULL) {
6947 show_oops(t, "stop_cb: no frame");
6948 return;
6951 webkit_web_frame_stop_loading(frame);
6952 abort_favicon_download(t);
6955 void
6956 setup_webkit(struct tab *t)
6958 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6959 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6960 FALSE, (char *)NULL);
6961 else
6962 warnx("webkit does not have \"enable-dns-prefetching\" property");
6963 g_object_set(G_OBJECT(t->settings),
6964 "user-agent", t->user_agent, (char *)NULL);
6965 g_object_set(G_OBJECT(t->settings),
6966 "enable-scripts", enable_scripts, (char *)NULL);
6967 g_object_set(G_OBJECT(t->settings),
6968 "enable-plugins", enable_plugins, (char *)NULL);
6969 g_object_set(G_OBJECT(t->settings),
6970 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6971 g_object_set(G_OBJECT(t->settings),
6972 "enable_spell_checking", enable_spell_checking, (char *)NULL);
6973 g_object_set(G_OBJECT(t->settings),
6974 "spell_checking_languages", spell_check_languages, (char *)NULL);
6975 g_object_set(G_OBJECT(t->wv),
6976 "full-content-zoom", TRUE, (char *)NULL);
6977 adjustfont_webkit(t, XT_FONT_SET);
6979 webkit_web_view_set_settings(t->wv, t->settings);
6982 GtkWidget *
6983 create_browser(struct tab *t)
6985 GtkWidget *w;
6986 gchar *strval;
6988 if (t == NULL) {
6989 show_oops_s("create_browser invalid parameters");
6990 return (NULL);
6993 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6994 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6995 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6996 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6998 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6999 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7000 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7002 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7003 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7005 /* set defaults */
7006 t->settings = webkit_web_settings_new();
7008 if (user_agent == NULL) {
7009 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7010 (char *)NULL);
7011 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7012 g_free(strval);
7013 } else
7014 t->user_agent = g_strdup(user_agent);
7016 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7018 setup_webkit(t);
7020 return (w);
7023 GtkWidget *
7024 create_window(void)
7026 GtkWidget *w;
7028 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7029 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7030 gtk_widget_set_name(w, "xxxterm");
7031 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7032 g_signal_connect(G_OBJECT(w), "delete_event",
7033 G_CALLBACK (gtk_main_quit), NULL);
7035 return (w);
7038 GtkWidget *
7039 create_kiosk_toolbar(struct tab *t)
7041 GtkWidget *toolbar = NULL, *b;
7043 b = gtk_hbox_new(FALSE, 0);
7044 toolbar = b;
7045 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7047 /* backward button */
7048 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7049 gtk_widget_set_sensitive(t->backward, FALSE);
7050 g_signal_connect(G_OBJECT(t->backward), "clicked",
7051 G_CALLBACK(backward_cb), t);
7052 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7054 /* forward button */
7055 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7056 gtk_widget_set_sensitive(t->forward, FALSE);
7057 g_signal_connect(G_OBJECT(t->forward), "clicked",
7058 G_CALLBACK(forward_cb), t);
7059 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7061 /* home button */
7062 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7063 gtk_widget_set_sensitive(t->gohome, true);
7064 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7065 G_CALLBACK(home_cb), t);
7066 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7068 /* create widgets but don't use them */
7069 t->uri_entry = gtk_entry_new();
7070 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7071 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7072 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7074 return (toolbar);
7077 GtkWidget *
7078 create_toolbar(struct tab *t)
7080 GtkWidget *toolbar = NULL, *b, *eb1;
7082 b = gtk_hbox_new(FALSE, 0);
7083 toolbar = b;
7084 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7086 if (fancy_bar) {
7087 /* backward button */
7088 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7089 gtk_widget_set_sensitive(t->backward, FALSE);
7090 g_signal_connect(G_OBJECT(t->backward), "clicked",
7091 G_CALLBACK(backward_cb), t);
7092 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7094 /* forward button */
7095 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7096 gtk_widget_set_sensitive(t->forward, FALSE);
7097 g_signal_connect(G_OBJECT(t->forward), "clicked",
7098 G_CALLBACK(forward_cb), t);
7099 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7100 FALSE, 0);
7102 /* stop button */
7103 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7104 gtk_widget_set_sensitive(t->stop, FALSE);
7105 g_signal_connect(G_OBJECT(t->stop), "clicked",
7106 G_CALLBACK(stop_cb), t);
7107 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7108 FALSE, 0);
7110 /* JS button */
7111 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7112 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7113 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7114 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7115 G_CALLBACK(js_toggle_cb), t);
7116 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7119 t->uri_entry = gtk_entry_new();
7120 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7121 G_CALLBACK(activate_uri_entry_cb), t);
7122 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7123 G_CALLBACK(entry_key_cb), t);
7124 completion_add(t);
7125 eb1 = gtk_hbox_new(FALSE, 0);
7126 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7127 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7128 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7130 /* search entry */
7131 if (fancy_bar && search_string) {
7132 GtkWidget *eb2;
7133 t->search_entry = gtk_entry_new();
7134 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7135 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7136 G_CALLBACK(activate_search_entry_cb), t);
7137 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7138 G_CALLBACK(entry_key_cb), t);
7139 gtk_widget_set_size_request(t->search_entry, -1, -1);
7140 eb2 = gtk_hbox_new(FALSE, 0);
7141 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7142 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7144 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7146 return (toolbar);
7149 void
7150 recalc_tabs(void)
7152 struct tab *t;
7154 TAILQ_FOREACH(t, &tabs, entry)
7155 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7159 undo_close_tab_save(struct tab *t)
7161 int m, n;
7162 const gchar *uri;
7163 struct undo *u1, *u2;
7164 GList *items;
7165 WebKitWebHistoryItem *item;
7167 if ((uri = get_uri(t->wv)) == NULL)
7168 return (1);
7170 u1 = g_malloc0(sizeof(struct undo));
7171 u1->uri = g_strdup(uri);
7173 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7175 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7176 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7177 u1->back = n;
7179 /* forward history */
7180 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7182 while (items) {
7183 item = items->data;
7184 u1->history = g_list_prepend(u1->history,
7185 webkit_web_history_item_copy(item));
7186 items = g_list_next(items);
7189 /* current item */
7190 if (m) {
7191 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7192 u1->history = g_list_prepend(u1->history,
7193 webkit_web_history_item_copy(item));
7196 /* back history */
7197 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7199 while (items) {
7200 item = items->data;
7201 u1->history = g_list_prepend(u1->history,
7202 webkit_web_history_item_copy(item));
7203 items = g_list_next(items);
7206 TAILQ_INSERT_HEAD(&undos, u1, entry);
7208 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7209 u2 = TAILQ_LAST(&undos, undo_tailq);
7210 TAILQ_REMOVE(&undos, u2, entry);
7211 g_free(u2->uri);
7212 g_list_free(u2->history);
7213 g_free(u2);
7214 } else
7215 undo_count++;
7217 return (0);
7220 void
7221 delete_tab(struct tab *t)
7223 struct karg a;
7225 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7227 if (t == NULL)
7228 return;
7230 TAILQ_REMOVE(&tabs, t, entry);
7232 /* halt all webkit activity */
7233 abort_favicon_download(t);
7234 webkit_web_view_stop_loading(t->wv);
7235 undo_close_tab_save(t);
7237 if (browser_mode == XT_BM_KIOSK) {
7238 gtk_widget_destroy(t->uri_entry);
7239 gtk_widget_destroy(t->stop);
7240 gtk_widget_destroy(t->js_toggle);
7243 gtk_widget_destroy(t->vbox);
7244 g_free(t->user_agent);
7245 g_free(t->stylesheet);
7246 g_free(t);
7248 if (TAILQ_EMPTY(&tabs)) {
7249 if (browser_mode == XT_BM_KIOSK)
7250 create_new_tab(home, NULL, 1, -1);
7251 else
7252 create_new_tab(NULL, NULL, 1, -1);
7255 /* recreate session */
7256 if (session_autosave) {
7257 a.s = NULL;
7258 save_tabs(t, &a);
7262 void
7263 adjustfont_webkit(struct tab *t, int adjust)
7265 gfloat zoom;
7267 if (t == NULL) {
7268 show_oops_s("adjustfont_webkit invalid parameters");
7269 return;
7272 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7273 if (adjust == XT_FONT_SET) {
7274 t->font_size = default_font_size;
7275 zoom = default_zoom_level;
7276 t->font_size += adjust;
7277 g_object_set(G_OBJECT(t->settings), "default-font-size",
7278 t->font_size, (char *)NULL);
7279 g_object_get(G_OBJECT(t->settings), "default-font-size",
7280 &t->font_size, (char *)NULL);
7281 } else {
7282 t->font_size += adjust;
7283 zoom += adjust/25.0;
7284 if (zoom < 0.0) {
7285 zoom = 0.04;
7288 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7289 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7292 void
7293 append_tab(struct tab *t)
7295 if (t == NULL)
7296 return;
7298 TAILQ_INSERT_TAIL(&tabs, t, entry);
7299 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7302 struct tab *
7303 create_new_tab(char *title, struct undo *u, int focus, int position)
7305 struct tab *t;
7306 int load = 1, id;
7307 GtkWidget *b, *bb;
7308 WebKitWebHistoryItem *item;
7309 GList *items;
7310 GdkColor color;
7312 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7314 if (tabless && !TAILQ_EMPTY(&tabs)) {
7315 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7316 return (NULL);
7319 t = g_malloc0(sizeof *t);
7321 if (title == NULL) {
7322 title = "(untitled)";
7323 load = 0;
7326 t->vbox = gtk_vbox_new(FALSE, 0);
7328 /* label + button for tab */
7329 b = gtk_hbox_new(FALSE, 0);
7330 t->tab_content = b;
7332 #if GTK_CHECK_VERSION(2, 20, 0)
7333 t->spinner = gtk_spinner_new ();
7334 #endif
7335 t->label = gtk_label_new(title);
7336 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7337 gtk_widget_set_size_request(t->label, 100, 0);
7338 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7339 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7340 gtk_widget_set_size_request(b, 130, 0);
7342 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7343 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7344 #if GTK_CHECK_VERSION(2, 20, 0)
7345 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7346 #endif
7348 /* toolbar */
7349 if (browser_mode == XT_BM_KIOSK)
7350 t->toolbar = create_kiosk_toolbar(t);
7351 else
7352 t->toolbar = create_toolbar(t);
7354 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7356 /* browser */
7357 t->browser_win = create_browser(t);
7358 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7360 /* oops message for user feedback */
7361 t->oops = gtk_entry_new();
7362 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7363 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7364 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7365 gdk_color_parse(XT_COLOR_RED, &color);
7366 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7367 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7369 /* command entry */
7370 t->cmd = gtk_entry_new();
7371 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7372 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7373 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7375 /* status bar */
7376 t->statusbar = gtk_entry_new();
7377 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7378 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7379 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7380 gdk_color_parse(XT_COLOR_BLACK, &color);
7381 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7382 gdk_color_parse(XT_COLOR_WHITE, &color);
7383 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7384 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7386 /* xtp meaning is normal by default */
7387 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7389 /* set empty favicon */
7390 xt_icon_from_name(t, "text-html");
7392 /* and show it all */
7393 gtk_widget_show_all(b);
7394 gtk_widget_show_all(t->vbox);
7396 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7397 append_tab(t);
7398 else {
7399 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7400 if (id > gtk_notebook_get_n_pages(notebook))
7401 append_tab(t);
7402 else {
7403 TAILQ_INSERT_TAIL(&tabs, t, entry);
7404 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7405 recalc_tabs();
7409 #if GTK_CHECK_VERSION(2, 20, 0)
7410 /* turn spinner off if we are a new tab without uri */
7411 if (!load) {
7412 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7413 gtk_widget_hide(t->spinner);
7415 #endif
7416 /* make notebook tabs reorderable */
7417 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7419 g_object_connect(G_OBJECT(t->cmd),
7420 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7421 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7422 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7423 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7424 (char *)NULL);
7426 /* reuse wv_button_cb to hide oops */
7427 g_object_connect(G_OBJECT(t->oops),
7428 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7429 (char *)NULL);
7431 g_object_connect(G_OBJECT(t->wv),
7432 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7433 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7434 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7435 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7436 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7437 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7438 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7439 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7440 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7441 "signal::event", G_CALLBACK(webview_event_cb), t,
7442 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7443 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7444 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7445 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7446 #endif
7447 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7448 (char *)NULL);
7449 g_signal_connect(t->wv,
7450 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7451 g_signal_connect(t->wv,
7452 "notify::title", G_CALLBACK(notify_title_cb), t);
7454 /* hijack the unused keys as if we were the browser */
7455 g_object_connect(G_OBJECT(t->toolbar),
7456 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7457 (char *)NULL);
7459 g_signal_connect(G_OBJECT(bb), "button_press_event",
7460 G_CALLBACK(tab_close_cb), t);
7462 /* hide stuff */
7463 hide_cmd(t);
7464 hide_oops(t);
7465 url_set_visibility();
7466 statusbar_set_visibility();
7468 if (focus) {
7469 gtk_notebook_set_current_page(notebook, t->tab_id);
7470 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7471 t->tab_id);
7474 if (load) {
7475 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7476 load_uri(t, title);
7477 } else {
7478 if (show_url == 1)
7479 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7480 else
7481 focus_webview(t);
7484 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7485 /* restore the tab's history */
7486 if (u && u->history) {
7487 items = u->history;
7488 while (items) {
7489 item = items->data;
7490 webkit_web_back_forward_list_add_item(t->bfl, item);
7491 items = g_list_next(items);
7494 item = g_list_nth_data(u->history, u->back);
7495 if (item)
7496 webkit_web_view_go_to_back_forward_item(t->wv, item);
7498 g_list_free(items);
7499 g_list_free(u->history);
7500 } else
7501 webkit_web_back_forward_list_clear(t->bfl);
7503 return (t);
7506 void
7507 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7508 gpointer *udata)
7510 struct tab *t;
7511 const gchar *uri;
7513 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7515 if (gtk_notebook_get_current_page(notebook) == -1)
7516 recalc_tabs();
7518 TAILQ_FOREACH(t, &tabs, entry) {
7519 if (t->tab_id == pn) {
7520 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7521 "%d\n", pn);
7523 uri = webkit_web_view_get_title(t->wv);
7524 if (uri == NULL)
7525 uri = XT_NAME;
7526 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7528 hide_cmd(t);
7529 hide_oops(t);
7531 if (t->focus_wv) {
7532 /* can't use focus_webview here */
7533 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7539 void
7540 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7541 gpointer *udata)
7543 recalc_tabs();
7546 void
7547 menuitem_response(struct tab *t)
7549 gtk_notebook_set_current_page(notebook, t->tab_id);
7552 gboolean
7553 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7555 GtkWidget *menu, *menu_items;
7556 GdkEventButton *bevent;
7557 const gchar *uri;
7558 struct tab *ti;
7560 if (event->type == GDK_BUTTON_PRESS) {
7561 bevent = (GdkEventButton *) event;
7562 menu = gtk_menu_new();
7564 TAILQ_FOREACH(ti, &tabs, entry) {
7565 if ((uri = get_uri(ti->wv)) == NULL)
7566 /* XXX make sure there is something to print */
7567 /* XXX add gui pages in here to look purdy */
7568 uri = "(untitled)";
7569 menu_items = gtk_menu_item_new_with_label(uri);
7570 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7571 gtk_widget_show(menu_items);
7573 g_signal_connect_swapped((menu_items),
7574 "activate", G_CALLBACK(menuitem_response),
7575 (gpointer)ti);
7578 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7579 bevent->button, bevent->time);
7581 /* unref object so it'll free itself when popped down */
7582 #if !GTK_CHECK_VERSION(3, 0, 0)
7583 /* XXX does not need unref with gtk+3? */
7584 g_object_ref_sink(menu);
7585 g_object_unref(menu);
7586 #endif
7588 return (TRUE /* eat event */);
7591 return (FALSE /* propagate */);
7595 icon_size_map(int icon_size)
7597 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7598 icon_size > GTK_ICON_SIZE_DIALOG)
7599 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7601 return (icon_size);
7604 GtkWidget *
7605 create_button(char *name, char *stockid, int size)
7607 GtkWidget *button, *image;
7608 gchar *rcstring;
7609 int gtk_icon_size;
7611 rcstring = g_strdup_printf(
7612 "style \"%s-style\"\n"
7613 "{\n"
7614 " GtkWidget::focus-padding = 0\n"
7615 " GtkWidget::focus-line-width = 0\n"
7616 " xthickness = 0\n"
7617 " ythickness = 0\n"
7618 "}\n"
7619 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7620 gtk_rc_parse_string(rcstring);
7621 g_free(rcstring);
7622 button = gtk_button_new();
7623 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7624 gtk_icon_size = icon_size_map(size ? size : icon_size);
7626 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7627 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7628 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7629 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7630 gtk_widget_set_name(button, name);
7631 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7633 return (button);
7636 void
7637 button_set_stockid(GtkWidget *button, char *stockid)
7639 GtkWidget *image;
7641 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7642 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7643 gtk_button_set_image(GTK_BUTTON(button), image);
7646 void
7647 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
7649 GtkClipboard *clipboard;
7650 gchar *p = NULL, *s = NULL;
7653 * This code is very aggressive!
7654 * It basically ensures that the primary and regular clipboard are
7655 * always set the same. This obviously messes with standard X protocol
7656 * but those clowns should have come up with something better.
7659 /* XXX make this setting? */
7660 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7661 p = gtk_clipboard_wait_for_text(primary);
7662 if (p == NULL) {
7663 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
7664 p = gtk_clipboard_wait_for_text(clipboard);
7665 if (p)
7666 gtk_clipboard_set_text(primary, p, -1);
7667 } else {
7668 DNPRINTF(XT_D_CLIP, "primary got selection\n");
7669 s = gtk_clipboard_wait_for_text(clipboard);
7670 if (s) {
7672 * if s and p are the same the string was set by
7673 * clipb_clipboard_cb so do nothing in that case
7674 * to prevent endless loop
7676 if (!strcmp(s, p))
7677 goto done;
7679 gtk_clipboard_set_text(clipboard, p, -1);
7681 done:
7682 if (p)
7683 g_free(p);
7684 if (s)
7685 g_free(s);
7688 void
7689 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
7691 GtkClipboard *primary;
7692 gchar *p = NULL, *s = NULL;
7694 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
7696 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7697 p = gtk_clipboard_wait_for_text(clipboard);
7698 if (p) {
7699 s = gtk_clipboard_wait_for_text(primary);
7700 if (s) {
7702 * if s and p are the same the string was set by
7703 * clipb_primary_cb so do nothing in that case
7704 * to prevent endless loop and deselection of text
7706 if (!strcmp(s, p))
7707 goto done;
7709 gtk_clipboard_set_text(primary, p, -1);
7711 done:
7712 if (p)
7713 g_free(p);
7714 if (s)
7715 g_free(s);
7718 void
7719 create_canvas(void)
7721 GtkWidget *vbox;
7722 GList *l = NULL;
7723 GdkPixbuf *pb;
7724 char file[PATH_MAX];
7725 int i;
7727 vbox = gtk_vbox_new(FALSE, 0);
7728 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7729 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7730 #if !GTK_CHECK_VERSION(3, 0, 0)
7731 /* XXX seems to be needed with gtk+2 */
7732 gtk_notebook_set_tab_hborder(notebook, 0);
7733 gtk_notebook_set_tab_vborder(notebook, 0);
7734 #endif
7735 gtk_notebook_set_scrollable(notebook, TRUE);
7736 notebook_tab_set_visibility(notebook);
7737 gtk_notebook_set_show_border(notebook, FALSE);
7738 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7740 abtn = gtk_button_new();
7741 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7742 gtk_widget_set_size_request(arrow, -1, -1);
7743 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7744 gtk_widget_set_size_request(abtn, -1, 20);
7746 #if GTK_CHECK_VERSION(2, 20, 0)
7747 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7748 #endif
7749 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7750 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7751 gtk_widget_set_size_request(vbox, -1, -1);
7753 g_object_connect(G_OBJECT(notebook),
7754 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7755 (char *)NULL);
7756 g_object_connect(G_OBJECT(notebook),
7757 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
7758 (char *)NULL);
7759 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7760 G_CALLBACK(arrow_cb), NULL);
7762 main_window = create_window();
7763 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7764 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7766 /* icons */
7767 for (i = 0; i < LENGTH(icons); i++) {
7768 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7769 pb = gdk_pixbuf_new_from_file(file, NULL);
7770 l = g_list_append(l, pb);
7772 gtk_window_set_default_icon_list(l);
7774 /* clipboard */
7775 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
7776 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
7777 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
7778 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
7780 gtk_widget_show_all(abtn);
7781 gtk_widget_show_all(main_window);
7784 void
7785 set_hook(void **hook, char *name)
7787 if (hook == NULL)
7788 errx(1, "set_hook");
7790 if (*hook == NULL) {
7791 *hook = dlsym(RTLD_NEXT, name);
7792 if (*hook == NULL)
7793 errx(1, "can't hook %s", name);
7797 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7798 gboolean
7799 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7801 g_return_val_if_fail(cookie1, FALSE);
7802 g_return_val_if_fail(cookie2, FALSE);
7804 return (!strcmp (cookie1->name, cookie2->name) &&
7805 !strcmp (cookie1->value, cookie2->value) &&
7806 !strcmp (cookie1->path, cookie2->path) &&
7807 !strcmp (cookie1->domain, cookie2->domain));
7810 void
7811 transfer_cookies(void)
7813 GSList *cf;
7814 SoupCookie *sc, *pc;
7816 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7818 for (;cf; cf = cf->next) {
7819 pc = cf->data;
7820 sc = soup_cookie_copy(pc);
7821 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7824 soup_cookies_free(cf);
7827 void
7828 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7830 GSList *cf;
7831 SoupCookie *ci;
7833 print_cookie("soup_cookie_jar_delete_cookie", c);
7835 if (cookies_enabled == 0)
7836 return;
7838 if (jar == NULL || c == NULL)
7839 return;
7841 /* find and remove from persistent jar */
7842 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7844 for (;cf; cf = cf->next) {
7845 ci = cf->data;
7846 if (soup_cookie_equal(ci, c)) {
7847 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7848 break;
7852 soup_cookies_free(cf);
7854 /* delete from session jar */
7855 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7858 void
7859 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7861 struct domain *d = NULL;
7862 SoupCookie *c;
7863 FILE *r_cookie_f;
7865 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7866 jar, p_cookiejar, s_cookiejar);
7868 if (cookies_enabled == 0)
7869 return;
7871 /* see if we are up and running */
7872 if (p_cookiejar == NULL) {
7873 _soup_cookie_jar_add_cookie(jar, cookie);
7874 return;
7876 /* disallow p_cookiejar adds, shouldn't happen */
7877 if (jar == p_cookiejar)
7878 return;
7880 /* sanity */
7881 if (jar == NULL || cookie == NULL)
7882 return;
7884 if (enable_cookie_whitelist &&
7885 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7886 blocked_cookies++;
7887 DNPRINTF(XT_D_COOKIE,
7888 "soup_cookie_jar_add_cookie: reject %s\n",
7889 cookie->domain);
7890 if (save_rejected_cookies) {
7891 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7892 show_oops_s("can't open reject cookie file");
7893 return;
7895 fseek(r_cookie_f, 0, SEEK_END);
7896 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7897 cookie->http_only ? "#HttpOnly_" : "",
7898 cookie->domain,
7899 *cookie->domain == '.' ? "TRUE" : "FALSE",
7900 cookie->path,
7901 cookie->secure ? "TRUE" : "FALSE",
7902 cookie->expires ?
7903 (gulong)soup_date_to_time_t(cookie->expires) :
7905 cookie->name,
7906 cookie->value);
7907 fflush(r_cookie_f);
7908 fclose(r_cookie_f);
7910 if (!allow_volatile_cookies)
7911 return;
7914 if (cookie->expires == NULL && session_timeout) {
7915 soup_cookie_set_expires(cookie,
7916 soup_date_new_from_now(session_timeout));
7917 print_cookie("modified add cookie", cookie);
7920 /* see if we are white listed for persistence */
7921 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7922 /* add to persistent jar */
7923 c = soup_cookie_copy(cookie);
7924 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7925 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7928 /* add to session jar */
7929 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7930 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7933 void
7934 setup_cookies(void)
7936 char file[PATH_MAX];
7938 set_hook((void *)&_soup_cookie_jar_add_cookie,
7939 "soup_cookie_jar_add_cookie");
7940 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7941 "soup_cookie_jar_delete_cookie");
7943 if (cookies_enabled == 0)
7944 return;
7947 * the following code is intricate due to overriding several libsoup
7948 * functions.
7949 * do not alter order of these operations.
7952 /* rejected cookies */
7953 if (save_rejected_cookies)
7954 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
7956 /* persistent cookies */
7957 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
7958 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7960 /* session cookies */
7961 s_cookiejar = soup_cookie_jar_new();
7962 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7963 cookie_policy, (void *)NULL);
7964 transfer_cookies();
7966 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7969 void
7970 setup_proxy(char *uri)
7972 if (proxy_uri) {
7973 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7974 soup_uri_free(proxy_uri);
7975 proxy_uri = NULL;
7977 if (http_proxy) {
7978 if (http_proxy != uri) {
7979 g_free(http_proxy);
7980 http_proxy = NULL;
7984 if (uri) {
7985 http_proxy = g_strdup(uri);
7986 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7987 proxy_uri = soup_uri_new(http_proxy);
7988 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7993 send_cmd_to_socket(char *cmd)
7995 int s, len, rv = 1;
7996 struct sockaddr_un sa;
7998 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7999 warnx("%s: socket", __func__);
8000 return (rv);
8003 sa.sun_family = AF_UNIX;
8004 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8005 work_dir, XT_SOCKET_FILE);
8006 len = SUN_LEN(&sa);
8008 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8009 warnx("%s: connect", __func__);
8010 goto done;
8013 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8014 warnx("%s: send", __func__);
8015 goto done;
8018 rv = 0;
8019 done:
8020 close(s);
8021 return (rv);
8024 gboolean
8025 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8027 int s, n;
8028 char str[XT_MAX_URL_LENGTH];
8029 socklen_t t = sizeof(struct sockaddr_un);
8030 struct sockaddr_un sa;
8031 struct passwd *p;
8032 uid_t uid;
8033 gid_t gid;
8034 struct tab *tt;
8035 gint fd = g_io_channel_unix_get_fd(source);
8037 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8038 warn("accept");
8039 return (FALSE);
8042 if (getpeereid(s, &uid, &gid) == -1) {
8043 warn("getpeereid");
8044 return (FALSE);
8046 if (uid != getuid() || gid != getgid()) {
8047 warnx("unauthorized user");
8048 return (FALSE);
8051 p = getpwuid(uid);
8052 if (p == NULL) {
8053 warnx("not a valid user");
8054 return (FALSE);
8057 n = recv(s, str, sizeof(str), 0);
8058 if (n <= 0)
8059 return (FALSE);
8061 tt = TAILQ_LAST(&tabs, tab_list);
8062 cmd_execute(tt, str);
8063 return (TRUE);
8067 is_running(void)
8069 int s, len, rv = 1;
8070 struct sockaddr_un sa;
8072 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8073 warn("is_running: socket");
8074 return (-1);
8077 sa.sun_family = AF_UNIX;
8078 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8079 work_dir, XT_SOCKET_FILE);
8080 len = SUN_LEN(&sa);
8082 /* connect to see if there is a listener */
8083 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8084 rv = 0; /* not running */
8085 else
8086 rv = 1; /* already running */
8088 close(s);
8090 return (rv);
8094 build_socket(void)
8096 int s, len;
8097 struct sockaddr_un sa;
8099 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8100 warn("build_socket: socket");
8101 return (-1);
8104 sa.sun_family = AF_UNIX;
8105 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8106 work_dir, XT_SOCKET_FILE);
8107 len = SUN_LEN(&sa);
8109 /* connect to see if there is a listener */
8110 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8111 /* no listener so we will */
8112 unlink(sa.sun_path);
8114 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8115 warn("build_socket: bind");
8116 goto done;
8119 if (listen(s, 1) == -1) {
8120 warn("build_socket: listen");
8121 goto done;
8124 return (s);
8127 done:
8128 close(s);
8129 return (-1);
8132 gboolean
8133 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8134 GtkTreeIter *iter, struct tab *t)
8136 gchar *value;
8138 gtk_tree_model_get(model, iter, 0, &value, -1);
8139 load_uri(t, value);
8140 g_free(value);
8142 return (FALSE);
8145 gboolean
8146 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8147 GtkTreeIter *iter, struct tab *t)
8149 gchar *value;
8151 gtk_tree_model_get(model, iter, 0, &value, -1);
8152 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8153 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8154 g_free(value);
8156 return (TRUE);
8159 void
8160 completion_add_uri(const gchar *uri)
8162 GtkTreeIter iter;
8164 /* add uri to list_store */
8165 gtk_list_store_append(completion_model, &iter);
8166 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8169 gboolean
8170 completion_match(GtkEntryCompletion *completion, const gchar *key,
8171 GtkTreeIter *iter, gpointer user_data)
8173 gchar *value;
8174 gboolean match = FALSE;
8176 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8177 -1);
8179 if (value == NULL)
8180 return FALSE;
8182 match = match_uri(value, key);
8184 g_free(value);
8185 return (match);
8188 void
8189 completion_add(struct tab *t)
8191 /* enable completion for tab */
8192 t->completion = gtk_entry_completion_new();
8193 gtk_entry_completion_set_text_column(t->completion, 0);
8194 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8195 gtk_entry_completion_set_model(t->completion,
8196 GTK_TREE_MODEL(completion_model));
8197 gtk_entry_completion_set_match_func(t->completion, completion_match,
8198 NULL, NULL);
8199 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8200 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8201 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8202 G_CALLBACK(completion_select_cb), t);
8203 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8204 G_CALLBACK(completion_hover_cb), t);
8207 void
8208 xxx_dir(char *dir)
8210 struct stat sb;
8212 if (stat(dir, &sb)) {
8213 if (mkdir(dir, S_IRWXU) == -1)
8214 err(1, "mkdir %s", dir);
8215 if (stat(dir, &sb))
8216 err(1, "stat %s", dir);
8218 if (S_ISDIR(sb.st_mode) == 0)
8219 errx(1, "%s not a dir", dir);
8220 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8221 warnx("fixing invalid permissions on %s", dir);
8222 if (chmod(dir, S_IRWXU) == -1)
8223 err(1, "chmod %s", dir);
8227 void
8228 usage(void)
8230 fprintf(stderr,
8231 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8232 exit(0);
8237 main(int argc, char *argv[])
8239 struct stat sb;
8240 int c, s, optn = 0, opte = 0, focus = 1;
8241 char conf[PATH_MAX] = { '\0' };
8242 char file[PATH_MAX];
8243 char *env_proxy = NULL;
8244 FILE *f = NULL;
8245 struct karg a;
8246 struct sigaction sact;
8247 gchar *priority = g_strdup("NORMAL");
8248 GIOChannel *channel;
8250 start_argv = argv;
8252 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8254 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8255 switch (c) {
8256 case 'S':
8257 show_url = 0;
8258 break;
8259 case 'T':
8260 show_tabs = 0;
8261 break;
8262 case 'V':
8263 errx(0 , "Version: %s", version);
8264 break;
8265 case 'f':
8266 strlcpy(conf, optarg, sizeof(conf));
8267 break;
8268 case 's':
8269 strlcpy(named_session, optarg, sizeof(named_session));
8270 break;
8271 case 't':
8272 tabless = 1;
8273 break;
8274 case 'n':
8275 optn = 1;
8276 break;
8277 case 'e':
8278 opte = 1;
8279 break;
8280 default:
8281 usage();
8282 /* NOTREACHED */
8285 argc -= optind;
8286 argv += optind;
8288 RB_INIT(&hl);
8289 RB_INIT(&js_wl);
8290 RB_INIT(&downloads);
8292 TAILQ_INIT(&tabs);
8293 TAILQ_INIT(&mtl);
8294 TAILQ_INIT(&aliases);
8295 TAILQ_INIT(&undos);
8296 TAILQ_INIT(&kbl);
8298 init_keybindings();
8300 gnutls_global_init();
8302 /* generate session keys for xtp pages */
8303 generate_xtp_session_key(&dl_session_key);
8304 generate_xtp_session_key(&hl_session_key);
8305 generate_xtp_session_key(&cl_session_key);
8306 generate_xtp_session_key(&fl_session_key);
8308 /* prepare gtk */
8309 gtk_init(&argc, &argv);
8310 if (!g_thread_supported())
8311 g_thread_init(NULL);
8313 /* signals */
8314 bzero(&sact, sizeof(sact));
8315 sigemptyset(&sact.sa_mask);
8316 sact.sa_handler = sigchild;
8317 sact.sa_flags = SA_NOCLDSTOP;
8318 sigaction(SIGCHLD, &sact, NULL);
8320 /* set download dir */
8321 pwd = getpwuid(getuid());
8322 if (pwd == NULL)
8323 errx(1, "invalid user %d", getuid());
8324 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8326 /* set default string settings */
8327 home = g_strdup("https://www.cyphertite.com");
8328 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8329 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8330 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8332 /* read config file */
8333 if (strlen(conf) == 0)
8334 snprintf(conf, sizeof conf, "%s/.%s",
8335 pwd->pw_dir, XT_CONF_FILE);
8336 config_parse(conf, 0);
8338 /* working directory */
8339 if (strlen(work_dir) == 0)
8340 snprintf(work_dir, sizeof work_dir, "%s/%s",
8341 pwd->pw_dir, XT_DIR);
8342 xxx_dir(work_dir);
8344 /* icon cache dir */
8345 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8346 xxx_dir(cache_dir);
8348 /* certs dir */
8349 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8350 xxx_dir(certs_dir);
8352 /* sessions dir */
8353 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8354 work_dir, XT_SESSIONS_DIR);
8355 xxx_dir(sessions_dir);
8357 /* runtime settings that can override config file */
8358 if (runtime_settings[0] != '\0')
8359 config_parse(runtime_settings, 1);
8361 /* download dir */
8362 if (!strcmp(download_dir, pwd->pw_dir))
8363 strlcat(download_dir, "/downloads", sizeof download_dir);
8364 xxx_dir(download_dir);
8366 /* favorites file */
8367 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8368 if (stat(file, &sb)) {
8369 warnx("favorites file doesn't exist, creating it");
8370 if ((f = fopen(file, "w")) == NULL)
8371 err(1, "favorites");
8372 fclose(f);
8375 /* cookies */
8376 session = webkit_get_default_session();
8377 /* XXX ssl-priority property not quite available yet */
8378 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8379 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8380 (char *)NULL);
8381 else
8382 warnx("session does not have \"ssl-priority\" property");
8383 setup_cookies();
8385 /* certs */
8386 if (ssl_ca_file) {
8387 if (stat(ssl_ca_file, &sb)) {
8388 warnx("no CA file: %s", ssl_ca_file);
8389 g_free(ssl_ca_file);
8390 ssl_ca_file = NULL;
8391 } else
8392 g_object_set(session,
8393 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8394 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8395 (void *)NULL);
8398 /* proxy */
8399 env_proxy = getenv("http_proxy");
8400 if (env_proxy)
8401 setup_proxy(env_proxy);
8402 else
8403 setup_proxy(http_proxy);
8405 if (opte) {
8406 send_cmd_to_socket(argv[0]);
8407 exit(0);
8410 /* set some connection parameters */
8411 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8412 g_object_set(session, "max-conns-per-host", max_host_connections,
8413 (char *)NULL);
8415 /* see if there is already an xxxterm running */
8416 if (single_instance && is_running()) {
8417 optn = 1;
8418 warnx("already running");
8421 char *cmd = NULL;
8422 if (optn) {
8423 while (argc) {
8424 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8425 send_cmd_to_socket(cmd);
8426 if (cmd)
8427 g_free(cmd);
8429 argc--;
8430 argv++;
8432 exit(0);
8435 /* uri completion */
8436 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8438 /* go graphical */
8439 create_canvas();
8441 if (save_global_history)
8442 restore_global_history();
8444 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8445 restore_saved_tabs();
8446 else {
8447 a.s = named_session;
8448 a.i = XT_SES_DONOTHING;
8449 open_tabs(NULL, &a);
8452 while (argc) {
8453 create_new_tab(argv[0], NULL, focus, -1);
8454 focus = 0;
8456 argc--;
8457 argv++;
8460 if (TAILQ_EMPTY(&tabs))
8461 create_new_tab(home, NULL, 1, -1);
8463 if (enable_socket)
8464 if ((s = build_socket()) != -1) {
8465 channel = g_io_channel_unix_new(s);
8466 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8469 gtk_main();
8471 gnutls_global_deinit();
8473 return (0);