only copy clipboards onto each other if the mouse has been released;
[xxxterm.git] / xxxterm.c
blobda7f5fa62d68ebbdd3a0a7bd70512ec10f1b846f
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, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <err.h>
35 #include <pwd.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <pthread.h>
39 #include <dlfcn.h>
40 #include <errno.h>
41 #include <signal.h>
42 #include <libgen.h>
43 #include <ctype.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #if defined(__linux__)
48 #include "linux/util.h"
49 #include "linux/tree.h"
50 #elif defined(__FreeBSD__)
51 #include <libutil.h>
52 #include "freebsd/util.h"
53 #include <sys/tree.h>
54 #else /* OpenBSD */
55 #include <util.h>
56 #include <sys/tree.h>
57 #endif
58 #include <sys/queue.h>
59 #include <sys/stat.h>
60 #include <sys/socket.h>
61 #include <sys/un.h>
62 #include <sys/time.h>
63 #include <sys/resource.h>
65 #include <gtk/gtk.h>
66 #include <gdk/gdkkeysyms.h>
68 #if GTK_CHECK_VERSION(3,0,0)
69 /* we still use GDK_* instead of GDK_KEY_* */
70 #include <gdk/gdkkeysyms-compat.h>
71 #endif
73 #include <webkit/webkit.h>
74 #include <libsoup/soup.h>
75 #include <gnutls/gnutls.h>
76 #include <JavaScriptCore/JavaScript.h>
77 #include <gnutls/x509.h>
79 #include "javascript.h"
82 javascript.h borrowed from vimprobable2 under the following license:
84 Copyright (c) 2009 Leon Winter
85 Copyright (c) 2009 Hannes Schueller
86 Copyright (c) 2009 Matto Fransen
88 Permission is hereby granted, free of charge, to any person obtaining a copy
89 of this software and associated documentation files (the "Software"), to deal
90 in the Software without restriction, including without limitation the rights
91 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
92 copies of the Software, and to permit persons to whom the Software is
93 furnished to do so, subject to the following conditions:
95 The above copyright notice and this permission notice shall be included in
96 all copies or substantial portions of the Software.
98 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
99 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
100 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
101 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
102 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
103 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
104 THE SOFTWARE.
107 static char *version = "$xxxterm$";
109 /* hooked functions */
110 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
111 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
112 SoupCookie *);
114 /*#define XT_DEBUG*/
115 #ifdef XT_DEBUG
116 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
117 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
118 #define XT_D_MOVE 0x0001
119 #define XT_D_KEY 0x0002
120 #define XT_D_TAB 0x0004
121 #define XT_D_URL 0x0008
122 #define XT_D_CMD 0x0010
123 #define XT_D_NAV 0x0020
124 #define XT_D_DOWNLOAD 0x0040
125 #define XT_D_CONFIG 0x0080
126 #define XT_D_JS 0x0100
127 #define XT_D_FAVORITE 0x0200
128 #define XT_D_PRINTING 0x0400
129 #define XT_D_COOKIE 0x0800
130 #define XT_D_KEYBINDING 0x1000
131 #define XT_D_CLIP 0x2000
132 u_int32_t swm_debug = 0
133 | XT_D_MOVE
134 | XT_D_KEY
135 | XT_D_TAB
136 | XT_D_URL
137 | XT_D_CMD
138 | XT_D_NAV
139 | XT_D_DOWNLOAD
140 | XT_D_CONFIG
141 | XT_D_JS
142 | XT_D_FAVORITE
143 | XT_D_PRINTING
144 | XT_D_COOKIE
145 | XT_D_KEYBINDING
146 | XT_D_CLIP
148 #else
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
151 #endif
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
159 ~(GDK_BUTTON5_MASK))
161 char *icons[] = {
162 "xxxtermicon16.png",
163 "xxxtermicon32.png",
164 "xxxtermicon48.png",
165 "xxxtermicon64.png",
166 "xxxtermicon128.png"
169 struct tab {
170 TAILQ_ENTRY(tab) entry;
171 GtkWidget *vbox;
172 GtkWidget *tab_content;
173 struct {
174 GtkWidget *label;
175 GtkWidget *eventbox;
176 GtkWidget *box;
177 GtkWidget *sep;
178 } tab_elems;
179 GtkWidget *label;
180 GtkWidget *spinner;
181 GtkWidget *uri_entry;
182 GtkWidget *search_entry;
183 GtkWidget *toolbar;
184 GtkWidget *browser_win;
185 GtkWidget *statusbar_box;
186 struct {
187 GtkWidget *statusbar;
188 GtkWidget *position;
189 } sbe;
190 GtkWidget *cmd;
191 GtkWidget *buffers;
192 GtkWidget *oops;
193 GtkWidget *backward;
194 GtkWidget *forward;
195 GtkWidget *stop;
196 GtkWidget *gohome;
197 GtkWidget *js_toggle;
198 GtkEntryCompletion *completion;
199 guint tab_id;
200 WebKitWebView *wv;
202 WebKitWebHistoryItem *item;
203 WebKitWebBackForwardList *bfl;
205 /* favicon */
206 WebKitNetworkRequest *icon_request;
207 WebKitDownload *icon_download;
208 gchar *icon_dest_uri;
210 /* adjustments for browser */
211 GtkScrollbar *sb_h;
212 GtkScrollbar *sb_v;
213 GtkAdjustment *adjust_h;
214 GtkAdjustment *adjust_v;
216 /* flags */
217 int focus_wv;
218 int ctrl_click;
219 gchar *status;
220 int xtp_meaning; /* identifies dls/favorites */
222 /* hints */
223 int hints_on;
224 int hint_mode;
225 #define XT_HINT_NONE (0)
226 #define XT_HINT_NUMERICAL (1)
227 #define XT_HINT_ALPHANUM (2)
228 char hint_buf[128];
229 char hint_num[128];
231 /* custom stylesheet */
232 int styled;
233 char *stylesheet;
235 /* search */
236 char *search_text;
237 int search_forward;
239 /* settings */
240 WebKitWebSettings *settings;
241 int font_size;
242 gchar *user_agent;
244 TAILQ_HEAD(tab_list, tab);
246 struct history {
247 RB_ENTRY(history) entry;
248 const gchar *uri;
249 const gchar *title;
251 RB_HEAD(history_list, history);
253 struct download {
254 RB_ENTRY(download) entry;
255 int id;
256 WebKitDownload *download;
257 struct tab *tab;
259 RB_HEAD(download_list, download);
261 struct domain {
262 RB_ENTRY(domain) entry;
263 gchar *d;
264 int handy; /* app use */
266 RB_HEAD(domain_list, domain);
268 struct undo {
269 TAILQ_ENTRY(undo) entry;
270 gchar *uri;
271 GList *history;
272 int back; /* Keeps track of how many back
273 * history items there are. */
275 TAILQ_HEAD(undo_tailq, undo);
277 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
278 int next_download_id = 1;
280 struct karg {
281 int i;
282 char *s;
283 int p;
286 /* defines */
287 #define XT_NAME ("XXXTerm")
288 #define XT_DIR (".xxxterm")
289 #define XT_CACHE_DIR ("cache")
290 #define XT_CERT_DIR ("certs/")
291 #define XT_SESSIONS_DIR ("sessions/")
292 #define XT_CONF_FILE ("xxxterm.conf")
293 #define XT_FAVS_FILE ("favorites")
294 #define XT_SAVED_TABS_FILE ("main_session")
295 #define XT_RESTART_TABS_FILE ("restart_tabs")
296 #define XT_SOCKET_FILE ("socket")
297 #define XT_HISTORY_FILE ("history")
298 #define XT_REJECT_FILE ("rejected.txt")
299 #define XT_COOKIE_FILE ("cookies.txt")
300 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
301 #define XT_CB_HANDLED (TRUE)
302 #define XT_CB_PASSTHROUGH (FALSE)
303 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
304 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
305 #define XT_DLMAN_REFRESH "10"
306 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
307 "td{overflow: hidden;" \
308 " padding: 2px 2px 2px 2px;" \
309 " border: 1px solid black;" \
310 " vertical-align:top;" \
311 " word-wrap: break-word}\n" \
312 "tr:hover{background: #ffff99}\n" \
313 "th{background-color: #cccccc;" \
314 " border: 1px solid black}\n" \
315 "table{width: 100%%;" \
316 " border: 1px black solid;" \
317 " border-collapse:collapse}\n" \
318 ".progress-outer{" \
319 "border: 1px solid black;" \
320 " height: 8px;" \
321 " width: 90%%}\n" \
322 ".progress-inner{float: left;" \
323 " height: 8px;" \
324 " background: green}\n" \
325 ".dlstatus{font-size: small;" \
326 " text-align: center}\n" \
327 "</style>\n"
328 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
329 #define XT_MAX_UNDO_CLOSE_TAB (32)
330 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
331 #define XT_PRINT_EXTRA_MARGIN 10
333 /* colors */
334 #define XT_COLOR_RED "#cc0000"
335 #define XT_COLOR_YELLOW "#ffff66"
336 #define XT_COLOR_BLUE "lightblue"
337 #define XT_COLOR_GREEN "#99ff66"
338 #define XT_COLOR_WHITE "white"
339 #define XT_COLOR_BLACK "black"
341 #define XT_COLOR_CT_BACKGROUND "#000000"
342 #define XT_COLOR_CT_INACTIVE "#dddddd"
343 #define XT_COLOR_CT_ACTIVE "#bbbb00"
344 #define XT_COLOR_CT_SEPARATOR "#555555"
347 * xxxterm "protocol" (xtp)
348 * We use this for managing stuff like downloads and favorites. They
349 * make magical HTML pages in memory which have xxxt:// links in order
350 * to communicate with xxxterm's internals. These links take the format:
351 * xxxt://class/session_key/action/arg
353 * Don't begin xtp class/actions as 0. atoi returns that on error.
355 * Typically we have not put addition of items in this framework, as
356 * adding items is either done via an ex-command or via a keybinding instead.
359 #define XT_XTP_STR "xxxt://"
361 /* XTP classes (xxxt://<class>) */
362 #define XT_XTP_INVALID 0 /* invalid */
363 #define XT_XTP_DL 1 /* downloads */
364 #define XT_XTP_HL 2 /* history */
365 #define XT_XTP_CL 3 /* cookies */
366 #define XT_XTP_FL 4 /* favorites */
368 /* XTP download actions */
369 #define XT_XTP_DL_LIST 1
370 #define XT_XTP_DL_CANCEL 2
371 #define XT_XTP_DL_REMOVE 3
373 /* XTP history actions */
374 #define XT_XTP_HL_LIST 1
375 #define XT_XTP_HL_REMOVE 2
377 /* XTP cookie actions */
378 #define XT_XTP_CL_LIST 1
379 #define XT_XTP_CL_REMOVE 2
381 /* XTP cookie actions */
382 #define XT_XTP_FL_LIST 1
383 #define XT_XTP_FL_REMOVE 2
385 /* actions */
386 #define XT_MOVE_INVALID (0)
387 #define XT_MOVE_DOWN (1)
388 #define XT_MOVE_UP (2)
389 #define XT_MOVE_BOTTOM (3)
390 #define XT_MOVE_TOP (4)
391 #define XT_MOVE_PAGEDOWN (5)
392 #define XT_MOVE_PAGEUP (6)
393 #define XT_MOVE_HALFDOWN (7)
394 #define XT_MOVE_HALFUP (8)
395 #define XT_MOVE_LEFT (9)
396 #define XT_MOVE_FARLEFT (10)
397 #define XT_MOVE_RIGHT (11)
398 #define XT_MOVE_FARRIGHT (12)
400 #define XT_TAB_LAST (-4)
401 #define XT_TAB_FIRST (-3)
402 #define XT_TAB_PREV (-2)
403 #define XT_TAB_NEXT (-1)
404 #define XT_TAB_INVALID (0)
405 #define XT_TAB_NEW (1)
406 #define XT_TAB_DELETE (2)
407 #define XT_TAB_DELQUIT (3)
408 #define XT_TAB_OPEN (4)
409 #define XT_TAB_UNDO_CLOSE (5)
410 #define XT_TAB_SHOW (6)
411 #define XT_TAB_HIDE (7)
412 #define XT_TAB_NEXTSTYLE (8)
414 #define XT_NAV_INVALID (0)
415 #define XT_NAV_BACK (1)
416 #define XT_NAV_FORWARD (2)
417 #define XT_NAV_RELOAD (3)
418 #define XT_NAV_RELOAD_CACHE (4)
420 #define XT_FOCUS_INVALID (0)
421 #define XT_FOCUS_URI (1)
422 #define XT_FOCUS_SEARCH (2)
424 #define XT_SEARCH_INVALID (0)
425 #define XT_SEARCH_NEXT (1)
426 #define XT_SEARCH_PREV (2)
428 #define XT_PASTE_CURRENT_TAB (0)
429 #define XT_PASTE_NEW_TAB (1)
431 #define XT_FONT_SET (0)
433 #define XT_URL_SHOW (1)
434 #define XT_URL_HIDE (2)
436 #define XT_WL_TOGGLE (1<<0)
437 #define XT_WL_ENABLE (1<<1)
438 #define XT_WL_DISABLE (1<<2)
439 #define XT_WL_FQDN (1<<3) /* default */
440 #define XT_WL_TOPLEVEL (1<<4)
441 #define XT_WL_PERSISTENT (1<<5)
442 #define XT_WL_SESSION (1<<6)
443 #define XT_WL_RELOAD (1<<7)
445 #define XT_SHOW (1<<7)
446 #define XT_DELETE (1<<8)
447 #define XT_SAVE (1<<9)
448 #define XT_OPEN (1<<10)
450 #define XT_CMD_OPEN (0)
451 #define XT_CMD_OPEN_CURRENT (1)
452 #define XT_CMD_TABNEW (2)
453 #define XT_CMD_TABNEW_CURRENT (3)
455 #define XT_STATUS_NOTHING (0)
456 #define XT_STATUS_LINK (1)
457 #define XT_STATUS_URI (2)
458 #define XT_STATUS_LOADING (3)
460 #define XT_SES_DONOTHING (0)
461 #define XT_SES_CLOSETABS (1)
463 #define XT_BM_NORMAL (0)
464 #define XT_BM_WHITELIST (1)
465 #define XT_BM_KIOSK (2)
467 #define XT_PREFIX (1<<0)
468 #define XT_USERARG (1<<1)
469 #define XT_URLARG (1<<2)
470 #define XT_INTARG (1<<3)
472 #define XT_TABS_NORMAL 0
473 #define XT_TABS_COMPACT 1
475 /* mime types */
476 struct mime_type {
477 char *mt_type;
478 char *mt_action;
479 int mt_default;
480 int mt_download;
481 TAILQ_ENTRY(mime_type) entry;
483 TAILQ_HEAD(mime_type_list, mime_type);
485 /* uri aliases */
486 struct alias {
487 char *a_name;
488 char *a_uri;
489 TAILQ_ENTRY(alias) entry;
491 TAILQ_HEAD(alias_list, alias);
493 /* settings that require restart */
494 int tabless = 0; /* allow only 1 tab */
495 int enable_socket = 0;
496 int single_instance = 0; /* only allow one xxxterm to run */
497 int fancy_bar = 1; /* fancy toolbar */
498 int browser_mode = XT_BM_NORMAL;
499 int enable_localstorage = 0;
501 /* runtime settings */
502 int show_tabs = 1; /* show tabs on notebook */
503 int tab_style = XT_TABS_NORMAL; /* tab bar style */
504 int show_url = 1; /* show url toolbar on notebook */
505 int show_statusbar = 0; /* vimperator style status bar */
506 int ctrl_click_focus = 0; /* ctrl click gets focus */
507 int cookies_enabled = 1; /* enable cookies */
508 int read_only_cookies = 0; /* enable to not write cookies */
509 int enable_scripts = 1;
510 int enable_plugins = 0;
511 int default_font_size = 12;
512 gfloat default_zoom_level = 1.0;
513 int window_height = 768;
514 int window_width = 1024;
515 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
516 int refresh_interval = 10; /* download refresh interval */
517 int enable_cookie_whitelist = 0;
518 int enable_js_whitelist = 0;
519 int session_timeout = 3600; /* cookie session timeout */
520 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
521 char *ssl_ca_file = NULL;
522 char *resource_dir = NULL;
523 gboolean ssl_strict_certs = FALSE;
524 int append_next = 1; /* append tab after current tab */
525 char *home = NULL;
526 char *search_string = NULL;
527 char *http_proxy = NULL;
528 char download_dir[PATH_MAX];
529 char runtime_settings[PATH_MAX]; /* override of settings */
530 int allow_volatile_cookies = 0;
531 int save_global_history = 0; /* save global history to disk */
532 char *user_agent = NULL;
533 int save_rejected_cookies = 0;
534 int session_autosave = 0;
535 int guess_search = 0;
536 int dns_prefetch = FALSE;
537 gint max_connections = 25;
538 gint max_host_connections = 5;
539 gint enable_spell_checking = 0;
540 char *spell_check_languages = NULL;
542 char *cmd_font_name = NULL;
543 char *statusbar_font_name = NULL;
544 PangoFontDescription *cmd_font;
545 PangoFontDescription *statusbar_font;
547 int btn_down; /* M1 down in any wv */
549 struct settings;
550 struct key_binding;
551 int set_browser_mode(struct settings *, char *);
552 int set_cookie_policy(struct settings *, char *);
553 int set_download_dir(struct settings *, char *);
554 int set_runtime_dir(struct settings *, char *);
555 int set_tab_style(struct settings *, char *);
556 int set_work_dir(struct settings *, char *);
557 int add_alias(struct settings *, char *);
558 int add_mime_type(struct settings *, char *);
559 int add_cookie_wl(struct settings *, char *);
560 int add_js_wl(struct settings *, char *);
561 int add_kb(struct settings *, char *);
562 void button_set_stockid(GtkWidget *, char *);
563 GtkWidget * create_button(char *, char *, int);
565 char *get_browser_mode(struct settings *);
566 char *get_cookie_policy(struct settings *);
567 char *get_download_dir(struct settings *);
568 char *get_runtime_dir(struct settings *);
569 char *get_tab_style(struct settings *);
570 char *get_work_dir(struct settings *);
572 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
573 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
574 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
575 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
576 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
578 void recalc_tabs(void);
579 void recolor_compact_tabs(void);
580 void set_current_tab(int page_num);
581 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
583 struct special {
584 int (*set)(struct settings *, char *);
585 char *(*get)(struct settings *);
586 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
589 struct special s_browser_mode = {
590 set_browser_mode,
591 get_browser_mode,
592 NULL
595 struct special s_cookie = {
596 set_cookie_policy,
597 get_cookie_policy,
598 NULL
601 struct special s_alias = {
602 add_alias,
603 NULL,
604 walk_alias
607 struct special s_mime = {
608 add_mime_type,
609 NULL,
610 walk_mime_type
613 struct special s_js = {
614 add_js_wl,
615 NULL,
616 walk_js_wl
619 struct special s_kb = {
620 add_kb,
621 NULL,
622 walk_kb
625 struct special s_cookie_wl = {
626 add_cookie_wl,
627 NULL,
628 walk_cookie_wl
631 struct special s_download_dir = {
632 set_download_dir,
633 get_download_dir,
634 NULL
637 struct special s_work_dir = {
638 set_work_dir,
639 get_work_dir,
640 NULL
643 struct special s_tab_style = {
644 set_tab_style,
645 get_tab_style,
646 NULL
649 struct settings {
650 char *name;
651 int type;
652 #define XT_S_INVALID (0)
653 #define XT_S_INT (1)
654 #define XT_S_STR (2)
655 #define XT_S_FLOAT (3)
656 uint32_t flags;
657 #define XT_SF_RESTART (1<<0)
658 #define XT_SF_RUNTIME (1<<1)
659 int *ival;
660 char **sval;
661 struct special *s;
662 gfloat *fval;
663 } rs[] = {
664 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
665 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
666 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
667 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
668 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
669 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
670 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
671 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
672 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
673 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
674 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
675 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
676 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
677 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
678 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
679 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
680 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
681 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
682 { "home", XT_S_STR, 0, NULL, &home, NULL },
683 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
684 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
685 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
686 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
687 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
688 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
689 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
690 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
691 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
692 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
693 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
694 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
695 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
696 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
697 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
698 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
699 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
700 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
701 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
702 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
703 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
704 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
705 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
706 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
708 /* font settings */
709 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
710 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
712 /* runtime settings */
713 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
714 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
715 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
716 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
717 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
720 int about(struct tab *, struct karg *);
721 int blank(struct tab *, struct karg *);
722 int ca_cmd(struct tab *, struct karg *);
723 int cookie_show_wl(struct tab *, struct karg *);
724 int js_show_wl(struct tab *, struct karg *);
725 int help(struct tab *, struct karg *);
726 int set(struct tab *, struct karg *);
727 int stats(struct tab *, struct karg *);
728 int marco(struct tab *, struct karg *);
729 const char * marco_message(int *);
730 int xtp_page_cl(struct tab *, struct karg *);
731 int xtp_page_dl(struct tab *, struct karg *);
732 int xtp_page_fl(struct tab *, struct karg *);
733 int xtp_page_hl(struct tab *, struct karg *);
734 void xt_icon_from_file(struct tab *, char *);
735 const gchar *get_uri(struct tab *);
736 const gchar *get_title(struct tab *);
738 #define XT_URI_ABOUT ("about:")
739 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
740 #define XT_URI_ABOUT_ABOUT ("about")
741 #define XT_URI_ABOUT_BLANK ("blank")
742 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
743 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
744 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
745 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
746 #define XT_URI_ABOUT_FAVORITES ("favorites")
747 #define XT_URI_ABOUT_HELP ("help")
748 #define XT_URI_ABOUT_HISTORY ("history")
749 #define XT_URI_ABOUT_JSWL ("jswl")
750 #define XT_URI_ABOUT_SET ("set")
751 #define XT_URI_ABOUT_STATS ("stats")
752 #define XT_URI_ABOUT_MARCO ("marco")
754 struct about_type {
755 char *name;
756 int (*func)(struct tab *, struct karg *);
757 } about_list[] = {
758 { XT_URI_ABOUT_ABOUT, about },
759 { XT_URI_ABOUT_BLANK, blank },
760 { XT_URI_ABOUT_CERTS, ca_cmd },
761 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
762 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
763 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
764 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
765 { XT_URI_ABOUT_HELP, help },
766 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
767 { XT_URI_ABOUT_JSWL, js_show_wl },
768 { XT_URI_ABOUT_SET, set },
769 { XT_URI_ABOUT_STATS, stats },
770 { XT_URI_ABOUT_MARCO, marco },
773 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
774 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
775 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
776 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
777 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
778 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
779 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
781 /* globals */
782 extern char *__progname;
783 char **start_argv;
784 struct passwd *pwd;
785 GtkWidget *main_window;
786 GtkNotebook *notebook;
787 GtkWidget *tab_bar;
788 GtkWidget *arrow, *abtn;
789 struct tab_list tabs;
790 struct history_list hl;
791 struct download_list downloads;
792 struct domain_list c_wl;
793 struct domain_list js_wl;
794 struct undo_tailq undos;
795 struct keybinding_list kbl;
796 int undo_count;
797 int updating_dl_tabs = 0;
798 int updating_hl_tabs = 0;
799 int updating_cl_tabs = 0;
800 int updating_fl_tabs = 0;
801 char *global_search;
802 uint64_t blocked_cookies = 0;
803 char named_session[PATH_MAX];
804 int icon_size_map(int);
806 GtkListStore *completion_model;
807 void completion_add(struct tab *);
808 void completion_add_uri(const gchar *);
809 GtkListStore *buffers_store;
810 void xxx_dir(char *);
812 void
813 sigchild(int sig)
815 int saved_errno, status;
816 pid_t pid;
818 saved_errno = errno;
820 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
821 if (pid == -1) {
822 if (errno == EINTR)
823 continue;
824 if (errno != ECHILD) {
826 clog_warn("sigchild: waitpid:");
829 break;
832 if (WIFEXITED(status)) {
833 if (WEXITSTATUS(status) != 0) {
835 clog_warnx("sigchild: child exit status: %d",
836 WEXITSTATUS(status));
839 } else {
841 clog_warnx("sigchild: child is terminated abnormally");
846 errno = saved_errno;
850 is_g_object_setting(GObject *o, char *str)
852 guint n_props = 0, i;
853 GParamSpec **proplist;
855 if (! G_IS_OBJECT(o))
856 return (0);
858 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
859 &n_props);
861 for (i=0; i < n_props; i++) {
862 if (! strcmp(proplist[i]->name, str))
863 return (1);
865 return (0);
868 gchar *
869 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
871 gchar *r;
873 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
874 "<head>\n"
875 "<title>%s</title>\n"
876 "%s"
877 "%s"
878 "</head>\n"
879 "<body>\n"
880 "<h1>%s</h1>\n"
881 "%s\n</body>\n"
882 "</html>",
883 title,
884 addstyles ? XT_PAGE_STYLE : "",
885 head,
886 title,
887 body);
889 return r;
893 * Display a web page from a HTML string in memory, rather than from a URL
895 void
896 load_webkit_string(struct tab *t, const char *str, gchar *title)
898 char file[PATH_MAX];
899 int i;
901 /* we set this to indicate we want to manually do navaction */
902 if (t->bfl)
903 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
905 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
906 if (title) {
907 /* set t->xtp_meaning */
908 for (i = 0; i < LENGTH(about_list); i++)
909 if (!strcmp(title, about_list[i].name)) {
910 t->xtp_meaning = i;
911 break;
914 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
915 #if GTK_CHECK_VERSION(2, 20, 0)
916 gtk_spinner_stop(GTK_SPINNER(t->spinner));
917 gtk_widget_hide(t->spinner);
918 #endif
919 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
920 xt_icon_from_file(t, file);
924 struct tab *
925 get_current_tab(void)
927 struct tab *t;
929 TAILQ_FOREACH(t, &tabs, entry) {
930 if (t->tab_id == gtk_notebook_get_current_page(notebook))
931 return (t);
934 warnx("%s: no current tab", __func__);
936 return (NULL);
939 void
940 set_status(struct tab *t, gchar *s, int status)
942 gchar *type = NULL;
944 if (s == NULL)
945 return;
947 switch (status) {
948 case XT_STATUS_LOADING:
949 type = g_strdup_printf("Loading: %s", s);
950 s = type;
951 break;
952 case XT_STATUS_LINK:
953 type = g_strdup_printf("Link: %s", s);
954 if (!t->status)
955 t->status = g_strdup(gtk_entry_get_text(
956 GTK_ENTRY(t->sbe.statusbar)));
957 s = type;
958 break;
959 case XT_STATUS_URI:
960 type = g_strdup_printf("%s", s);
961 if (!t->status) {
962 t->status = g_strdup(type);
964 s = type;
965 if (!t->status)
966 t->status = g_strdup(s);
967 break;
968 case XT_STATUS_NOTHING:
969 /* FALL THROUGH */
970 default:
971 break;
973 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
974 if (type)
975 g_free(type);
978 void
979 hide_cmd(struct tab *t)
981 gtk_widget_hide(t->cmd);
984 void
985 show_cmd(struct tab *t)
987 gtk_widget_hide(t->oops);
988 gtk_widget_show(t->cmd);
991 void
992 hide_buffers(struct tab *t)
994 gtk_widget_hide(t->buffers);
995 gtk_list_store_clear(buffers_store);
998 enum {
999 COL_ID = 0,
1000 COL_TITLE,
1001 NUM_COLS
1005 sort_tabs_by_page_num(struct tab ***stabs)
1007 int num_tabs = 0;
1008 struct tab *t;
1010 num_tabs = gtk_notebook_get_n_pages(notebook);
1012 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1014 TAILQ_FOREACH(t, &tabs, entry)
1015 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1017 return (num_tabs);
1020 void
1021 buffers_make_list(void)
1023 int i, num_tabs;
1024 const gchar *title = NULL;
1025 GtkTreeIter iter;
1026 struct tab **stabs = NULL;
1028 num_tabs = sort_tabs_by_page_num(&stabs);
1030 for (i = 0; i < num_tabs; i++)
1031 if (stabs[i]) {
1032 gtk_list_store_append(buffers_store, &iter);
1033 title = get_title(stabs[i]);
1034 gtk_list_store_set(buffers_store, &iter,
1035 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1036 * rather than 0. */
1037 COL_TITLE, title,
1038 -1);
1041 g_free(stabs);
1044 void
1045 show_buffers(struct tab *t)
1047 buffers_make_list();
1048 gtk_widget_show(t->buffers);
1049 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1052 void
1053 toggle_buffers(struct tab *t)
1055 if (gtk_widget_get_visible(t->buffers))
1056 hide_buffers(t);
1057 else
1058 show_buffers(t);
1062 buffers(struct tab *t, struct karg *args)
1064 show_buffers(t);
1066 return (0);
1069 void
1070 hide_oops(struct tab *t)
1072 gtk_widget_hide(t->oops);
1075 void
1076 show_oops(struct tab *at, const char *fmt, ...)
1078 va_list ap;
1079 char *msg;
1080 struct tab *t = NULL;
1082 if (fmt == NULL)
1083 return;
1085 if (at == NULL) {
1086 if ((t = get_current_tab()) == NULL)
1087 return;
1088 } else
1089 t = at;
1091 va_start(ap, fmt);
1092 if (vasprintf(&msg, fmt, ap) == -1)
1093 errx(1, "show_oops failed");
1094 va_end(ap);
1096 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1097 gtk_widget_hide(t->cmd);
1098 gtk_widget_show(t->oops);
1101 char *
1102 get_as_string(struct settings *s)
1104 char *r = NULL;
1106 if (s == NULL)
1107 return (NULL);
1109 if (s->s) {
1110 if (s->s->get)
1111 r = s->s->get(s);
1112 else
1113 warnx("get_as_string skip %s\n", s->name);
1114 } else if (s->type == XT_S_INT)
1115 r = g_strdup_printf("%d", *s->ival);
1116 else if (s->type == XT_S_STR)
1117 r = g_strdup(*s->sval);
1118 else if (s->type == XT_S_FLOAT)
1119 r = g_strdup_printf("%f", *s->fval);
1120 else
1121 r = g_strdup_printf("INVALID TYPE");
1123 return (r);
1126 void
1127 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1129 int i;
1130 char *s;
1132 for (i = 0; i < LENGTH(rs); i++) {
1133 if (rs[i].s && rs[i].s->walk)
1134 rs[i].s->walk(&rs[i], cb, cb_args);
1135 else {
1136 s = get_as_string(&rs[i]);
1137 cb(&rs[i], s, cb_args);
1138 g_free(s);
1144 set_browser_mode(struct settings *s, char *val)
1146 if (!strcmp(val, "whitelist")) {
1147 browser_mode = XT_BM_WHITELIST;
1148 allow_volatile_cookies = 0;
1149 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1150 cookies_enabled = 1;
1151 enable_cookie_whitelist = 1;
1152 read_only_cookies = 0;
1153 save_rejected_cookies = 0;
1154 session_timeout = 3600;
1155 enable_scripts = 0;
1156 enable_js_whitelist = 1;
1157 enable_localstorage = 0;
1158 } else if (!strcmp(val, "normal")) {
1159 browser_mode = XT_BM_NORMAL;
1160 allow_volatile_cookies = 0;
1161 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1162 cookies_enabled = 1;
1163 enable_cookie_whitelist = 0;
1164 read_only_cookies = 0;
1165 save_rejected_cookies = 0;
1166 session_timeout = 3600;
1167 enable_scripts = 1;
1168 enable_js_whitelist = 0;
1169 enable_localstorage = 1;
1170 } else if (!strcmp(val, "kiosk")) {
1171 browser_mode = XT_BM_KIOSK;
1172 allow_volatile_cookies = 0;
1173 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1174 cookies_enabled = 1;
1175 enable_cookie_whitelist = 0;
1176 read_only_cookies = 0;
1177 save_rejected_cookies = 0;
1178 session_timeout = 3600;
1179 enable_scripts = 1;
1180 enable_js_whitelist = 0;
1181 enable_localstorage = 1;
1182 show_tabs = 0;
1183 tabless = 1;
1184 } else
1185 return (1);
1187 return (0);
1190 char *
1191 get_browser_mode(struct settings *s)
1193 char *r = NULL;
1195 if (browser_mode == XT_BM_WHITELIST)
1196 r = g_strdup("whitelist");
1197 else if (browser_mode == XT_BM_NORMAL)
1198 r = g_strdup("normal");
1199 else if (browser_mode == XT_BM_KIOSK)
1200 r = g_strdup("kiosk");
1201 else
1202 return (NULL);
1204 return (r);
1208 set_cookie_policy(struct settings *s, char *val)
1210 if (!strcmp(val, "no3rdparty"))
1211 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1212 else if (!strcmp(val, "accept"))
1213 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1214 else if (!strcmp(val, "reject"))
1215 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1216 else
1217 return (1);
1219 return (0);
1222 char *
1223 get_cookie_policy(struct settings *s)
1225 char *r = NULL;
1227 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1228 r = g_strdup("no3rdparty");
1229 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1230 r = g_strdup("accept");
1231 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1232 r = g_strdup("reject");
1233 else
1234 return (NULL);
1236 return (r);
1239 char *
1240 get_download_dir(struct settings *s)
1242 if (download_dir[0] == '\0')
1243 return (0);
1244 return (g_strdup(download_dir));
1248 set_download_dir(struct settings *s, char *val)
1250 if (val[0] == '~')
1251 snprintf(download_dir, sizeof download_dir, "%s/%s",
1252 pwd->pw_dir, &val[1]);
1253 else
1254 strlcpy(download_dir, val, sizeof download_dir);
1256 return (0);
1260 * Session IDs.
1261 * We use these to prevent people putting xxxt:// URLs on
1262 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1264 #define XT_XTP_SES_KEY_SZ 8
1265 #define XT_XTP_SES_KEY_HEX_FMT \
1266 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1267 char *dl_session_key; /* downloads */
1268 char *hl_session_key; /* history list */
1269 char *cl_session_key; /* cookie list */
1270 char *fl_session_key; /* favorites list */
1272 char work_dir[PATH_MAX];
1273 char certs_dir[PATH_MAX];
1274 char cache_dir[PATH_MAX];
1275 char sessions_dir[PATH_MAX];
1276 char cookie_file[PATH_MAX];
1277 SoupURI *proxy_uri = NULL;
1278 SoupSession *session;
1279 SoupCookieJar *s_cookiejar;
1280 SoupCookieJar *p_cookiejar;
1281 char rc_fname[PATH_MAX];
1283 struct mime_type_list mtl;
1284 struct alias_list aliases;
1286 /* protos */
1287 struct tab *create_new_tab(char *, struct undo *, int, int);
1288 void delete_tab(struct tab *);
1289 void adjustfont_webkit(struct tab *, int);
1290 int run_script(struct tab *, char *);
1291 int download_rb_cmp(struct download *, struct download *);
1292 gboolean cmd_execute(struct tab *t, char *str);
1295 history_rb_cmp(struct history *h1, struct history *h2)
1297 return (strcmp(h1->uri, h2->uri));
1299 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1302 domain_rb_cmp(struct domain *d1, struct domain *d2)
1304 return (strcmp(d1->d, d2->d));
1306 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1308 char *
1309 get_work_dir(struct settings *s)
1311 if (work_dir[0] == '\0')
1312 return (0);
1313 return (g_strdup(work_dir));
1317 set_work_dir(struct settings *s, char *val)
1319 if (val[0] == '~')
1320 snprintf(work_dir, sizeof work_dir, "%s/%s",
1321 pwd->pw_dir, &val[1]);
1322 else
1323 strlcpy(work_dir, val, sizeof work_dir);
1325 return (0);
1328 char *
1329 get_tab_style(struct settings *s)
1331 if (tab_style == XT_TABS_NORMAL)
1332 return (g_strdup("normal"));
1333 else
1334 return (g_strdup("compact"));
1338 set_tab_style(struct settings *s, char *val)
1340 if (!strcmp(val, "normal"))
1341 tab_style = XT_TABS_NORMAL;
1342 else if (!strcmp(val, "compact"))
1343 tab_style = XT_TABS_COMPACT;
1344 else
1345 return (1);
1347 return (0);
1351 * generate a session key to secure xtp commands.
1352 * pass in a ptr to the key in question and it will
1353 * be modified in place.
1355 void
1356 generate_xtp_session_key(char **key)
1358 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1360 /* free old key */
1361 if (*key)
1362 g_free(*key);
1364 /* make a new one */
1365 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1366 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1367 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1368 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1370 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1374 * validate a xtp session key.
1375 * return 1 if OK
1378 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1380 if (strcmp(trusted, untrusted) != 0) {
1381 show_oops(t, "%s: xtp session key mismatch possible spoof",
1382 __func__);
1383 return (0);
1386 return (1);
1390 download_rb_cmp(struct download *e1, struct download *e2)
1392 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1394 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1396 struct valid_url_types {
1397 char *type;
1398 } vut[] = {
1399 { "http://" },
1400 { "https://" },
1401 { "ftp://" },
1402 { "file://" },
1403 { XT_XTP_STR },
1407 valid_url_type(char *url)
1409 int i;
1411 for (i = 0; i < LENGTH(vut); i++)
1412 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1413 return (0);
1415 return (1);
1418 void
1419 print_cookie(char *msg, SoupCookie *c)
1421 if (c == NULL)
1422 return;
1424 if (msg)
1425 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1426 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1427 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1428 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1429 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1430 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1431 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1432 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1433 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1434 DNPRINTF(XT_D_COOKIE, "====================================\n");
1437 void
1438 walk_alias(struct settings *s,
1439 void (*cb)(struct settings *, char *, void *), void *cb_args)
1441 struct alias *a;
1442 char *str;
1444 if (s == NULL || cb == NULL) {
1445 show_oops(NULL, "walk_alias invalid parameters");
1446 return;
1449 TAILQ_FOREACH(a, &aliases, entry) {
1450 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1451 cb(s, str, cb_args);
1452 g_free(str);
1456 char *
1457 match_alias(char *url_in)
1459 struct alias *a;
1460 char *arg;
1461 char *url_out = NULL, *search, *enc_arg;
1463 search = g_strdup(url_in);
1464 arg = search;
1465 if (strsep(&arg, " \t") == NULL) {
1466 show_oops(NULL, "match_alias: NULL URL");
1467 goto done;
1470 TAILQ_FOREACH(a, &aliases, entry) {
1471 if (!strcmp(search, a->a_name))
1472 break;
1475 if (a != NULL) {
1476 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1477 a->a_name);
1478 if (arg != NULL) {
1479 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1480 url_out = g_strdup_printf(a->a_uri, enc_arg);
1481 g_free(enc_arg);
1482 } else
1483 url_out = g_strdup_printf(a->a_uri, "");
1485 done:
1486 g_free(search);
1487 return (url_out);
1490 char *
1491 guess_url_type(char *url_in)
1493 struct stat sb;
1494 char *url_out = NULL, *enc_search = NULL;
1496 url_out = match_alias(url_in);
1497 if (url_out != NULL)
1498 return (url_out);
1500 if (guess_search) {
1502 * If there is no dot nor slash in the string and it isn't a
1503 * path to a local file and doesn't resolves to an IP, assume
1504 * that the user wants to search for the string.
1507 if (strchr(url_in, '.') == NULL &&
1508 strchr(url_in, '/') == NULL &&
1509 stat(url_in, &sb) != 0 &&
1510 gethostbyname(url_in) == NULL) {
1512 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1513 url_out = g_strdup_printf(search_string, enc_search);
1514 g_free(enc_search);
1515 return (url_out);
1519 /* XXX not sure about this heuristic */
1520 if (stat(url_in, &sb) == 0)
1521 url_out = g_strdup_printf("file://%s", url_in);
1522 else
1523 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1525 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1527 return (url_out);
1530 void
1531 load_uri(struct tab *t, gchar *uri)
1533 struct karg args;
1534 gchar *newuri = NULL;
1535 int i;
1537 if (uri == NULL)
1538 return;
1540 /* Strip leading spaces. */
1541 while (*uri && isspace(*uri))
1542 uri++;
1544 if (strlen(uri) == 0) {
1545 blank(t, NULL);
1546 return;
1549 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1551 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1552 for (i = 0; i < LENGTH(about_list); i++)
1553 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1554 bzero(&args, sizeof args);
1555 about_list[i].func(t, &args);
1556 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1557 FALSE);
1558 return;
1560 show_oops(t, "invalid about page");
1561 return;
1564 if (valid_url_type(uri)) {
1565 newuri = guess_url_type(uri);
1566 uri = newuri;
1569 set_status(t, (char *)uri, XT_STATUS_LOADING);
1570 webkit_web_view_load_uri(t->wv, uri);
1572 if (newuri)
1573 g_free(newuri);
1576 const gchar *
1577 get_uri(struct tab *t)
1579 const gchar *uri = NULL;
1581 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
1582 uri = webkit_web_view_get_uri(t->wv);
1583 else
1584 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, about_list[t->xtp_meaning].name);
1586 return uri;
1589 const gchar *
1590 get_title(struct tab *t)
1592 const gchar *set = NULL, *title = NULL;
1594 title = webkit_web_view_get_title(t->wv);
1595 set = title ? title : get_uri(t);
1596 if (!set || t->xtp_meaning == XT_XTP_TAB_MEANING_BL) {
1597 set = "(untitled)";
1599 return set;
1603 add_alias(struct settings *s, char *line)
1605 char *l, *alias;
1606 struct alias *a = NULL;
1608 if (s == NULL || line == NULL) {
1609 show_oops(NULL, "add_alias invalid parameters");
1610 return (1);
1613 l = line;
1614 a = g_malloc(sizeof(*a));
1616 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1617 show_oops(NULL, "add_alias: incomplete alias definition");
1618 goto bad;
1620 if (strlen(alias) == 0 || strlen(l) == 0) {
1621 show_oops(NULL, "add_alias: invalid alias definition");
1622 goto bad;
1625 a->a_name = g_strdup(alias);
1626 a->a_uri = g_strdup(l);
1628 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1630 TAILQ_INSERT_TAIL(&aliases, a, entry);
1632 return (0);
1633 bad:
1634 if (a)
1635 g_free(a);
1636 return (1);
1640 add_mime_type(struct settings *s, char *line)
1642 char *mime_type;
1643 char *l;
1644 struct mime_type *m = NULL;
1645 int downloadfirst = 0;
1647 /* XXX this could be smarter */
1649 if (line == NULL || strlen(line) == 0) {
1650 show_oops(NULL, "add_mime_type invalid parameters");
1651 return (1);
1654 l = line;
1655 if (*l == '@') {
1656 downloadfirst = 1;
1657 l++;
1659 m = g_malloc(sizeof(*m));
1661 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1662 show_oops(NULL, "add_mime_type: invalid mime_type");
1663 goto bad;
1665 if (mime_type[strlen(mime_type) - 1] == '*') {
1666 mime_type[strlen(mime_type) - 1] = '\0';
1667 m->mt_default = 1;
1668 } else
1669 m->mt_default = 0;
1671 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1672 show_oops(NULL, "add_mime_type: invalid mime_type");
1673 goto bad;
1676 m->mt_type = g_strdup(mime_type);
1677 m->mt_action = g_strdup(l);
1678 m->mt_download = downloadfirst;
1680 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1681 m->mt_type, m->mt_action, m->mt_default);
1683 TAILQ_INSERT_TAIL(&mtl, m, entry);
1685 return (0);
1686 bad:
1687 if (m)
1688 g_free(m);
1689 return (1);
1692 struct mime_type *
1693 find_mime_type(char *mime_type)
1695 struct mime_type *m, *def = NULL, *rv = NULL;
1697 TAILQ_FOREACH(m, &mtl, entry) {
1698 if (m->mt_default &&
1699 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1700 def = m;
1702 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1703 rv = m;
1704 break;
1708 if (rv == NULL)
1709 rv = def;
1711 return (rv);
1714 void
1715 walk_mime_type(struct settings *s,
1716 void (*cb)(struct settings *, char *, void *), void *cb_args)
1718 struct mime_type *m;
1719 char *str;
1721 if (s == NULL || cb == NULL) {
1722 show_oops(NULL, "walk_mime_type invalid parameters");
1723 return;
1726 TAILQ_FOREACH(m, &mtl, entry) {
1727 str = g_strdup_printf("%s%s --> %s",
1728 m->mt_type,
1729 m->mt_default ? "*" : "",
1730 m->mt_action);
1731 cb(s, str, cb_args);
1732 g_free(str);
1736 void
1737 wl_add(char *str, struct domain_list *wl, int handy)
1739 struct domain *d;
1740 int add_dot = 0;
1742 if (str == NULL || wl == NULL || strlen(str) < 2)
1743 return;
1745 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1747 /* treat *.moo.com the same as .moo.com */
1748 if (str[0] == '*' && str[1] == '.')
1749 str = &str[1];
1750 else if (str[0] == '.')
1751 str = &str[0];
1752 else
1753 add_dot = 1;
1755 d = g_malloc(sizeof *d);
1756 if (add_dot)
1757 d->d = g_strdup_printf(".%s", str);
1758 else
1759 d->d = g_strdup(str);
1760 d->handy = handy;
1762 if (RB_INSERT(domain_list, wl, d))
1763 goto unwind;
1765 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1766 return;
1767 unwind:
1768 if (d) {
1769 if (d->d)
1770 g_free(d->d);
1771 g_free(d);
1776 add_cookie_wl(struct settings *s, char *entry)
1778 wl_add(entry, &c_wl, 1);
1779 return (0);
1782 void
1783 walk_cookie_wl(struct settings *s,
1784 void (*cb)(struct settings *, char *, void *), void *cb_args)
1786 struct domain *d;
1788 if (s == NULL || cb == NULL) {
1789 show_oops(NULL, "walk_cookie_wl invalid parameters");
1790 return;
1793 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1794 cb(s, d->d, cb_args);
1797 void
1798 walk_js_wl(struct settings *s,
1799 void (*cb)(struct settings *, char *, void *), void *cb_args)
1801 struct domain *d;
1803 if (s == NULL || cb == NULL) {
1804 show_oops(NULL, "walk_js_wl invalid parameters");
1805 return;
1808 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1809 cb(s, d->d, cb_args);
1813 add_js_wl(struct settings *s, char *entry)
1815 wl_add(entry, &js_wl, 1 /* persistent */);
1816 return (0);
1819 struct domain *
1820 wl_find(const gchar *search, struct domain_list *wl)
1822 int i;
1823 struct domain *d = NULL, dfind;
1824 gchar *s = NULL;
1826 if (search == NULL || wl == NULL)
1827 return (NULL);
1828 if (strlen(search) < 2)
1829 return (NULL);
1831 if (search[0] != '.')
1832 s = g_strdup_printf(".%s", search);
1833 else
1834 s = g_strdup(search);
1836 for (i = strlen(s) - 1; i >= 0; i--) {
1837 if (s[i] == '.') {
1838 dfind.d = &s[i];
1839 d = RB_FIND(domain_list, wl, &dfind);
1840 if (d)
1841 goto done;
1845 done:
1846 if (s)
1847 g_free(s);
1849 return (d);
1852 struct domain *
1853 wl_find_uri(const gchar *s, struct domain_list *wl)
1855 int i;
1856 char *ss;
1857 struct domain *r;
1859 if (s == NULL || wl == NULL)
1860 return (NULL);
1862 if (!strncmp(s, "http://", strlen("http://")))
1863 s = &s[strlen("http://")];
1864 else if (!strncmp(s, "https://", strlen("https://")))
1865 s = &s[strlen("https://")];
1867 if (strlen(s) < 2)
1868 return (NULL);
1870 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1871 /* chop string at first slash */
1872 if (s[i] == '/' || s[i] == '\0') {
1873 ss = g_strdup(s);
1874 ss[i] = '\0';
1875 r = wl_find(ss, wl);
1876 g_free(ss);
1877 return (r);
1880 return (NULL);
1883 char *
1884 get_toplevel_domain(char *domain)
1886 char *s;
1887 int found = 0;
1889 if (domain == NULL)
1890 return (NULL);
1891 if (strlen(domain) < 2)
1892 return (NULL);
1894 s = &domain[strlen(domain) - 1];
1895 while (s != domain) {
1896 if (*s == '.') {
1897 found++;
1898 if (found == 2)
1899 return (s);
1901 s--;
1904 if (found)
1905 return (domain);
1907 return (NULL);
1911 settings_add(char *var, char *val)
1913 int i, rv, *p;
1914 gfloat *f;
1915 char **s;
1917 /* get settings */
1918 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1919 if (strcmp(var, rs[i].name))
1920 continue;
1922 if (rs[i].s) {
1923 if (rs[i].s->set(&rs[i], val))
1924 errx(1, "invalid value for %s: %s", var, val);
1925 rv = 1;
1926 break;
1927 } else
1928 switch (rs[i].type) {
1929 case XT_S_INT:
1930 p = rs[i].ival;
1931 *p = atoi(val);
1932 rv = 1;
1933 break;
1934 case XT_S_STR:
1935 s = rs[i].sval;
1936 if (s == NULL)
1937 errx(1, "invalid sval for %s",
1938 rs[i].name);
1939 if (*s)
1940 g_free(*s);
1941 *s = g_strdup(val);
1942 rv = 1;
1943 break;
1944 case XT_S_FLOAT:
1945 f = rs[i].fval;
1946 *f = atof(val);
1947 rv = 1;
1948 break;
1949 case XT_S_INVALID:
1950 default:
1951 errx(1, "invalid type for %s", var);
1953 break;
1955 return (rv);
1958 #define WS "\n= \t"
1959 void
1960 config_parse(char *filename, int runtime)
1962 FILE *config, *f;
1963 char *line, *cp, *var, *val;
1964 size_t len, lineno = 0;
1965 int handled;
1966 char file[PATH_MAX];
1967 struct stat sb;
1969 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1971 if (filename == NULL)
1972 return;
1974 if (runtime && runtime_settings[0] != '\0') {
1975 snprintf(file, sizeof file, "%s/%s",
1976 work_dir, runtime_settings);
1977 if (stat(file, &sb)) {
1978 warnx("runtime file doesn't exist, creating it");
1979 if ((f = fopen(file, "w")) == NULL)
1980 err(1, "runtime");
1981 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1982 fclose(f);
1984 } else
1985 strlcpy(file, filename, sizeof file);
1987 if ((config = fopen(file, "r")) == NULL) {
1988 warn("config_parse: cannot open %s", filename);
1989 return;
1992 for (;;) {
1993 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1994 if (feof(config) || ferror(config))
1995 break;
1997 cp = line;
1998 cp += (long)strspn(cp, WS);
1999 if (cp[0] == '\0') {
2000 /* empty line */
2001 free(line);
2002 continue;
2005 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2006 errx(1, "invalid config file entry: %s", line);
2008 cp += (long)strspn(cp, WS);
2010 if ((val = strsep(&cp, "\0")) == NULL)
2011 break;
2013 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2014 handled = settings_add(var, val);
2015 if (handled == 0)
2016 errx(1, "invalid conf file entry: %s=%s", var, val);
2018 free(line);
2021 fclose(config);
2024 char *
2025 js_ref_to_string(JSContextRef context, JSValueRef ref)
2027 char *s = NULL;
2028 size_t l;
2029 JSStringRef jsref;
2031 jsref = JSValueToStringCopy(context, ref, NULL);
2032 if (jsref == NULL)
2033 return (NULL);
2035 l = JSStringGetMaximumUTF8CStringSize(jsref);
2036 s = g_malloc(l);
2037 if (s)
2038 JSStringGetUTF8CString(jsref, s, l);
2039 JSStringRelease(jsref);
2041 return (s);
2044 void
2045 disable_hints(struct tab *t)
2047 bzero(t->hint_buf, sizeof t->hint_buf);
2048 bzero(t->hint_num, sizeof t->hint_num);
2049 run_script(t, "vimprobable_clear()");
2050 t->hints_on = 0;
2051 t->hint_mode = XT_HINT_NONE;
2054 void
2055 enable_hints(struct tab *t)
2057 bzero(t->hint_buf, sizeof t->hint_buf);
2058 run_script(t, "vimprobable_show_hints()");
2059 t->hints_on = 1;
2060 t->hint_mode = XT_HINT_NONE;
2063 #define XT_JS_OPEN ("open;")
2064 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2065 #define XT_JS_FIRE ("fire;")
2066 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2067 #define XT_JS_FOUND ("found;")
2068 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2071 run_script(struct tab *t, char *s)
2073 JSGlobalContextRef ctx;
2074 WebKitWebFrame *frame;
2075 JSStringRef str;
2076 JSValueRef val, exception;
2077 char *es, buf[128];
2079 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2080 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2082 frame = webkit_web_view_get_main_frame(t->wv);
2083 ctx = webkit_web_frame_get_global_context(frame);
2085 str = JSStringCreateWithUTF8CString(s);
2086 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2087 NULL, 0, &exception);
2088 JSStringRelease(str);
2090 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2091 if (val == NULL) {
2092 es = js_ref_to_string(ctx, exception);
2093 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2094 g_free(es);
2095 return (1);
2096 } else {
2097 es = js_ref_to_string(ctx, val);
2098 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2100 /* handle return value right here */
2101 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2102 disable_hints(t);
2103 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
2106 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2107 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2108 &es[XT_JS_FIRE_LEN]);
2109 run_script(t, buf);
2110 disable_hints(t);
2113 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2114 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2115 disable_hints(t);
2118 g_free(es);
2121 return (0);
2125 hint(struct tab *t, struct karg *args)
2128 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2130 if (t->hints_on == 0)
2131 enable_hints(t);
2132 else
2133 disable_hints(t);
2135 return (0);
2138 void
2139 apply_style(struct tab *t)
2141 g_object_set(G_OBJECT(t->settings),
2142 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2146 userstyle(struct tab *t, struct karg *args)
2148 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2150 if (t->styled) {
2151 t->styled = 0;
2152 g_object_set(G_OBJECT(t->settings),
2153 "user-stylesheet-uri", NULL, (char *)NULL);
2154 } else {
2155 t->styled = 1;
2156 apply_style(t);
2158 return (0);
2162 * Doesn't work fully, due to the following bug:
2163 * https://bugs.webkit.org/show_bug.cgi?id=51747
2166 restore_global_history(void)
2168 char file[PATH_MAX];
2169 FILE *f;
2170 struct history *h;
2171 gchar *uri;
2172 gchar *title;
2173 const char delim[3] = {'\\', '\\', '\0'};
2175 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2177 if ((f = fopen(file, "r")) == NULL) {
2178 warnx("%s: fopen", __func__);
2179 return (1);
2182 for (;;) {
2183 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2184 if (feof(f) || ferror(f))
2185 break;
2187 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2188 if (feof(f) || ferror(f)) {
2189 free(uri);
2190 warnx("%s: broken history file\n", __func__);
2191 return (1);
2194 if (uri && strlen(uri) && title && strlen(title)) {
2195 webkit_web_history_item_new_with_data(uri, title);
2196 h = g_malloc(sizeof(struct history));
2197 h->uri = g_strdup(uri);
2198 h->title = g_strdup(title);
2199 RB_INSERT(history_list, &hl, h);
2200 completion_add_uri(h->uri);
2201 } else {
2202 warnx("%s: failed to restore history\n", __func__);
2203 free(uri);
2204 free(title);
2205 return (1);
2208 free(uri);
2209 free(title);
2210 uri = NULL;
2211 title = NULL;
2214 return (0);
2218 save_global_history_to_disk(struct tab *t)
2220 char file[PATH_MAX];
2221 FILE *f;
2222 struct history *h;
2224 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2226 if ((f = fopen(file, "w")) == NULL) {
2227 show_oops(t, "%s: global history file: %s",
2228 __func__, strerror(errno));
2229 return (1);
2232 RB_FOREACH_REVERSE(h, history_list, &hl) {
2233 if (h->uri && h->title)
2234 fprintf(f, "%s\n%s\n", h->uri, h->title);
2237 fclose(f);
2239 return (0);
2243 quit(struct tab *t, struct karg *args)
2245 if (save_global_history)
2246 save_global_history_to_disk(t);
2248 gtk_main_quit();
2250 return (1);
2254 open_tabs(struct tab *t, struct karg *a)
2256 char file[PATH_MAX];
2257 FILE *f = NULL;
2258 char *uri = NULL;
2259 int rv = 1;
2260 struct tab *ti, *tt;
2262 if (a == NULL)
2263 goto done;
2265 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2266 if ((f = fopen(file, "r")) == NULL)
2267 goto done;
2269 ti = TAILQ_LAST(&tabs, tab_list);
2271 for (;;) {
2272 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2273 if (feof(f) || ferror(f))
2274 break;
2276 /* retrieve session name */
2277 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2278 strlcpy(named_session,
2279 &uri[strlen(XT_SAVE_SESSION_ID)],
2280 sizeof named_session);
2281 continue;
2284 if (uri && strlen(uri))
2285 create_new_tab(uri, NULL, 1, -1);
2287 free(uri);
2288 uri = NULL;
2291 /* close open tabs */
2292 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2293 for (;;) {
2294 tt = TAILQ_FIRST(&tabs);
2295 if (tt != ti) {
2296 delete_tab(tt);
2297 continue;
2299 delete_tab(tt);
2300 break;
2302 recalc_tabs();
2305 rv = 0;
2306 done:
2307 if (f)
2308 fclose(f);
2310 return (rv);
2314 restore_saved_tabs(void)
2316 char file[PATH_MAX];
2317 int unlink_file = 0;
2318 struct stat sb;
2319 struct karg a;
2320 int rv = 0;
2322 snprintf(file, sizeof file, "%s/%s",
2323 sessions_dir, XT_RESTART_TABS_FILE);
2324 if (stat(file, &sb) == -1)
2325 a.s = XT_SAVED_TABS_FILE;
2326 else {
2327 unlink_file = 1;
2328 a.s = XT_RESTART_TABS_FILE;
2331 a.i = XT_SES_DONOTHING;
2332 rv = open_tabs(NULL, &a);
2334 if (unlink_file)
2335 unlink(file);
2337 return (rv);
2341 save_tabs(struct tab *t, struct karg *a)
2343 char file[PATH_MAX];
2344 FILE *f;
2345 int num_tabs = 0, i;
2346 struct tab **stabs = NULL;
2348 if (a == NULL)
2349 return (1);
2350 if (a->s == NULL)
2351 snprintf(file, sizeof file, "%s/%s",
2352 sessions_dir, named_session);
2353 else
2354 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2356 if ((f = fopen(file, "w")) == NULL) {
2357 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2358 return (1);
2361 /* save session name */
2362 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2364 /* Save tabs, in the order they are arranged in the notebook. */
2365 num_tabs = sort_tabs_by_page_num(&stabs);
2367 for (i = 0; i < num_tabs; i++)
2368 if (stabs[i] && get_uri(stabs[i]) != NULL)
2369 fprintf(f, "%s\n", get_uri(stabs[i]));
2371 g_free(stabs);
2373 /* try and make sure this gets to disk NOW. XXX Backup first? */
2374 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2375 show_oops(t, "May not have managed to save session: %s",
2376 strerror(errno));
2379 fclose(f);
2381 return (0);
2385 save_tabs_and_quit(struct tab *t, struct karg *args)
2387 struct karg a;
2389 a.s = NULL;
2390 save_tabs(t, &a);
2391 quit(t, NULL);
2393 return (1);
2397 yank_uri(struct tab *t, struct karg *args)
2399 const gchar *uri;
2400 GtkClipboard *clipboard;
2402 if ((uri = get_uri(t)) == NULL)
2403 return (1);
2405 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2406 gtk_clipboard_set_text(clipboard, uri, -1);
2408 return (0);
2412 paste_uri(struct tab *t, struct karg *args)
2414 GtkClipboard *clipboard;
2415 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2416 gint len;
2417 gchar *p = NULL, *uri;
2419 /* try primary clipboard first */
2420 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2421 p = gtk_clipboard_wait_for_text(clipboard);
2423 /* if it failed get whatever text is in cut_buffer0 */
2424 if (p == NULL)
2425 if (gdk_property_get(gdk_get_default_root_window(),
2426 atom,
2427 gdk_atom_intern("STRING", FALSE),
2429 65536 /* picked out of my butt */,
2430 FALSE,
2431 NULL,
2432 NULL,
2433 &len,
2434 (guchar **)&p)) {
2435 /* yes sir, we need to NUL the string */
2436 p[len] = '\0';
2439 if (p) {
2440 uri = p;
2441 while (*uri && isspace(*uri))
2442 uri++;
2443 if (strlen(uri) == 0) {
2444 show_oops(t, "empty paste buffer");
2445 goto done;
2447 if (guess_search == 0 && valid_url_type(uri)) {
2448 /* we can be clever and paste this in search box */
2449 show_oops(t, "not a valid URL");
2450 goto done;
2453 if (args->i == XT_PASTE_CURRENT_TAB)
2454 load_uri(t, uri);
2455 else if (args->i == XT_PASTE_NEW_TAB)
2456 create_new_tab(uri, NULL, 1, -1);
2459 done:
2460 if (p)
2461 g_free(p);
2463 return (0);
2466 char *
2467 find_domain(const gchar *s, int add_dot)
2469 int i;
2470 char *r = NULL, *ss = NULL;
2472 if (s == NULL)
2473 return (NULL);
2475 if (!strncmp(s, "http://", strlen("http://")))
2476 s = &s[strlen("http://")];
2477 else if (!strncmp(s, "https://", strlen("https://")))
2478 s = &s[strlen("https://")];
2480 if (strlen(s) < 2)
2481 return (NULL);
2483 ss = g_strdup(s);
2484 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2485 /* chop string at first slash */
2486 if (ss[i] == '/' || ss[i] == '\0') {
2487 ss[i] = '\0';
2488 if (add_dot)
2489 r = g_strdup_printf(".%s", ss);
2490 else
2491 r = g_strdup(ss);
2492 break;
2494 g_free(ss);
2496 return (r);
2500 toggle_cwl(struct tab *t, struct karg *args)
2502 struct domain *d;
2503 const gchar *uri;
2504 char *dom = NULL, *dom_toggle = NULL;
2505 int es;
2507 if (args == NULL)
2508 return (1);
2510 uri = get_uri(t);
2511 dom = find_domain(uri, 1);
2512 d = wl_find(dom, &c_wl);
2514 if (d == NULL)
2515 es = 0;
2516 else
2517 es = 1;
2519 if (args->i & XT_WL_TOGGLE)
2520 es = !es;
2521 else if ((args->i & XT_WL_ENABLE) && es != 1)
2522 es = 1;
2523 else if ((args->i & XT_WL_DISABLE) && es != 0)
2524 es = 0;
2526 if (args->i & XT_WL_TOPLEVEL)
2527 dom_toggle = get_toplevel_domain(dom);
2528 else
2529 dom_toggle = dom;
2531 if (es)
2532 /* enable cookies for domain */
2533 wl_add(dom_toggle, &c_wl, 0);
2534 else
2535 /* disable cookies for domain */
2536 RB_REMOVE(domain_list, &c_wl, d);
2538 if (args->i & XT_WL_RELOAD)
2539 webkit_web_view_reload(t->wv);
2541 g_free(dom);
2542 return (0);
2546 toggle_js(struct tab *t, struct karg *args)
2548 int es;
2549 const gchar *uri;
2550 struct domain *d;
2551 char *dom = NULL, *dom_toggle = NULL;
2553 if (args == NULL)
2554 return (1);
2556 g_object_get(G_OBJECT(t->settings),
2557 "enable-scripts", &es, (char *)NULL);
2558 if (args->i & XT_WL_TOGGLE)
2559 es = !es;
2560 else if ((args->i & XT_WL_ENABLE) && es != 1)
2561 es = 1;
2562 else if ((args->i & XT_WL_DISABLE) && es != 0)
2563 es = 0;
2564 else
2565 return (1);
2567 uri = get_uri(t);
2568 dom = find_domain(uri, 1);
2570 if (uri == NULL || dom == NULL) {
2571 show_oops(t, "Can't toggle domain in JavaScript white list");
2572 goto done;
2575 if (args->i & XT_WL_TOPLEVEL)
2576 dom_toggle = get_toplevel_domain(dom);
2577 else
2578 dom_toggle = dom;
2580 if (es) {
2581 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2582 wl_add(dom_toggle, &js_wl, 0 /* session */);
2583 } else {
2584 d = wl_find(dom_toggle, &js_wl);
2585 if (d)
2586 RB_REMOVE(domain_list, &js_wl, d);
2587 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2589 g_object_set(G_OBJECT(t->settings),
2590 "enable-scripts", es, (char *)NULL);
2591 g_object_set(G_OBJECT(t->settings),
2592 "javascript-can-open-windows-automatically", es, (char *)NULL);
2593 webkit_web_view_set_settings(t->wv, t->settings);
2595 if (args->i & XT_WL_RELOAD)
2596 webkit_web_view_reload(t->wv);
2597 done:
2598 if (dom)
2599 g_free(dom);
2600 return (0);
2603 void
2604 js_toggle_cb(GtkWidget *w, struct tab *t)
2606 struct karg a;
2608 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2609 toggle_cwl(t, &a);
2611 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2612 toggle_js(t, &a);
2616 toggle_src(struct tab *t, struct karg *args)
2618 gboolean mode;
2620 if (t == NULL)
2621 return (0);
2623 mode = webkit_web_view_get_view_source_mode(t->wv);
2624 webkit_web_view_set_view_source_mode(t->wv, !mode);
2625 webkit_web_view_reload(t->wv);
2627 return (0);
2630 void
2631 focus_webview(struct tab *t)
2633 if (t == NULL)
2634 return;
2636 /* only grab focus if we are visible */
2637 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2638 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2642 focus(struct tab *t, struct karg *args)
2644 if (t == NULL || args == NULL)
2645 return (1);
2647 if (show_url == 0)
2648 return (0);
2650 if (args->i == XT_FOCUS_URI)
2651 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2652 else if (args->i == XT_FOCUS_SEARCH)
2653 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2655 return (0);
2659 stats(struct tab *t, struct karg *args)
2661 char *page, *body, *s, line[64 * 1024];
2662 uint64_t line_count = 0;
2663 FILE *r_cookie_f;
2665 if (t == NULL)
2666 show_oops(NULL, "stats invalid parameters");
2668 line[0] = '\0';
2669 if (save_rejected_cookies) {
2670 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2671 for (;;) {
2672 s = fgets(line, sizeof line, r_cookie_f);
2673 if (s == NULL || feof(r_cookie_f) ||
2674 ferror(r_cookie_f))
2675 break;
2676 line_count++;
2678 fclose(r_cookie_f);
2679 snprintf(line, sizeof line,
2680 "<br/>Cookies blocked(*) total: %llu", line_count);
2681 } else
2682 show_oops(t, "Can't open blocked cookies file: %s",
2683 strerror(errno));
2686 body = g_strdup_printf(
2687 "Cookies blocked(*) this session: %llu"
2688 "%s"
2689 "<p><small><b>*</b> results vary based on settings</small></p>",
2690 blocked_cookies,
2691 line);
2693 page = get_html_page("Statistics", body, "", 0);
2694 g_free(body);
2696 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2697 g_free(page);
2699 return (0);
2703 marco(struct tab *t, struct karg *args)
2705 char *page, line[64 * 1024];
2706 int len;
2708 if (t == NULL)
2709 show_oops(NULL, "marco invalid parameters");
2711 line[0] = '\0';
2712 snprintf(line, sizeof line, "%s", marco_message(&len));
2714 page = get_html_page("Marco Sez...", line, "", 0);
2716 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2717 g_free(page);
2719 return (0);
2723 blank(struct tab *t, struct karg *args)
2725 if (t == NULL)
2726 show_oops(NULL, "blank invalid parameters");
2728 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2730 return (0);
2733 about(struct tab *t, struct karg *args)
2735 char *page, *body;
2737 if (t == NULL)
2738 show_oops(NULL, "about invalid parameters");
2740 body = g_strdup_printf("<b>Version: %s</b><p>"
2741 "Authors:"
2742 "<ul>"
2743 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2744 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2745 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2746 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2747 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2748 "</ul>"
2749 "Copyrights and licenses can be found on the XXXterm "
2750 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2751 version
2754 page = get_html_page("About", body, "", 0);
2755 g_free(body);
2757 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2758 g_free(page);
2760 return (0);
2764 help(struct tab *t, struct karg *args)
2766 char *page, *head, *body;
2768 if (t == NULL)
2769 show_oops(NULL, "help invalid parameters");
2771 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2772 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2773 "</head>\n";
2774 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2775 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2776 "cgi-bin/man-cgi?xxxterm</a>";
2778 page = get_html_page("XXXterm", body, head, FALSE);
2780 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2781 g_free(page);
2783 return (0);
2787 * update all favorite tabs apart from one. Pass NULL if
2788 * you want to update all.
2790 void
2791 update_favorite_tabs(struct tab *apart_from)
2793 struct tab *t;
2794 if (!updating_fl_tabs) {
2795 updating_fl_tabs = 1; /* stop infinite recursion */
2796 TAILQ_FOREACH(t, &tabs, entry)
2797 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2798 && (t != apart_from))
2799 xtp_page_fl(t, NULL);
2800 updating_fl_tabs = 0;
2804 /* show a list of favorites (bookmarks) */
2806 xtp_page_fl(struct tab *t, struct karg *args)
2808 char file[PATH_MAX];
2809 FILE *f;
2810 char *uri = NULL, *title = NULL;
2811 size_t len, lineno = 0;
2812 int i, failed = 0;
2813 char *body, *tmp, *page = NULL;
2814 const char delim[3] = {'\\', '\\', '\0'};
2816 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2818 if (t == NULL)
2819 warn("%s: bad param", __func__);
2821 /* new session key */
2822 if (!updating_fl_tabs)
2823 generate_xtp_session_key(&fl_session_key);
2825 /* open favorites */
2826 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2827 if ((f = fopen(file, "r")) == NULL) {
2828 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2829 return (1);
2832 /* body */
2833 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2834 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2835 "<th style='width: 40px'>Rm</th></tr>\n");
2837 for (i = 1;;) {
2838 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2839 if (feof(f) || ferror(f))
2840 break;
2841 if (len == 0) {
2842 free(title);
2843 title = NULL;
2844 continue;
2847 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2848 if (feof(f) || ferror(f)) {
2849 show_oops(t, "favorites file corrupt");
2850 failed = 1;
2851 break;
2854 tmp = body;
2855 body = g_strdup_printf("%s<tr>"
2856 "<td>%d</td>"
2857 "<td><a href='%s'>%s</a></td>"
2858 "<td style='text-align: center'>"
2859 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2860 "</tr>\n",
2861 body, i, uri, title,
2862 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2864 g_free(tmp);
2866 free(uri);
2867 uri = NULL;
2868 free(title);
2869 title = NULL;
2870 i++;
2872 fclose(f);
2874 /* if none, say so */
2875 if (i == 1) {
2876 tmp = body;
2877 body = g_strdup_printf("%s<tr>"
2878 "<td colspan='3' style='text-align: center'>"
2879 "No favorites - To add one use the 'favadd' command."
2880 "</td></tr>", body);
2881 g_free(tmp);
2884 tmp = body;
2885 body = g_strdup_printf("%s</table>", body);
2886 g_free(tmp);
2888 if (uri)
2889 free(uri);
2890 if (title)
2891 free(title);
2893 /* render */
2894 if (!failed) {
2895 page = get_html_page("Favorites", body, "", 1);
2896 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2897 g_free(page);
2900 update_favorite_tabs(t);
2902 if (body)
2903 g_free(body);
2905 return (failed);
2908 void
2909 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2910 size_t cert_count, char *title)
2912 gnutls_datum_t cinfo;
2913 char *tmp, *body;
2914 int i;
2916 body = g_strdup("");
2918 for (i = 0; i < cert_count; i++) {
2919 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2920 &cinfo))
2921 return;
2923 tmp = body;
2924 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2925 body, i, cinfo.data);
2926 gnutls_free(cinfo.data);
2927 g_free(tmp);
2930 tmp = get_html_page(title, body, "", 0);
2931 g_free(body);
2933 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2934 g_free(tmp);
2938 ca_cmd(struct tab *t, struct karg *args)
2940 FILE *f = NULL;
2941 int rv = 1, certs = 0, certs_read;
2942 struct stat sb;
2943 gnutls_datum_t dt;
2944 gnutls_x509_crt_t *c = NULL;
2945 char *certs_buf = NULL, *s;
2947 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2948 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2949 return (1);
2952 if (fstat(fileno(f), &sb) == -1) {
2953 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2954 goto done;
2957 certs_buf = g_malloc(sb.st_size + 1);
2958 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2959 show_oops(t, "Can't read CA file: %s", strerror(errno));
2960 goto done;
2962 certs_buf[sb.st_size] = '\0';
2964 s = certs_buf;
2965 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2966 certs++;
2967 s += strlen("BEGIN CERTIFICATE");
2970 bzero(&dt, sizeof dt);
2971 dt.data = (unsigned char *)certs_buf;
2972 dt.size = sb.st_size;
2973 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2974 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
2975 GNUTLS_X509_FMT_PEM, 0);
2976 if (certs_read <= 0) {
2977 show_oops(t, "No cert(s) available");
2978 goto done;
2980 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2981 done:
2982 if (c)
2983 g_free(c);
2984 if (certs_buf)
2985 g_free(certs_buf);
2986 if (f)
2987 fclose(f);
2989 return (rv);
2993 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2995 SoupURI *su = NULL;
2996 struct addrinfo hints, *res = NULL, *ai;
2997 int s = -1, on;
2998 char port[8];
3000 if (uri && !g_str_has_prefix(uri, "https://"))
3001 goto done;
3003 su = soup_uri_new(uri);
3004 if (su == NULL)
3005 goto done;
3006 if (!SOUP_URI_VALID_FOR_HTTP(su))
3007 goto done;
3009 snprintf(port, sizeof port, "%d", su->port);
3010 bzero(&hints, sizeof(struct addrinfo));
3011 hints.ai_flags = AI_CANONNAME;
3012 hints.ai_family = AF_UNSPEC;
3013 hints.ai_socktype = SOCK_STREAM;
3015 if (getaddrinfo(su->host, port, &hints, &res))
3016 goto done;
3018 for (ai = res; ai; ai = ai->ai_next) {
3019 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3020 continue;
3022 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3023 if (s < 0)
3024 goto done;
3025 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3026 sizeof(on)) == -1)
3027 goto done;
3029 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3030 goto done;
3033 if (domain)
3034 strlcpy(domain, su->host, domain_sz);
3035 done:
3036 if (su)
3037 soup_uri_free(su);
3038 if (res)
3039 freeaddrinfo(res);
3041 return (s);
3045 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3047 if (gsession)
3048 gnutls_deinit(gsession);
3049 if (xcred)
3050 gnutls_certificate_free_credentials(xcred);
3052 return (0);
3056 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3057 gnutls_certificate_credentials_t *xc)
3059 gnutls_certificate_credentials_t xcred;
3060 gnutls_session_t gsession;
3061 int rv = 1;
3063 if (gs == NULL || xc == NULL)
3064 goto done;
3066 *gs = NULL;
3067 *xc = NULL;
3069 gnutls_certificate_allocate_credentials(&xcred);
3070 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3071 GNUTLS_X509_FMT_PEM);
3072 gnutls_init(&gsession, GNUTLS_CLIENT);
3073 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3074 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3075 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3076 if ((rv = gnutls_handshake(gsession)) < 0) {
3077 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3079 gnutls_error_is_fatal(rv),
3080 gnutls_strerror_name(rv));
3081 stop_tls(gsession, xcred);
3082 goto done;
3085 gnutls_credentials_type_t cred;
3086 cred = gnutls_auth_get_type(gsession);
3087 if (cred != GNUTLS_CRD_CERTIFICATE) {
3088 stop_tls(gsession, xcred);
3089 goto done;
3092 *gs = gsession;
3093 *xc = xcred;
3094 rv = 0;
3095 done:
3096 return (rv);
3100 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3101 size_t *cert_count)
3103 unsigned int len;
3104 const gnutls_datum_t *cl;
3105 gnutls_x509_crt_t *all_certs;
3106 int i, rv = 1;
3108 if (certs == NULL || cert_count == NULL)
3109 goto done;
3110 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3111 goto done;
3112 cl = gnutls_certificate_get_peers(gsession, &len);
3113 if (len == 0)
3114 goto done;
3116 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3117 for (i = 0; i < len; i++) {
3118 gnutls_x509_crt_init(&all_certs[i]);
3119 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3120 GNUTLS_X509_FMT_PEM < 0)) {
3121 g_free(all_certs);
3122 goto done;
3126 *certs = all_certs;
3127 *cert_count = len;
3128 rv = 0;
3129 done:
3130 return (rv);
3133 void
3134 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3136 int i;
3138 for (i = 0; i < cert_count; i++)
3139 gnutls_x509_crt_deinit(certs[i]);
3140 g_free(certs);
3143 void
3144 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3145 size_t cert_count, char *domain)
3147 size_t cert_buf_sz;
3148 char cert_buf[64 * 1024], file[PATH_MAX];
3149 int i;
3150 FILE *f;
3151 GdkColor color;
3153 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3154 return;
3156 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3157 if ((f = fopen(file, "w")) == NULL) {
3158 show_oops(t, "Can't create cert file %s %s",
3159 file, strerror(errno));
3160 return;
3163 for (i = 0; i < cert_count; i++) {
3164 cert_buf_sz = sizeof cert_buf;
3165 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3166 cert_buf, &cert_buf_sz)) {
3167 show_oops(t, "gnutls_x509_crt_export failed");
3168 goto done;
3170 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3171 show_oops(t, "Can't write certs: %s", strerror(errno));
3172 goto done;
3176 /* not the best spot but oh well */
3177 gdk_color_parse("lightblue", &color);
3178 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3179 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
3180 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &color);
3181 gdk_color_parse(XT_COLOR_BLACK, &color);
3182 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
3183 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &color);
3184 done:
3185 fclose(f);
3189 load_compare_cert(struct tab *t, struct karg *args)
3191 const gchar *uri;
3192 char domain[8182], file[PATH_MAX];
3193 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3194 int s = -1, rv = 1, i;
3195 size_t cert_count;
3196 FILE *f = NULL;
3197 size_t cert_buf_sz;
3198 gnutls_session_t gsession;
3199 gnutls_x509_crt_t *certs;
3200 gnutls_certificate_credentials_t xcred;
3202 if (t == NULL)
3203 return (1);
3205 if ((uri = get_uri(t)) == NULL)
3206 return (1);
3208 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3209 return (1);
3211 /* go ssl/tls */
3212 if (start_tls(t, s, &gsession, &xcred)) {
3213 show_oops(t, "Start TLS failed");
3214 goto done;
3217 /* get certs */
3218 if (get_connection_certs(gsession, &certs, &cert_count)) {
3219 show_oops(t, "Can't get connection certificates");
3220 goto done;
3223 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3224 if ((f = fopen(file, "r")) == NULL)
3225 goto freeit;
3227 for (i = 0; i < cert_count; i++) {
3228 cert_buf_sz = sizeof cert_buf;
3229 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3230 cert_buf, &cert_buf_sz)) {
3231 goto freeit;
3233 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3234 rv = -1; /* critical */
3235 goto freeit;
3237 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3238 rv = -1; /* critical */
3239 goto freeit;
3243 rv = 0;
3244 freeit:
3245 if (f)
3246 fclose(f);
3247 free_connection_certs(certs, cert_count);
3248 done:
3249 /* we close the socket first for speed */
3250 if (s != -1)
3251 close(s);
3252 stop_tls(gsession, xcred);
3254 return (rv);
3258 cert_cmd(struct tab *t, struct karg *args)
3260 const gchar *uri;
3261 char domain[8182];
3262 int s = -1;
3263 size_t cert_count;
3264 gnutls_session_t gsession;
3265 gnutls_x509_crt_t *certs;
3266 gnutls_certificate_credentials_t xcred;
3268 if (t == NULL)
3269 return (1);
3271 if (ssl_ca_file == NULL) {
3272 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3273 return (1);
3276 if ((uri = get_uri(t)) == NULL) {
3277 show_oops(t, "Invalid URI");
3278 return (1);
3281 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3282 show_oops(t, "Invalid certificate URI: %s", uri);
3283 return (1);
3286 /* go ssl/tls */
3287 if (start_tls(t, s, &gsession, &xcred)) {
3288 show_oops(t, "Start TLS failed");
3289 goto done;
3292 /* get certs */
3293 if (get_connection_certs(gsession, &certs, &cert_count)) {
3294 show_oops(t, "get_connection_certs failed");
3295 goto done;
3298 if (args->i & XT_SHOW)
3299 show_certs(t, certs, cert_count, "Certificate Chain");
3300 else if (args->i & XT_SAVE)
3301 save_certs(t, certs, cert_count, domain);
3303 free_connection_certs(certs, cert_count);
3304 done:
3305 /* we close the socket first for speed */
3306 if (s != -1)
3307 close(s);
3308 stop_tls(gsession, xcred);
3310 return (0);
3314 remove_cookie(int index)
3316 int i, rv = 1;
3317 GSList *cf;
3318 SoupCookie *c;
3320 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3322 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3324 for (i = 1; cf; cf = cf->next, i++) {
3325 if (i != index)
3326 continue;
3327 c = cf->data;
3328 print_cookie("remove cookie", c);
3329 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3330 rv = 0;
3331 break;
3334 soup_cookies_free(cf);
3336 return (rv);
3340 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3342 struct domain *d;
3343 char *tmp, *body;
3345 body = g_strdup("");
3347 /* p list */
3348 if (args->i & XT_WL_PERSISTENT) {
3349 tmp = body;
3350 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3351 g_free(tmp);
3352 RB_FOREACH(d, domain_list, wl) {
3353 if (d->handy == 0)
3354 continue;
3355 tmp = body;
3356 body = g_strdup_printf("%s%s<br/>", body, d->d);
3357 g_free(tmp);
3361 /* s list */
3362 if (args->i & XT_WL_SESSION) {
3363 tmp = body;
3364 body = g_strdup_printf("%s<h2>Session</h2>", body);
3365 g_free(tmp);
3366 RB_FOREACH(d, domain_list, wl) {
3367 if (d->handy == 1)
3368 continue;
3369 tmp = body;
3370 body = g_strdup_printf("%s%s<br/>", body, d->d);
3371 g_free(tmp);
3375 tmp = get_html_page(title, body, "", 0);
3376 g_free(body);
3377 if (wl == &js_wl)
3378 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3379 else
3380 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3381 g_free(tmp);
3382 return (0);
3386 wl_save(struct tab *t, struct karg *args, int js)
3388 char file[PATH_MAX];
3389 FILE *f;
3390 char *line = NULL, *lt = NULL;
3391 size_t linelen;
3392 const gchar *uri;
3393 char *dom = NULL, *dom_save = NULL;
3394 struct karg a;
3395 struct domain *d;
3396 GSList *cf;
3397 SoupCookie *ci, *c;
3399 if (t == NULL || args == NULL)
3400 return (1);
3402 if (runtime_settings[0] == '\0')
3403 return (1);
3405 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3406 if ((f = fopen(file, "r+")) == NULL)
3407 return (1);
3409 uri = get_uri(t);
3410 dom = find_domain(uri, 1);
3411 if (uri == NULL || dom == NULL) {
3412 show_oops(t, "Can't add domain to %s white list",
3413 js ? "JavaScript" : "cookie");
3414 goto done;
3417 if (args->i & XT_WL_TOPLEVEL) {
3418 /* save domain */
3419 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3420 show_oops(t, "invalid domain: %s", dom);
3421 goto done;
3423 } else if (args->i & XT_WL_FQDN) {
3424 /* save fqdn */
3425 dom_save = dom;
3426 } else
3427 goto done;
3429 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3431 while (!feof(f)) {
3432 line = fparseln(f, &linelen, NULL, NULL, 0);
3433 if (line == NULL)
3434 continue;
3435 if (!strcmp(line, lt))
3436 goto done;
3437 free(line);
3438 line = NULL;
3441 fprintf(f, "%s\n", lt);
3443 a.i = XT_WL_ENABLE;
3444 a.i |= args->i;
3445 if (js) {
3446 d = wl_find(dom_save, &js_wl);
3447 if (!d) {
3448 settings_add("js_wl", dom_save);
3449 d = wl_find(dom_save, &js_wl);
3451 toggle_js(t, &a);
3452 } else {
3453 d = wl_find(dom_save, &c_wl);
3454 if (!d) {
3455 settings_add("cookie_wl", dom_save);
3456 d = wl_find(dom_save, &c_wl);
3458 toggle_cwl(t, &a);
3460 /* find and add to persistent jar */
3461 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3462 for (;cf; cf = cf->next) {
3463 ci = cf->data;
3464 if (!strcmp(dom_save, ci->domain) ||
3465 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3466 c = soup_cookie_copy(ci);
3467 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3470 soup_cookies_free(cf);
3472 if (d)
3473 d->handy = 1;
3475 done:
3476 if (line)
3477 free(line);
3478 if (dom)
3479 g_free(dom);
3480 if (lt)
3481 g_free(lt);
3482 fclose(f);
3484 return (0);
3488 js_show_wl(struct tab *t, struct karg *args)
3490 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3491 wl_show(t, args, "JavaScript White List", &js_wl);
3493 return (0);
3497 cookie_show_wl(struct tab *t, struct karg *args)
3499 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3500 wl_show(t, args, "Cookie White List", &c_wl);
3502 return (0);
3506 cookie_cmd(struct tab *t, struct karg *args)
3508 if (args->i & XT_SHOW)
3509 wl_show(t, args, "Cookie White List", &c_wl);
3510 else if (args->i & XT_WL_TOGGLE) {
3511 args->i |= XT_WL_RELOAD;
3512 toggle_cwl(t, args);
3513 } else if (args->i & XT_SAVE) {
3514 args->i |= XT_WL_RELOAD;
3515 wl_save(t, args, 0);
3516 } else if (args->i & XT_DELETE)
3517 show_oops(t, "'cookie delete' currently unimplemented");
3519 return (0);
3523 js_cmd(struct tab *t, struct karg *args)
3525 if (args->i & XT_SHOW)
3526 wl_show(t, args, "JavaScript White List", &js_wl);
3527 else if (args->i & XT_SAVE) {
3528 args->i |= XT_WL_RELOAD;
3529 wl_save(t, args, 1);
3530 } else if (args->i & XT_WL_TOGGLE) {
3531 args->i |= XT_WL_RELOAD;
3532 toggle_js(t, args);
3533 } else if (args->i & XT_DELETE)
3534 show_oops(t, "'js delete' currently unimplemented");
3536 return (0);
3540 toplevel_cmd(struct tab *t, struct karg *args)
3542 js_toggle_cb(t->js_toggle, t);
3544 return (0);
3548 add_favorite(struct tab *t, struct karg *args)
3550 char file[PATH_MAX];
3551 FILE *f;
3552 char *line = NULL;
3553 size_t urilen, linelen;
3554 const gchar *uri, *title;
3556 if (t == NULL)
3557 return (1);
3559 /* don't allow adding of xtp pages to favorites */
3560 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3561 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3562 return (1);
3565 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3566 if ((f = fopen(file, "r+")) == NULL) {
3567 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3568 return (1);
3571 title = webkit_web_view_get_title(t->wv);
3572 uri = get_uri(t);
3574 if (title == NULL)
3575 title = uri;
3577 if (title == NULL || uri == NULL) {
3578 show_oops(t, "can't add page to favorites");
3579 goto done;
3582 urilen = strlen(uri);
3584 for (;;) {
3585 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3586 if (feof(f) || ferror(f))
3587 break;
3589 if (linelen == urilen && !strcmp(line, uri))
3590 goto done;
3592 free(line);
3593 line = NULL;
3596 fprintf(f, "\n%s\n%s", title, uri);
3597 done:
3598 if (line)
3599 free(line);
3600 fclose(f);
3602 update_favorite_tabs(NULL);
3604 return (0);
3608 navaction(struct tab *t, struct karg *args)
3610 WebKitWebHistoryItem *item;
3612 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3613 t->tab_id, args->i);
3615 if (t->item) {
3616 if (args->i == XT_NAV_BACK)
3617 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3618 else
3619 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3620 if (item == NULL)
3621 return (XT_CB_PASSTHROUGH);
3622 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3623 t->item = NULL;
3624 return (XT_CB_PASSTHROUGH);
3627 switch (args->i) {
3628 case XT_NAV_BACK:
3629 webkit_web_view_go_back(t->wv);
3630 break;
3631 case XT_NAV_FORWARD:
3632 webkit_web_view_go_forward(t->wv);
3633 break;
3634 case XT_NAV_RELOAD:
3635 webkit_web_view_reload(t->wv);
3636 break;
3637 case XT_NAV_RELOAD_CACHE:
3638 webkit_web_view_reload_bypass_cache(t->wv);
3639 break;
3641 return (XT_CB_PASSTHROUGH);
3645 move(struct tab *t, struct karg *args)
3647 GtkAdjustment *adjust;
3648 double pi, si, pos, ps, upper, lower, max;
3650 switch (args->i) {
3651 case XT_MOVE_DOWN:
3652 case XT_MOVE_UP:
3653 case XT_MOVE_BOTTOM:
3654 case XT_MOVE_TOP:
3655 case XT_MOVE_PAGEDOWN:
3656 case XT_MOVE_PAGEUP:
3657 case XT_MOVE_HALFDOWN:
3658 case XT_MOVE_HALFUP:
3659 adjust = t->adjust_v;
3660 break;
3661 default:
3662 adjust = t->adjust_h;
3663 break;
3666 pos = gtk_adjustment_get_value(adjust);
3667 ps = gtk_adjustment_get_page_size(adjust);
3668 upper = gtk_adjustment_get_upper(adjust);
3669 lower = gtk_adjustment_get_lower(adjust);
3670 si = gtk_adjustment_get_step_increment(adjust);
3671 pi = gtk_adjustment_get_page_increment(adjust);
3672 max = upper - ps;
3674 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3675 "max %f si %f pi %f\n",
3676 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3677 pos, ps, upper, lower, max, si, pi);
3679 switch (args->i) {
3680 case XT_MOVE_DOWN:
3681 case XT_MOVE_RIGHT:
3682 pos += si;
3683 gtk_adjustment_set_value(adjust, MIN(pos, max));
3684 break;
3685 case XT_MOVE_UP:
3686 case XT_MOVE_LEFT:
3687 pos -= si;
3688 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3689 break;
3690 case XT_MOVE_BOTTOM:
3691 case XT_MOVE_FARRIGHT:
3692 gtk_adjustment_set_value(adjust, max);
3693 break;
3694 case XT_MOVE_TOP:
3695 case XT_MOVE_FARLEFT:
3696 gtk_adjustment_set_value(adjust, lower);
3697 break;
3698 case XT_MOVE_PAGEDOWN:
3699 pos += pi;
3700 gtk_adjustment_set_value(adjust, MIN(pos, max));
3701 break;
3702 case XT_MOVE_PAGEUP:
3703 pos -= pi;
3704 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3705 break;
3706 case XT_MOVE_HALFDOWN:
3707 pos += pi / 2;
3708 gtk_adjustment_set_value(adjust, MIN(pos, max));
3709 break;
3710 case XT_MOVE_HALFUP:
3711 pos -= pi / 2;
3712 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3713 break;
3714 default:
3715 return (XT_CB_PASSTHROUGH);
3718 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3720 return (XT_CB_HANDLED);
3723 void
3724 url_set_visibility(void)
3726 struct tab *t;
3728 TAILQ_FOREACH(t, &tabs, entry) {
3729 if (show_url == 0) {
3730 gtk_widget_hide(t->toolbar);
3731 focus_webview(t);
3732 } else
3733 gtk_widget_show(t->toolbar);
3737 void
3738 notebook_tab_set_visibility()
3740 if (show_tabs == 0) {
3741 gtk_widget_hide(tab_bar);
3742 gtk_notebook_set_show_tabs(notebook, FALSE);
3743 } else {
3744 if (tab_style == XT_TABS_NORMAL) {
3745 gtk_widget_hide(tab_bar);
3746 gtk_notebook_set_show_tabs(notebook, TRUE);
3747 } else if (tab_style == XT_TABS_COMPACT) {
3748 gtk_widget_show(tab_bar);
3749 gtk_notebook_set_show_tabs(notebook, FALSE);
3754 void
3755 statusbar_set_visibility(void)
3757 struct tab *t;
3759 TAILQ_FOREACH(t, &tabs, entry) {
3760 if (show_statusbar == 0) {
3761 gtk_widget_hide(t->statusbar_box);
3762 focus_webview(t);
3763 } else
3764 gtk_widget_show(t->statusbar_box);
3768 void
3769 url_set(struct tab *t, int enable_url_entry)
3771 GdkPixbuf *pixbuf;
3772 int progress;
3774 show_url = enable_url_entry;
3776 if (enable_url_entry) {
3777 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3778 GTK_ENTRY_ICON_PRIMARY, NULL);
3779 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3780 } else {
3781 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3782 GTK_ENTRY_ICON_PRIMARY);
3783 progress =
3784 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3785 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
3786 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3787 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
3788 progress);
3793 fullscreen(struct tab *t, struct karg *args)
3795 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3797 if (t == NULL)
3798 return (XT_CB_PASSTHROUGH);
3800 if (show_url == 0) {
3801 url_set(t, 1);
3802 show_tabs = 1;
3803 } else {
3804 url_set(t, 0);
3805 show_tabs = 0;
3808 url_set_visibility();
3809 notebook_tab_set_visibility();
3811 return (XT_CB_HANDLED);
3815 statustoggle(struct tab *t, struct karg *args)
3817 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3819 if (show_statusbar == 1) {
3820 show_statusbar = 0;
3821 statusbar_set_visibility();
3822 } else if (show_statusbar == 0) {
3823 show_statusbar = 1;
3824 statusbar_set_visibility();
3826 return (XT_CB_HANDLED);
3830 urlaction(struct tab *t, struct karg *args)
3832 int rv = XT_CB_HANDLED;
3834 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3836 if (t == NULL)
3837 return (XT_CB_PASSTHROUGH);
3839 switch (args->i) {
3840 case XT_URL_SHOW:
3841 if (show_url == 0) {
3842 url_set(t, 1);
3843 url_set_visibility();
3845 break;
3846 case XT_URL_HIDE:
3847 if (show_url == 1) {
3848 url_set(t, 0);
3849 url_set_visibility();
3851 break;
3853 return (rv);
3857 tabaction(struct tab *t, struct karg *args)
3859 int rv = XT_CB_HANDLED;
3860 char *url = args->s;
3861 struct undo *u;
3862 struct tab *tt;
3864 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3866 if (t == NULL)
3867 return (XT_CB_PASSTHROUGH);
3869 switch (args->i) {
3870 case XT_TAB_NEW:
3871 if (strlen(url) > 0)
3872 create_new_tab(url, NULL, 1, args->p);
3873 else
3874 create_new_tab(NULL, NULL, 1, args->p);
3875 break;
3876 case XT_TAB_DELETE:
3877 if (args->p < 0)
3878 delete_tab(t);
3879 else
3880 TAILQ_FOREACH(tt, &tabs, entry)
3881 if (tt->tab_id == args->p - 1) {
3882 delete_tab(tt);
3883 recalc_tabs();
3884 break;
3886 break;
3887 case XT_TAB_DELQUIT:
3888 if (gtk_notebook_get_n_pages(notebook) > 1)
3889 delete_tab(t);
3890 else
3891 quit(t, args);
3892 break;
3893 case XT_TAB_OPEN:
3894 if (strlen(url) > 0)
3896 else {
3897 rv = XT_CB_PASSTHROUGH;
3898 goto done;
3900 load_uri(t, url);
3901 break;
3902 case XT_TAB_SHOW:
3903 if (show_tabs == 0) {
3904 show_tabs = 1;
3905 notebook_tab_set_visibility();
3907 break;
3908 case XT_TAB_HIDE:
3909 if (show_tabs == 1) {
3910 show_tabs = 0;
3911 notebook_tab_set_visibility();
3913 break;
3914 case XT_TAB_NEXTSTYLE:
3915 if (tab_style == XT_TABS_NORMAL)
3916 tab_style = XT_TABS_COMPACT;
3917 else
3918 tab_style = XT_TABS_NORMAL;
3919 notebook_tab_set_visibility();
3920 break;
3921 case XT_TAB_UNDO_CLOSE:
3922 if (undo_count == 0) {
3923 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3924 goto done;
3925 } else {
3926 undo_count--;
3927 u = TAILQ_FIRST(&undos);
3928 create_new_tab(u->uri, u, 1, -1);
3930 TAILQ_REMOVE(&undos, u, entry);
3931 g_free(u->uri);
3932 /* u->history is freed in create_new_tab() */
3933 g_free(u);
3935 break;
3936 default:
3937 rv = XT_CB_PASSTHROUGH;
3938 goto done;
3941 done:
3942 if (args->s) {
3943 g_free(args->s);
3944 args->s = NULL;
3947 return (rv);
3951 resizetab(struct tab *t, struct karg *args)
3953 if (t == NULL || args == NULL) {
3954 show_oops(NULL, "resizetab invalid parameters");
3955 return (XT_CB_PASSTHROUGH);
3958 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3959 t->tab_id, args->i);
3961 adjustfont_webkit(t, args->i);
3963 return (XT_CB_HANDLED);
3967 movetab(struct tab *t, struct karg *args)
3969 int n, dest;
3971 if (t == NULL || args == NULL) {
3972 show_oops(NULL, "movetab invalid parameters");
3973 return (XT_CB_PASSTHROUGH);
3976 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3977 t->tab_id, args->i);
3979 if (args->i >= XT_TAB_INVALID)
3980 return (XT_CB_PASSTHROUGH);
3982 if (TAILQ_EMPTY(&tabs))
3983 return (XT_CB_PASSTHROUGH);
3985 n = gtk_notebook_get_n_pages(notebook);
3986 dest = gtk_notebook_get_current_page(notebook);
3988 switch (args->i) {
3989 case XT_TAB_NEXT:
3990 if (args->p < 0)
3991 dest = dest == n - 1 ? 0 : dest + 1;
3992 else
3993 dest = args->p - 1;
3995 break;
3996 case XT_TAB_PREV:
3997 if (args->p < 0)
3998 dest -= 1;
3999 else
4000 dest -= args->p % n;
4002 if (dest < 0)
4003 dest += n;
4005 break;
4006 case XT_TAB_FIRST:
4007 dest = 0;
4008 break;
4009 case XT_TAB_LAST:
4010 dest = n - 1;
4011 break;
4012 default:
4013 return (XT_CB_PASSTHROUGH);
4016 if (dest < 0 || dest >= n)
4017 return (XT_CB_PASSTHROUGH);
4018 if (t->tab_id == dest) {
4019 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4020 return (XT_CB_HANDLED);
4023 set_current_tab(dest);
4025 return (XT_CB_HANDLED);
4028 int cmd_prefix = 0;
4031 command(struct tab *t, struct karg *args)
4033 char *s = NULL, *ss = NULL;
4034 GdkColor color;
4035 const gchar *uri;
4037 if (t == NULL || args == NULL) {
4038 show_oops(NULL, "command invalid parameters");
4039 return (XT_CB_PASSTHROUGH);
4042 switch (args->i) {
4043 case '/':
4044 s = "/";
4045 break;
4046 case '?':
4047 s = "?";
4048 break;
4049 case ':':
4050 if (cmd_prefix == 0)
4051 s = ":";
4052 else {
4053 ss = g_strdup_printf(":%d", cmd_prefix);
4054 s = ss;
4055 cmd_prefix = 0;
4057 break;
4058 case XT_CMD_OPEN:
4059 s = ":open ";
4060 break;
4061 case XT_CMD_TABNEW:
4062 s = ":tabnew ";
4063 break;
4064 case XT_CMD_OPEN_CURRENT:
4065 s = ":open ";
4066 /* FALL THROUGH */
4067 case XT_CMD_TABNEW_CURRENT:
4068 if (!s) /* FALL THROUGH? */
4069 s = ":tabnew ";
4070 if ((uri = get_uri(t)) != NULL) {
4071 ss = g_strdup_printf("%s%s", s, uri);
4072 s = ss;
4074 break;
4075 default:
4076 show_oops(t, "command: invalid opcode %d", args->i);
4077 return (XT_CB_PASSTHROUGH);
4080 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4082 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4083 gdk_color_parse(XT_COLOR_WHITE, &color);
4084 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4085 show_cmd(t);
4086 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4087 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4089 if (ss)
4090 g_free(ss);
4092 return (XT_CB_HANDLED);
4096 * Return a new string with a download row (in html)
4097 * appended. Old string is freed.
4099 char *
4100 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4103 WebKitDownloadStatus stat;
4104 char *status_html = NULL, *cmd_html = NULL, *new_html;
4105 gdouble progress;
4106 char cur_sz[FMT_SCALED_STRSIZE];
4107 char tot_sz[FMT_SCALED_STRSIZE];
4108 char *xtp_prefix;
4110 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4112 /* All actions wil take this form:
4113 * xxxt://class/seskey
4115 xtp_prefix = g_strdup_printf("%s%d/%s/",
4116 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4118 stat = webkit_download_get_status(dl->download);
4120 switch (stat) {
4121 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4122 status_html = g_strdup_printf("Finished");
4123 cmd_html = g_strdup_printf(
4124 "<a href='%s%d/%d'>Remove</a>",
4125 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4126 break;
4127 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4128 /* gather size info */
4129 progress = 100 * webkit_download_get_progress(dl->download);
4131 fmt_scaled(
4132 webkit_download_get_current_size(dl->download), cur_sz);
4133 fmt_scaled(
4134 webkit_download_get_total_size(dl->download), tot_sz);
4136 status_html = g_strdup_printf(
4137 "<div style='width: 100%%' align='center'>"
4138 "<div class='progress-outer'>"
4139 "<div class='progress-inner' style='width: %.2f%%'>"
4140 "</div></div></div>"
4141 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4142 progress, cur_sz, tot_sz, progress);
4144 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4145 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4147 break;
4148 /* LLL */
4149 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4150 status_html = g_strdup_printf("Cancelled");
4151 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4152 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4153 break;
4154 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4155 status_html = g_strdup_printf("Error!");
4156 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4157 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4158 break;
4159 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4160 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4161 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4162 status_html = g_strdup_printf("Starting");
4163 break;
4164 default:
4165 show_oops(t, "%s: unknown download status", __func__);
4168 new_html = g_strdup_printf(
4169 "%s\n<tr><td>%s</td><td>%s</td>"
4170 "<td style='text-align:center'>%s</td></tr>\n",
4171 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4172 status_html, cmd_html);
4173 g_free(html);
4175 if (status_html)
4176 g_free(status_html);
4178 if (cmd_html)
4179 g_free(cmd_html);
4181 g_free(xtp_prefix);
4183 return new_html;
4187 * update all download tabs apart from one. Pass NULL if
4188 * you want to update all.
4190 void
4191 update_download_tabs(struct tab *apart_from)
4193 struct tab *t;
4194 if (!updating_dl_tabs) {
4195 updating_dl_tabs = 1; /* stop infinite recursion */
4196 TAILQ_FOREACH(t, &tabs, entry)
4197 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4198 && (t != apart_from))
4199 xtp_page_dl(t, NULL);
4200 updating_dl_tabs = 0;
4205 * update all cookie tabs apart from one. Pass NULL if
4206 * you want to update all.
4208 void
4209 update_cookie_tabs(struct tab *apart_from)
4211 struct tab *t;
4212 if (!updating_cl_tabs) {
4213 updating_cl_tabs = 1; /* stop infinite recursion */
4214 TAILQ_FOREACH(t, &tabs, entry)
4215 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4216 && (t != apart_from))
4217 xtp_page_cl(t, NULL);
4218 updating_cl_tabs = 0;
4223 * update all history tabs apart from one. Pass NULL if
4224 * you want to update all.
4226 void
4227 update_history_tabs(struct tab *apart_from)
4229 struct tab *t;
4231 if (!updating_hl_tabs) {
4232 updating_hl_tabs = 1; /* stop infinite recursion */
4233 TAILQ_FOREACH(t, &tabs, entry)
4234 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4235 && (t != apart_from))
4236 xtp_page_hl(t, NULL);
4237 updating_hl_tabs = 0;
4241 /* cookie management XTP page */
4243 xtp_page_cl(struct tab *t, struct karg *args)
4245 char *body, *page, *tmp;
4246 int i = 1; /* all ids start 1 */
4247 GSList *sc, *pc, *pc_start;
4248 SoupCookie *c;
4249 char *type, *table_headers, *last_domain;
4251 DNPRINTF(XT_D_CMD, "%s", __func__);
4253 if (t == NULL) {
4254 show_oops(NULL, "%s invalid parameters", __func__);
4255 return (1);
4258 /* Generate a new session key */
4259 if (!updating_cl_tabs)
4260 generate_xtp_session_key(&cl_session_key);
4262 /* table headers */
4263 table_headers = g_strdup_printf("<table><tr>"
4264 "<th>Type</th>"
4265 "<th>Name</th>"
4266 "<th style='width:200px'>Value</th>"
4267 "<th>Path</th>"
4268 "<th>Expires</th>"
4269 "<th>Secure</th>"
4270 "<th>HTTP<br />only</th>"
4271 "<th style='width:40px'>Rm</th></tr>\n");
4273 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4274 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4275 pc_start = pc;
4277 body = NULL;
4278 last_domain = strdup("");
4279 for (; sc; sc = sc->next) {
4280 c = sc->data;
4282 if (strcmp(last_domain, c->domain) != 0) {
4283 /* new domain */
4284 free(last_domain);
4285 last_domain = strdup(c->domain);
4287 if (body != NULL) {
4288 tmp = body;
4289 body = g_strdup_printf("%s</table>"
4290 "<h2>%s</h2>%s\n",
4291 body, c->domain, table_headers);
4292 g_free(tmp);
4293 } else {
4294 /* first domain */
4295 body = g_strdup_printf("<h2>%s</h2>%s\n",
4296 c->domain, table_headers);
4300 type = "Session";
4301 for (pc = pc_start; pc; pc = pc->next)
4302 if (soup_cookie_equal(pc->data, c)) {
4303 type = "Session + Persistent";
4304 break;
4307 tmp = body;
4308 body = g_strdup_printf(
4309 "%s\n<tr>"
4310 "<td>%s</td>"
4311 "<td style='word-wrap:normal'>%s</td>"
4312 "<td>"
4313 " <textarea rows='4'>%s</textarea>"
4314 "</td>"
4315 "<td>%s</td>"
4316 "<td>%s</td>"
4317 "<td>%d</td>"
4318 "<td>%d</td>"
4319 "<td style='text-align:center'>"
4320 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4321 body,
4322 type,
4323 c->name,
4324 c->value,
4325 c->path,
4326 c->expires ?
4327 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4328 c->secure,
4329 c->http_only,
4331 XT_XTP_STR,
4332 XT_XTP_CL,
4333 cl_session_key,
4334 XT_XTP_CL_REMOVE,
4338 g_free(tmp);
4339 i++;
4342 soup_cookies_free(sc);
4343 soup_cookies_free(pc);
4345 /* small message if there are none */
4346 if (i == 1) {
4347 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4348 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4350 tmp = body;
4351 body = g_strdup_printf("%s</table>", body);
4352 g_free(tmp);
4354 page = get_html_page("Cookie Jar", body, "", TRUE);
4355 g_free(body);
4356 g_free(table_headers);
4357 g_free(last_domain);
4359 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4360 update_cookie_tabs(t);
4362 g_free(page);
4364 return (0);
4368 xtp_page_hl(struct tab *t, struct karg *args)
4370 char *body, *page, *tmp;
4371 struct history *h;
4372 int i = 1; /* all ids start 1 */
4374 DNPRINTF(XT_D_CMD, "%s", __func__);
4376 if (t == NULL) {
4377 show_oops(NULL, "%s invalid parameters", __func__);
4378 return (1);
4381 /* Generate a new session key */
4382 if (!updating_hl_tabs)
4383 generate_xtp_session_key(&hl_session_key);
4385 /* body */
4386 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4387 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4389 RB_FOREACH_REVERSE(h, history_list, &hl) {
4390 tmp = body;
4391 body = g_strdup_printf(
4392 "%s\n<tr>"
4393 "<td><a href='%s'>%s</a></td>"
4394 "<td>%s</td>"
4395 "<td style='text-align: center'>"
4396 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4397 body, h->uri, h->uri, h->title,
4398 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4399 XT_XTP_HL_REMOVE, i);
4401 g_free(tmp);
4402 i++;
4405 /* small message if there are none */
4406 if (i == 1) {
4407 tmp = body;
4408 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4409 "colspan='3'>No History</td></tr>\n", body);
4410 g_free(tmp);
4413 tmp = body;
4414 body = g_strdup_printf("%s</table>", body);
4415 g_free(tmp);
4417 page = get_html_page("History", body, "", TRUE);
4418 g_free(body);
4421 * update all history manager tabs as the xtp session
4422 * key has now changed. No need to update the current tab.
4423 * Already did that above.
4425 update_history_tabs(t);
4427 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4428 g_free(page);
4430 return (0);
4434 * Generate a web page detailing the status of any downloads
4437 xtp_page_dl(struct tab *t, struct karg *args)
4439 struct download *dl;
4440 char *body, *page, *tmp;
4441 char *ref;
4442 int n_dl = 1;
4444 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4446 if (t == NULL) {
4447 show_oops(NULL, "%s invalid parameters", __func__);
4448 return (1);
4452 * Generate a new session key for next page instance.
4453 * This only happens for the top level call to xtp_page_dl()
4454 * in which case updating_dl_tabs is 0.
4456 if (!updating_dl_tabs)
4457 generate_xtp_session_key(&dl_session_key);
4459 /* header - with refresh so as to update */
4460 if (refresh_interval >= 1)
4461 ref = g_strdup_printf(
4462 "<meta http-equiv='refresh' content='%u"
4463 ";url=%s%d/%s/%d' />\n",
4464 refresh_interval,
4465 XT_XTP_STR,
4466 XT_XTP_DL,
4467 dl_session_key,
4468 XT_XTP_DL_LIST);
4469 else
4470 ref = g_strdup("");
4472 body = g_strdup_printf("<div align='center'>"
4473 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4474 "</p><table><tr><th style='width: 60%%'>"
4475 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4476 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4478 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4479 body = xtp_page_dl_row(t, body, dl);
4480 n_dl++;
4483 /* message if no downloads in list */
4484 if (n_dl == 1) {
4485 tmp = body;
4486 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4487 " style='text-align: center'>"
4488 "No downloads</td></tr>\n", body);
4489 g_free(tmp);
4492 tmp = body;
4493 body = g_strdup_printf("%s</table></div>", body);
4494 g_free(tmp);
4496 page = get_html_page("Downloads", body, ref, 1);
4497 g_free(ref);
4498 g_free(body);
4501 * update all download manager tabs as the xtp session
4502 * key has now changed. No need to update the current tab.
4503 * Already did that above.
4505 update_download_tabs(t);
4507 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4508 g_free(page);
4510 return (0);
4514 search(struct tab *t, struct karg *args)
4516 gboolean d;
4518 if (t == NULL || args == NULL) {
4519 show_oops(NULL, "search invalid parameters");
4520 return (1);
4522 if (t->search_text == NULL) {
4523 if (global_search == NULL)
4524 return (XT_CB_PASSTHROUGH);
4525 else {
4526 t->search_text = g_strdup(global_search);
4527 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4528 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4532 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4533 t->tab_id, args->i, t->search_forward, t->search_text);
4535 switch (args->i) {
4536 case XT_SEARCH_NEXT:
4537 d = t->search_forward;
4538 break;
4539 case XT_SEARCH_PREV:
4540 d = !t->search_forward;
4541 break;
4542 default:
4543 return (XT_CB_PASSTHROUGH);
4546 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4548 return (XT_CB_HANDLED);
4551 struct settings_args {
4552 char **body;
4553 int i;
4556 void
4557 print_setting(struct settings *s, char *val, void *cb_args)
4559 char *tmp, *color;
4560 struct settings_args *sa = cb_args;
4562 if (sa == NULL)
4563 return;
4565 if (s->flags & XT_SF_RUNTIME)
4566 color = "#22cc22";
4567 else
4568 color = "#cccccc";
4570 tmp = *sa->body;
4571 *sa->body = g_strdup_printf(
4572 "%s\n<tr>"
4573 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4574 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4575 *sa->body,
4576 color,
4577 s->name,
4578 color,
4581 g_free(tmp);
4582 sa->i++;
4586 set(struct tab *t, struct karg *args)
4588 char *body, *page, *tmp;
4589 int i = 1;
4590 struct settings_args sa;
4592 bzero(&sa, sizeof sa);
4593 sa.body = &body;
4595 /* body */
4596 body = g_strdup_printf("<div align='center'><table><tr>"
4597 "<th align='left'>Setting</th>"
4598 "<th align='left'>Value</th></tr>\n");
4600 settings_walk(print_setting, &sa);
4601 i = sa.i;
4603 /* small message if there are none */
4604 if (i == 1) {
4605 tmp = body;
4606 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4607 "colspan='2'>No settings</td></tr>\n", body);
4608 g_free(tmp);
4611 tmp = body;
4612 body = g_strdup_printf("%s</table></div>", body);
4613 g_free(tmp);
4615 page = get_html_page("Settings", body, "", 0);
4617 g_free(body);
4619 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4621 g_free(page);
4623 return (XT_CB_PASSTHROUGH);
4627 session_save(struct tab *t, char *filename)
4629 struct karg a;
4630 int rv = 1;
4632 if (strlen(filename) == 0)
4633 goto done;
4635 if (filename[0] == '.' || filename[0] == '/')
4636 goto done;
4638 a.s = filename;
4639 if (save_tabs(t, &a))
4640 goto done;
4641 strlcpy(named_session, filename, sizeof named_session);
4643 rv = 0;
4644 done:
4645 return (rv);
4649 session_open(struct tab *t, char *filename)
4651 struct karg a;
4652 int rv = 1;
4654 if (strlen(filename) == 0)
4655 goto done;
4657 if (filename[0] == '.' || filename[0] == '/')
4658 goto done;
4660 a.s = filename;
4661 a.i = XT_SES_CLOSETABS;
4662 if (open_tabs(t, &a))
4663 goto done;
4665 strlcpy(named_session, filename, sizeof named_session);
4667 rv = 0;
4668 done:
4669 return (rv);
4673 session_delete(struct tab *t, char *filename)
4675 char file[PATH_MAX];
4676 int rv = 1;
4678 if (strlen(filename) == 0)
4679 goto done;
4681 if (filename[0] == '.' || filename[0] == '/')
4682 goto done;
4684 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4685 if (unlink(file))
4686 goto done;
4688 if (!strcmp(filename, named_session))
4689 strlcpy(named_session, XT_SAVED_TABS_FILE,
4690 sizeof named_session);
4692 rv = 0;
4693 done:
4694 return (rv);
4698 session_cmd(struct tab *t, struct karg *args)
4700 char *filename = args->s;
4702 if (t == NULL)
4703 return (1);
4705 if (args->i & XT_SHOW)
4706 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4707 XT_SAVED_TABS_FILE : named_session);
4708 else if (args->i & XT_SAVE) {
4709 if (session_save(t, filename)) {
4710 show_oops(t, "Can't save session: %s",
4711 filename ? filename : "INVALID");
4712 goto done;
4714 } else if (args->i & XT_OPEN) {
4715 if (session_open(t, filename)) {
4716 show_oops(t, "Can't open session: %s",
4717 filename ? filename : "INVALID");
4718 goto done;
4720 } else if (args->i & XT_DELETE) {
4721 if (session_delete(t, filename)) {
4722 show_oops(t, "Can't delete session: %s",
4723 filename ? filename : "INVALID");
4724 goto done;
4727 done:
4728 return (XT_CB_PASSTHROUGH);
4732 * Make a hardcopy of the page
4735 print_page(struct tab *t, struct karg *args)
4737 WebKitWebFrame *frame;
4738 GtkPageSetup *ps;
4739 GtkPrintOperation *op;
4740 GtkPrintOperationAction action;
4741 GtkPrintOperationResult print_res;
4742 GError *g_err = NULL;
4743 int marg_l, marg_r, marg_t, marg_b;
4745 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4747 ps = gtk_page_setup_new();
4748 op = gtk_print_operation_new();
4749 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4750 frame = webkit_web_view_get_main_frame(t->wv);
4752 /* the default margins are too small, so we will bump them */
4753 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4754 XT_PRINT_EXTRA_MARGIN;
4755 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4756 XT_PRINT_EXTRA_MARGIN;
4757 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4758 XT_PRINT_EXTRA_MARGIN;
4759 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4760 XT_PRINT_EXTRA_MARGIN;
4762 /* set margins */
4763 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4764 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4765 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4766 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4768 gtk_print_operation_set_default_page_setup(op, ps);
4770 /* this appears to free 'op' and 'ps' */
4771 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4773 /* check it worked */
4774 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4775 show_oops(NULL, "can't print: %s", g_err->message);
4776 g_error_free (g_err);
4777 return (1);
4780 return (0);
4784 go_home(struct tab *t, struct karg *args)
4786 load_uri(t, home);
4787 return (0);
4791 restart(struct tab *t, struct karg *args)
4793 struct karg a;
4795 a.s = XT_RESTART_TABS_FILE;
4796 save_tabs(t, &a);
4797 execvp(start_argv[0], start_argv);
4798 /* NOTREACHED */
4800 return (0);
4803 #define CTRL GDK_CONTROL_MASK
4804 #define MOD1 GDK_MOD1_MASK
4805 #define SHFT GDK_SHIFT_MASK
4807 /* inherent to GTK not all keys will be caught at all times */
4808 /* XXX sort key bindings */
4809 struct key_binding {
4810 char *cmd;
4811 guint mask;
4812 guint use_in_entry;
4813 guint key;
4814 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4815 } keys[] = {
4816 { "cookiejar", MOD1, 0, GDK_j },
4817 { "downloadmgr", MOD1, 0, GDK_d },
4818 { "history", MOD1, 0, GDK_h },
4819 { "print", CTRL, 0, GDK_p },
4820 { "search", 0, 0, GDK_slash },
4821 { "searchb", 0, 0, GDK_question },
4822 { "statustoggle", CTRL, 0, GDK_n },
4823 { "command", 0, 0, GDK_colon },
4824 { "qa", CTRL, 0, GDK_q },
4825 { "restart", MOD1, 0, GDK_q },
4826 { "js toggle", CTRL, 0, GDK_j },
4827 { "cookie toggle", MOD1, 0, GDK_c },
4828 { "togglesrc", CTRL, 0, GDK_s },
4829 { "yankuri", 0, 0, GDK_y },
4830 { "pasteuricur", 0, 0, GDK_p },
4831 { "pasteurinew", 0, 0, GDK_P },
4832 { "toplevel toggle", 0, 0, GDK_F4 },
4833 { "help", 0, 0, GDK_F1 },
4835 /* search */
4836 { "searchnext", 0, 0, GDK_n },
4837 { "searchprevious", 0, 0, GDK_N },
4839 /* focus */
4840 { "focusaddress", 0, 0, GDK_F6 },
4841 { "focussearch", 0, 0, GDK_F7 },
4843 /* hinting */
4844 { "hinting", 0, 0, GDK_f },
4846 /* custom stylesheet */
4847 { "userstyle", 0, 0, GDK_i },
4849 /* navigation */
4850 { "goback", 0, 0, GDK_BackSpace },
4851 { "goback", MOD1, 0, GDK_Left },
4852 { "goforward", SHFT, 0, GDK_BackSpace },
4853 { "goforward", MOD1, 0, GDK_Right },
4854 { "reload", 0, 0, GDK_F5 },
4855 { "reload", CTRL, 0, GDK_r },
4856 { "reloadforce", CTRL, 0, GDK_R },
4857 { "reload", CTRL, 0, GDK_l },
4858 { "favorites", MOD1, 1, GDK_f },
4860 /* vertical movement */
4861 { "scrolldown", 0, 0, GDK_j },
4862 { "scrolldown", 0, 0, GDK_Down },
4863 { "scrollup", 0, 0, GDK_Up },
4864 { "scrollup", 0, 0, GDK_k },
4865 { "scrollbottom", 0, 0, GDK_G },
4866 { "scrollbottom", 0, 0, GDK_End },
4867 { "scrolltop", 0, 0, GDK_Home },
4868 { "scrolltop", 0, 0, GDK_g },
4869 { "scrollpagedown", 0, 0, GDK_space },
4870 { "scrollpagedown", CTRL, 0, GDK_f },
4871 { "scrollhalfdown", CTRL, 0, GDK_d },
4872 { "scrollpagedown", 0, 0, GDK_Page_Down },
4873 { "scrollpageup", 0, 0, GDK_Page_Up },
4874 { "scrollpageup", CTRL, 0, GDK_b },
4875 { "scrollhalfup", CTRL, 0, GDK_u },
4876 /* horizontal movement */
4877 { "scrollright", 0, 0, GDK_l },
4878 { "scrollright", 0, 0, GDK_Right },
4879 { "scrollleft", 0, 0, GDK_Left },
4880 { "scrollleft", 0, 0, GDK_h },
4881 { "scrollfarright", 0, 0, GDK_dollar },
4882 { "scrollfarleft", 0, 0, GDK_0 },
4884 /* tabs */
4885 { "tabnew", CTRL, 0, GDK_t },
4886 { "999tabnew", CTRL, 0, GDK_T },
4887 { "tabclose", CTRL, 1, GDK_w },
4888 { "tabundoclose", 0, 0, GDK_U },
4889 { "tabnext 1", CTRL, 0, GDK_1 },
4890 { "tabnext 2", CTRL, 0, GDK_2 },
4891 { "tabnext 3", CTRL, 0, GDK_3 },
4892 { "tabnext 4", CTRL, 0, GDK_4 },
4893 { "tabnext 5", CTRL, 0, GDK_5 },
4894 { "tabnext 6", CTRL, 0, GDK_6 },
4895 { "tabnext 7", CTRL, 0, GDK_7 },
4896 { "tabnext 8", CTRL, 0, GDK_8 },
4897 { "tabnext 9", CTRL, 0, GDK_9 },
4898 { "tabnext 10", CTRL, 0, GDK_0 },
4899 { "tabfirst", CTRL, 0, GDK_less },
4900 { "tablast", CTRL, 0, GDK_greater },
4901 { "tabprevious", CTRL, 0, GDK_Left },
4902 { "tabnext", CTRL, 0, GDK_Right },
4903 { "focusout", CTRL, 0, GDK_minus },
4904 { "focusin", CTRL, 0, GDK_plus },
4905 { "focusin", CTRL, 0, GDK_equal },
4907 /* command aliases (handy when -S flag is used) */
4908 { "promptopen", 0, 0, GDK_F9 },
4909 { "promptopencurrent", 0, 0, GDK_F10 },
4910 { "prompttabnew", 0, 0, GDK_F11 },
4911 { "prompttabnewcurrent",0, 0, GDK_F12 },
4913 TAILQ_HEAD(keybinding_list, key_binding);
4915 void
4916 walk_kb(struct settings *s,
4917 void (*cb)(struct settings *, char *, void *), void *cb_args)
4919 struct key_binding *k;
4920 char str[1024];
4922 if (s == NULL || cb == NULL) {
4923 show_oops(NULL, "walk_kb invalid parameters");
4924 return;
4927 TAILQ_FOREACH(k, &kbl, entry) {
4928 if (k->cmd == NULL)
4929 continue;
4930 str[0] = '\0';
4932 /* sanity */
4933 if (gdk_keyval_name(k->key) == NULL)
4934 continue;
4936 strlcat(str, k->cmd, sizeof str);
4937 strlcat(str, ",", sizeof str);
4939 if (k->mask & GDK_SHIFT_MASK)
4940 strlcat(str, "S-", sizeof str);
4941 if (k->mask & GDK_CONTROL_MASK)
4942 strlcat(str, "C-", sizeof str);
4943 if (k->mask & GDK_MOD1_MASK)
4944 strlcat(str, "M1-", sizeof str);
4945 if (k->mask & GDK_MOD2_MASK)
4946 strlcat(str, "M2-", sizeof str);
4947 if (k->mask & GDK_MOD3_MASK)
4948 strlcat(str, "M3-", sizeof str);
4949 if (k->mask & GDK_MOD4_MASK)
4950 strlcat(str, "M4-", sizeof str);
4951 if (k->mask & GDK_MOD5_MASK)
4952 strlcat(str, "M5-", sizeof str);
4954 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4955 cb(s, str, cb_args);
4959 void
4960 init_keybindings(void)
4962 int i;
4963 struct key_binding *k;
4965 for (i = 0; i < LENGTH(keys); i++) {
4966 k = g_malloc0(sizeof *k);
4967 k->cmd = keys[i].cmd;
4968 k->mask = keys[i].mask;
4969 k->use_in_entry = keys[i].use_in_entry;
4970 k->key = keys[i].key;
4971 TAILQ_INSERT_HEAD(&kbl, k, entry);
4973 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4974 k->cmd ? k->cmd : "unnamed key");
4978 void
4979 keybinding_clearall(void)
4981 struct key_binding *k, *next;
4983 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4984 next = TAILQ_NEXT(k, entry);
4985 if (k->cmd == NULL)
4986 continue;
4988 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4989 k->cmd ? k->cmd : "unnamed key");
4990 TAILQ_REMOVE(&kbl, k, entry);
4991 g_free(k);
4996 keybinding_add(char *cmd, char *key, int use_in_entry)
4998 struct key_binding *k;
4999 guint keyval, mask = 0;
5000 int i;
5002 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5004 /* Keys which are to be used in entry have been prefixed with an
5005 * exclamation mark. */
5006 if (use_in_entry)
5007 key++;
5009 /* find modifier keys */
5010 if (strstr(key, "S-"))
5011 mask |= GDK_SHIFT_MASK;
5012 if (strstr(key, "C-"))
5013 mask |= GDK_CONTROL_MASK;
5014 if (strstr(key, "M1-"))
5015 mask |= GDK_MOD1_MASK;
5016 if (strstr(key, "M2-"))
5017 mask |= GDK_MOD2_MASK;
5018 if (strstr(key, "M3-"))
5019 mask |= GDK_MOD3_MASK;
5020 if (strstr(key, "M4-"))
5021 mask |= GDK_MOD4_MASK;
5022 if (strstr(key, "M5-"))
5023 mask |= GDK_MOD5_MASK;
5025 /* find keyname */
5026 for (i = strlen(key) - 1; i > 0; i--)
5027 if (key[i] == '-')
5028 key = &key[i + 1];
5030 /* validate keyname */
5031 keyval = gdk_keyval_from_name(key);
5032 if (keyval == GDK_VoidSymbol) {
5033 warnx("invalid keybinding name %s", key);
5034 return (1);
5036 /* must run this test too, gtk+ doesn't handle 10 for example */
5037 if (gdk_keyval_name(keyval) == NULL) {
5038 warnx("invalid keybinding name %s", key);
5039 return (1);
5042 /* Remove eventual dupes. */
5043 TAILQ_FOREACH(k, &kbl, entry)
5044 if (k->key == keyval && k->mask == mask) {
5045 TAILQ_REMOVE(&kbl, k, entry);
5046 g_free(k);
5047 break;
5050 /* add keyname */
5051 k = g_malloc0(sizeof *k);
5052 k->cmd = g_strdup(cmd);
5053 k->mask = mask;
5054 k->use_in_entry = use_in_entry;
5055 k->key = keyval;
5057 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5058 k->cmd,
5059 k->mask,
5060 k->use_in_entry,
5061 k->key);
5062 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5063 k->cmd, gdk_keyval_name(keyval));
5065 TAILQ_INSERT_HEAD(&kbl, k, entry);
5067 return (0);
5071 add_kb(struct settings *s, char *entry)
5073 char *kb, *key;
5075 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5077 /* clearall is special */
5078 if (!strcmp(entry, "clearall")) {
5079 keybinding_clearall();
5080 return (0);
5083 kb = strstr(entry, ",");
5084 if (kb == NULL)
5085 return (1);
5086 *kb = '\0';
5087 key = kb + 1;
5089 return (keybinding_add(entry, key, key[0] == '!'));
5092 struct cmd {
5093 char *cmd;
5094 int level;
5095 int (*func)(struct tab *, struct karg *);
5096 int arg;
5097 int type;
5098 } cmds[] = {
5099 { "command", 0, command, ':', 0 },
5100 { "search", 0, command, '/', 0 },
5101 { "searchb", 0, command, '?', 0 },
5102 { "togglesrc", 0, toggle_src, 0, 0 },
5104 /* yanking and pasting */
5105 { "yankuri", 0, yank_uri, 0, 0 },
5106 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5107 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5108 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5110 /* search */
5111 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5112 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5114 /* focus */
5115 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5116 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5118 /* hinting */
5119 { "hinting", 0, hint, 0, 0 },
5121 /* custom stylesheet */
5122 { "userstyle", 0, userstyle, 0, 0 },
5124 /* navigation */
5125 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5126 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5127 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5128 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5130 /* vertical movement */
5131 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5132 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5133 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5134 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5135 { "1", 0, move, XT_MOVE_TOP, 0 },
5136 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5137 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5138 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5139 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5140 /* horizontal movement */
5141 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5142 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5143 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5144 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5147 { "favorites", 0, xtp_page_fl, 0, 0 },
5148 { "fav", 0, xtp_page_fl, 0, 0 },
5149 { "favadd", 0, add_favorite, 0, 0 },
5151 { "qall", 0, quit, 0, 0 },
5152 { "quitall", 0, quit, 0, 0 },
5153 { "w", 0, save_tabs, 0, 0 },
5154 { "wq", 0, save_tabs_and_quit, 0, 0 },
5155 { "help", 0, help, 0, 0 },
5156 { "about", 0, about, 0, 0 },
5157 { "stats", 0, stats, 0, 0 },
5158 { "version", 0, about, 0, 0 },
5160 /* js command */
5161 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5162 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5163 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5164 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5165 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5166 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5167 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5168 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5169 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5170 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5171 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5173 /* cookie command */
5174 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5175 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5176 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5177 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5178 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5179 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5180 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5181 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5182 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5183 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5184 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5186 /* toplevel (domain) command */
5187 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5188 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5190 /* cookie jar */
5191 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5193 /* cert command */
5194 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5195 { "save", 1, cert_cmd, XT_SAVE, 0 },
5196 { "show", 1, cert_cmd, XT_SHOW, 0 },
5198 { "ca", 0, ca_cmd, 0, 0 },
5199 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5200 { "dl", 0, xtp_page_dl, 0, 0 },
5201 { "h", 0, xtp_page_hl, 0, 0 },
5202 { "history", 0, xtp_page_hl, 0, 0 },
5203 { "home", 0, go_home, 0, 0 },
5204 { "restart", 0, restart, 0, 0 },
5205 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5206 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5207 { "statustoggle", 0, statustoggle, 0, 0 },
5209 { "print", 0, print_page, 0, 0 },
5211 /* tabs */
5212 { "focusin", 0, resizetab, 1, 0 },
5213 { "focusout", 0, resizetab, -1, 0 },
5214 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5215 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5216 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5217 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5218 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5219 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5220 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5221 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5222 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5223 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5224 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5225 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5226 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5227 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5228 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5229 { "buffers", 0, buffers, 0, 0 },
5230 { "ls", 0, buffers, 0, 0 },
5231 { "tabs", 0, buffers, 0, 0 },
5233 /* command aliases (handy when -S flag is used) */
5234 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5235 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5236 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5237 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5239 /* settings */
5240 { "set", 0, set, 0, 0 },
5241 { "fullscreen", 0, fullscreen, 0, 0 },
5242 { "f", 0, fullscreen, 0, 0 },
5244 /* sessions */
5245 { "session", 0, session_cmd, XT_SHOW, 0 },
5246 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5247 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5248 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5249 { "show", 1, session_cmd, XT_SHOW, 0 },
5252 struct {
5253 int index;
5254 int len;
5255 gchar *list[256];
5256 } cmd_status = {-1, 0};
5258 gboolean
5259 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5262 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5263 btn_down = 0;
5265 return (FALSE);
5268 gboolean
5269 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5271 struct karg a;
5273 hide_oops(t);
5274 hide_buffers(t);
5276 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5277 btn_down = 1;
5278 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5279 /* go backward */
5280 a.i = XT_NAV_BACK;
5281 navaction(t, &a);
5283 return (TRUE);
5284 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5285 /* go forward */
5286 a.i = XT_NAV_FORWARD;
5287 navaction(t, &a);
5289 return (TRUE);
5292 return (FALSE);
5295 gboolean
5296 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5298 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5300 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5301 delete_tab(t);
5303 return (FALSE);
5307 * cancel, remove, etc. downloads
5309 void
5310 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5312 struct download find, *d = NULL;
5314 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5316 /* some commands require a valid download id */
5317 if (cmd != XT_XTP_DL_LIST) {
5318 /* lookup download in question */
5319 find.id = id;
5320 d = RB_FIND(download_list, &downloads, &find);
5322 if (d == NULL) {
5323 show_oops(t, "%s: no such download", __func__);
5324 return;
5328 /* decide what to do */
5329 switch (cmd) {
5330 case XT_XTP_DL_CANCEL:
5331 webkit_download_cancel(d->download);
5332 break;
5333 case XT_XTP_DL_REMOVE:
5334 webkit_download_cancel(d->download); /* just incase */
5335 g_object_unref(d->download);
5336 RB_REMOVE(download_list, &downloads, d);
5337 break;
5338 case XT_XTP_DL_LIST:
5339 /* Nothing */
5340 break;
5341 default:
5342 show_oops(t, "%s: unknown command", __func__);
5343 break;
5345 xtp_page_dl(t, NULL);
5349 * Actions on history, only does one thing for now, but
5350 * we provide the function for future actions
5352 void
5353 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5355 struct history *h, *next;
5356 int i = 1;
5358 switch (cmd) {
5359 case XT_XTP_HL_REMOVE:
5360 /* walk backwards, as listed in reverse */
5361 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5362 next = RB_PREV(history_list, &hl, h);
5363 if (id == i) {
5364 RB_REMOVE(history_list, &hl, h);
5365 g_free((gpointer) h->title);
5366 g_free((gpointer) h->uri);
5367 g_free(h);
5368 break;
5370 i++;
5372 break;
5373 case XT_XTP_HL_LIST:
5374 /* Nothing - just xtp_page_hl() below */
5375 break;
5376 default:
5377 show_oops(t, "%s: unknown command", __func__);
5378 break;
5381 xtp_page_hl(t, NULL);
5384 /* remove a favorite */
5385 void
5386 remove_favorite(struct tab *t, int index)
5388 char file[PATH_MAX], *title, *uri = NULL;
5389 char *new_favs, *tmp;
5390 FILE *f;
5391 int i;
5392 size_t len, lineno;
5394 /* open favorites */
5395 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5397 if ((f = fopen(file, "r")) == NULL) {
5398 show_oops(t, "%s: can't open favorites: %s",
5399 __func__, strerror(errno));
5400 return;
5403 /* build a string which will become the new favroites file */
5404 new_favs = g_strdup("");
5406 for (i = 1;;) {
5407 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5408 if (feof(f) || ferror(f))
5409 break;
5410 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5411 if (len == 0) {
5412 free(title);
5413 title = NULL;
5414 continue;
5417 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5418 if (feof(f) || ferror(f)) {
5419 show_oops(t, "%s: can't parse favorites %s",
5420 __func__, strerror(errno));
5421 goto clean;
5425 /* as long as this isn't the one we are deleting add to file */
5426 if (i != index) {
5427 tmp = new_favs;
5428 new_favs = g_strdup_printf("%s%s\n%s\n",
5429 new_favs, title, uri);
5430 g_free(tmp);
5433 free(uri);
5434 uri = NULL;
5435 free(title);
5436 title = NULL;
5437 i++;
5439 fclose(f);
5441 /* write back new favorites file */
5442 if ((f = fopen(file, "w")) == NULL) {
5443 show_oops(t, "%s: can't open favorites: %s",
5444 __func__, strerror(errno));
5445 goto clean;
5448 fwrite(new_favs, strlen(new_favs), 1, f);
5449 fclose(f);
5451 clean:
5452 if (uri)
5453 free(uri);
5454 if (title)
5455 free(title);
5457 g_free(new_favs);
5460 void
5461 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5463 switch (cmd) {
5464 case XT_XTP_FL_LIST:
5465 /* nothing, just the below call to xtp_page_fl() */
5466 break;
5467 case XT_XTP_FL_REMOVE:
5468 remove_favorite(t, arg);
5469 break;
5470 default:
5471 show_oops(t, "%s: invalid favorites command", __func__);
5472 break;
5475 xtp_page_fl(t, NULL);
5478 void
5479 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5481 switch (cmd) {
5482 case XT_XTP_CL_LIST:
5483 /* nothing, just xtp_page_cl() */
5484 break;
5485 case XT_XTP_CL_REMOVE:
5486 remove_cookie(arg);
5487 break;
5488 default:
5489 show_oops(t, "%s: unknown cookie xtp command", __func__);
5490 break;
5493 xtp_page_cl(t, NULL);
5496 /* link an XTP class to it's session key and handler function */
5497 struct xtp_despatch {
5498 uint8_t xtp_class;
5499 char **session_key;
5500 void (*handle_func)(struct tab *, uint8_t, int);
5503 struct xtp_despatch xtp_despatches[] = {
5504 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5505 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5506 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5507 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5508 { XT_XTP_INVALID, NULL, NULL }
5512 * is the url xtp protocol? (xxxt://)
5513 * if so, parse and despatch correct bahvior
5516 parse_xtp_url(struct tab *t, const char *url)
5518 char *dup = NULL, *p, *last;
5519 uint8_t n_tokens = 0;
5520 char *tokens[4] = {NULL, NULL, NULL, ""};
5521 struct xtp_despatch *dsp, *dsp_match = NULL;
5522 uint8_t req_class;
5523 int ret = FALSE;
5526 * tokens array meaning:
5527 * tokens[0] = class
5528 * tokens[1] = session key
5529 * tokens[2] = action
5530 * tokens[3] = optional argument
5533 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5535 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5536 goto clean;
5538 dup = g_strdup(url + strlen(XT_XTP_STR));
5540 /* split out the url */
5541 for ((p = strtok_r(dup, "/", &last)); p;
5542 (p = strtok_r(NULL, "/", &last))) {
5543 if (n_tokens < 4)
5544 tokens[n_tokens++] = p;
5547 /* should be atleast three fields 'class/seskey/command/arg' */
5548 if (n_tokens < 3)
5549 goto clean;
5551 dsp = xtp_despatches;
5552 req_class = atoi(tokens[0]);
5553 while (dsp->xtp_class) {
5554 if (dsp->xtp_class == req_class) {
5555 dsp_match = dsp;
5556 break;
5558 dsp++;
5561 /* did we find one atall? */
5562 if (dsp_match == NULL) {
5563 show_oops(t, "%s: no matching xtp despatch found", __func__);
5564 goto clean;
5567 /* check session key and call despatch function */
5568 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5569 ret = TRUE; /* all is well, this was a valid xtp request */
5570 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5573 clean:
5574 if (dup)
5575 g_free(dup);
5577 return (ret);
5582 void
5583 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5585 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5587 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5589 if (t == NULL) {
5590 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5591 return;
5594 if (uri == NULL) {
5595 show_oops(t, "activate_uri_entry_cb no uri");
5596 return;
5599 uri += strspn(uri, "\t ");
5601 /* if xxxt:// treat specially */
5602 if (parse_xtp_url(t, uri))
5603 return;
5605 /* otherwise continue to load page normally */
5606 load_uri(t, (gchar *)uri);
5607 focus_webview(t);
5610 void
5611 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5613 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5614 char *newuri = NULL;
5615 gchar *enc_search;
5617 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5619 if (t == NULL) {
5620 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5621 return;
5624 if (search_string == NULL) {
5625 show_oops(t, "no search_string");
5626 return;
5629 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5630 newuri = g_strdup_printf(search_string, enc_search);
5631 g_free(enc_search);
5633 webkit_web_view_load_uri(t->wv, newuri);
5634 focus_webview(t);
5636 if (newuri)
5637 g_free(newuri);
5640 void
5641 check_and_set_js(const gchar *uri, struct tab *t)
5643 struct domain *d = NULL;
5644 int es = 0;
5646 if (uri == NULL || t == NULL)
5647 return;
5649 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5650 es = 0;
5651 else
5652 es = 1;
5654 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5655 es ? "enable" : "disable", uri);
5657 g_object_set(G_OBJECT(t->settings),
5658 "enable-scripts", es, (char *)NULL);
5659 g_object_set(G_OBJECT(t->settings),
5660 "javascript-can-open-windows-automatically", es, (char *)NULL);
5661 webkit_web_view_set_settings(t->wv, t->settings);
5663 button_set_stockid(t->js_toggle,
5664 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5667 void
5668 show_ca_status(struct tab *t, const char *uri)
5670 WebKitWebFrame *frame;
5671 WebKitWebDataSource *source;
5672 WebKitNetworkRequest *request;
5673 SoupMessage *message;
5674 GdkColor color;
5675 gchar *col_str = XT_COLOR_WHITE;
5676 int r;
5678 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5679 ssl_strict_certs, ssl_ca_file, uri);
5681 if (uri == NULL)
5682 goto done;
5683 if (ssl_ca_file == NULL) {
5684 if (g_str_has_prefix(uri, "http://"))
5685 goto done;
5686 if (g_str_has_prefix(uri, "https://")) {
5687 col_str = XT_COLOR_RED;
5688 goto done;
5690 return;
5692 if (g_str_has_prefix(uri, "http://") ||
5693 !g_str_has_prefix(uri, "https://"))
5694 goto done;
5696 frame = webkit_web_view_get_main_frame(t->wv);
5697 source = webkit_web_frame_get_data_source(frame);
5698 request = webkit_web_data_source_get_request(source);
5699 message = webkit_network_request_get_message(request);
5701 if (message && (soup_message_get_flags(message) &
5702 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5703 col_str = XT_COLOR_GREEN;
5704 goto done;
5705 } else {
5706 r = load_compare_cert(t, NULL);
5707 if (r == 0)
5708 col_str = XT_COLOR_BLUE;
5709 else if (r == 1)
5710 col_str = XT_COLOR_YELLOW;
5711 else
5712 col_str = XT_COLOR_RED;
5713 goto done;
5715 done:
5716 if (col_str) {
5717 gdk_color_parse(col_str, &color);
5718 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5720 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5721 gtk_widget_modify_text(t->sbe.statusbar,
5722 GTK_STATE_NORMAL, &color);
5723 gtk_widget_modify_text(t->sbe.position,
5724 GTK_STATE_NORMAL, &color);
5725 gdk_color_parse(XT_COLOR_BLACK, &color);
5726 gtk_widget_modify_base(t->sbe.statusbar,
5727 GTK_STATE_NORMAL, &color);
5728 gtk_widget_modify_base(t->sbe.position,
5729 GTK_STATE_NORMAL, &color);
5730 } else {
5731 gtk_widget_modify_base(t->sbe.statusbar,
5732 GTK_STATE_NORMAL, &color);
5733 gtk_widget_modify_base(t->sbe.position,
5734 GTK_STATE_NORMAL, &color);
5735 gdk_color_parse(XT_COLOR_BLACK, &color);
5736 gtk_widget_modify_text(t->sbe.statusbar,
5737 GTK_STATE_NORMAL, &color);
5738 gtk_widget_modify_text(t->sbe.position,
5739 GTK_STATE_NORMAL, &color);
5744 void
5745 free_favicon(struct tab *t)
5747 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5748 __func__, t->icon_download, t->icon_request);
5750 if (t->icon_request)
5751 g_object_unref(t->icon_request);
5752 if (t->icon_dest_uri)
5753 g_free(t->icon_dest_uri);
5755 t->icon_request = NULL;
5756 t->icon_dest_uri = NULL;
5759 void
5760 xt_icon_from_name(struct tab *t, gchar *name)
5762 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5763 GTK_ENTRY_ICON_PRIMARY, "text-html");
5764 if (show_url == 0)
5765 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5766 GTK_ENTRY_ICON_PRIMARY, "text-html");
5767 else
5768 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5769 GTK_ENTRY_ICON_PRIMARY, NULL);
5772 void
5773 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5775 GdkPixbuf *pb_scaled;
5777 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5778 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5779 GDK_INTERP_BILINEAR);
5780 else
5781 pb_scaled = pb;
5783 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5784 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5785 if (show_url == 0)
5786 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5787 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5788 else
5789 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5790 GTK_ENTRY_ICON_PRIMARY, NULL);
5792 if (pb_scaled != pb)
5793 g_object_unref(pb_scaled);
5796 void
5797 xt_icon_from_file(struct tab *t, char *file)
5799 GdkPixbuf *pb;
5801 if (g_str_has_prefix(file, "file://"))
5802 file += strlen("file://");
5804 pb = gdk_pixbuf_new_from_file(file, NULL);
5805 if (pb) {
5806 xt_icon_from_pixbuf(t, pb);
5807 g_object_unref(pb);
5808 } else
5809 xt_icon_from_name(t, "text-html");
5812 gboolean
5813 is_valid_icon(char *file)
5815 gboolean valid = 0;
5816 const char *mime_type;
5817 GFileInfo *fi;
5818 GFile *gf;
5820 gf = g_file_new_for_path(file);
5821 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5822 NULL, NULL);
5823 mime_type = g_file_info_get_content_type(fi);
5824 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5825 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5826 g_strcmp0(mime_type, "image/png") == 0 ||
5827 g_strcmp0(mime_type, "image/gif") == 0 ||
5828 g_strcmp0(mime_type, "application/octet-stream") == 0;
5829 g_object_unref(fi);
5830 g_object_unref(gf);
5832 return (valid);
5835 void
5836 set_favicon_from_file(struct tab *t, char *file)
5838 struct stat sb;
5840 if (t == NULL || file == NULL)
5841 return;
5843 if (g_str_has_prefix(file, "file://"))
5844 file += strlen("file://");
5845 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5847 if (!stat(file, &sb)) {
5848 if (sb.st_size == 0 || !is_valid_icon(file)) {
5849 /* corrupt icon so trash it */
5850 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5851 __func__, file);
5852 unlink(file);
5853 /* no need to set icon to default here */
5854 return;
5857 xt_icon_from_file(t, file);
5860 void
5861 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5862 WebKitWebView *wv)
5864 WebKitDownloadStatus status = webkit_download_get_status(download);
5865 struct tab *tt = NULL, *t = NULL;
5868 * find the webview instead of passing in the tab as it could have been
5869 * deleted from underneath us.
5871 TAILQ_FOREACH(tt, &tabs, entry) {
5872 if (tt->wv == wv) {
5873 t = tt;
5874 break;
5877 if (t == NULL)
5878 return;
5880 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5881 __func__, t->tab_id, status);
5883 switch (status) {
5884 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5885 /* -1 */
5886 t->icon_download = NULL;
5887 free_favicon(t);
5888 break;
5889 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5890 /* 0 */
5891 break;
5892 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5893 /* 1 */
5894 break;
5895 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5896 /* 2 */
5897 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5898 __func__, t->tab_id);
5899 t->icon_download = NULL;
5900 free_favicon(t);
5901 break;
5902 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5903 /* 3 */
5905 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5906 __func__, t->icon_dest_uri);
5907 set_favicon_from_file(t, t->icon_dest_uri);
5908 /* these will be freed post callback */
5909 t->icon_request = NULL;
5910 t->icon_download = NULL;
5911 break;
5912 default:
5913 break;
5917 void
5918 abort_favicon_download(struct tab *t)
5920 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5922 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5923 if (t->icon_download) {
5924 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5925 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5926 webkit_download_cancel(t->icon_download);
5927 t->icon_download = NULL;
5928 } else
5929 free_favicon(t);
5932 xt_icon_from_name(t, "text-html");
5935 void
5936 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5938 GdkPixbuf *pb;
5939 gchar *name_hash, file[PATH_MAX];
5940 struct stat sb;
5942 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5944 if (uri == NULL || t == NULL)
5945 return;
5947 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5948 /* take icon from WebKitIconDatabase */
5949 pb = webkit_web_view_get_icon_pixbuf(wv);
5950 if (pb) {
5951 xt_icon_from_pixbuf(t, pb);
5952 g_object_unref(pb);
5953 } else
5954 xt_icon_from_name(t, "text-html");
5956 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5957 /* download icon to cache dir */
5958 if (t->icon_request) {
5959 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5960 return;
5963 /* check to see if we got the icon in cache */
5964 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5965 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5966 g_free(name_hash);
5968 if (!stat(file, &sb)) {
5969 if (sb.st_size > 0) {
5970 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5971 __func__, file);
5972 set_favicon_from_file(t, file);
5973 return;
5976 /* corrupt icon so trash it */
5977 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5978 __func__, file);
5979 unlink(file);
5982 /* create download for icon */
5983 t->icon_request = webkit_network_request_new(uri);
5984 if (t->icon_request == NULL) {
5985 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5986 __func__, uri);
5987 return;
5990 t->icon_download = webkit_download_new(t->icon_request);
5991 if (t->icon_download == NULL)
5992 return;
5994 /* we have to free icon_dest_uri later */
5995 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5996 webkit_download_set_destination_uri(t->icon_download,
5997 t->icon_dest_uri);
5999 if (webkit_download_get_status(t->icon_download) ==
6000 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6001 g_object_unref(t->icon_request);
6002 g_free(t->icon_dest_uri);
6003 t->icon_request = NULL;
6004 t->icon_dest_uri = NULL;
6005 return;
6008 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6009 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6011 webkit_download_start(t->icon_download);
6015 void
6016 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6018 const gchar *set = NULL, *uri = NULL, *title = NULL;
6019 struct history *h, find;
6020 const gchar *s_loading;
6021 struct karg a;
6023 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6024 webkit_web_view_get_load_status(wview), get_uri(t) ? get_uri(t) : "NOTHING");
6026 if (t == NULL) {
6027 show_oops(NULL, "notify_load_status_cb invalid parameters");
6028 return;
6031 switch (webkit_web_view_get_load_status(wview)) {
6032 case WEBKIT_LOAD_PROVISIONAL:
6033 /* 0 */
6034 abort_favicon_download(t);
6035 #if GTK_CHECK_VERSION(2, 20, 0)
6036 gtk_widget_show(t->spinner);
6037 gtk_spinner_start(GTK_SPINNER(t->spinner));
6038 #endif
6039 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6041 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6043 /* take focus if we are visible */
6044 focus_webview(t);
6045 t->focus_wv = 1;
6047 break;
6049 case WEBKIT_LOAD_COMMITTED:
6050 /* 1 */
6051 if ((uri = get_uri(t)) != NULL) {
6052 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6054 if (t->status) {
6055 g_free(t->status);
6056 t->status = NULL;
6058 set_status(t, (char *)uri, XT_STATUS_LOADING);
6061 /* check if js white listing is enabled */
6062 if (enable_js_whitelist) {
6063 uri = get_uri(t);
6064 check_and_set_js(uri, t);
6067 if (t->styled)
6068 apply_style(t);
6070 show_ca_status(t, uri);
6072 /* we know enough to autosave the session */
6073 if (session_autosave) {
6074 a.s = NULL;
6075 save_tabs(t, &a);
6077 break;
6079 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6080 /* 3 */
6081 break;
6083 case WEBKIT_LOAD_FINISHED:
6084 /* 2 */
6085 uri = get_uri(t);
6086 if (uri == NULL)
6087 return;
6089 if (!strncmp(uri, "http://", strlen("http://")) ||
6090 !strncmp(uri, "https://", strlen("https://")) ||
6091 !strncmp(uri, "file://", strlen("file://"))) {
6092 find.uri = uri;
6093 h = RB_FIND(history_list, &hl, &find);
6094 if (!h) {
6095 title = webkit_web_view_get_title(wview);
6096 set = title ? title: uri;
6097 h = g_malloc(sizeof *h);
6098 h->uri = g_strdup(uri);
6099 h->title = g_strdup(set);
6100 RB_INSERT(history_list, &hl, h);
6101 completion_add_uri(h->uri);
6102 update_history_tabs(NULL);
6106 set_status(t, (char *)uri, XT_STATUS_URI);
6107 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6108 case WEBKIT_LOAD_FAILED:
6109 /* 4 */
6110 #endif
6111 #if GTK_CHECK_VERSION(2, 20, 0)
6112 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6113 gtk_widget_hide(t->spinner);
6114 #endif
6115 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
6116 if (s_loading && !strcmp(s_loading, "Loading"))
6117 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6118 default:
6119 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6120 break;
6123 if (t->item)
6124 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6125 else
6126 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6127 webkit_web_view_can_go_back(wview));
6129 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6130 webkit_web_view_can_go_forward(wview));
6133 void
6134 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6136 const gchar *set = NULL, *title = NULL;
6138 title = webkit_web_view_get_title(wview);
6139 set = title ? title : get_uri(t);
6140 if (set) {
6141 gtk_label_set_text(GTK_LABEL(t->label), set);
6142 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), set);
6143 gtk_window_set_title(GTK_WINDOW(main_window), set);
6144 } else {
6145 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6146 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), "(untitled)");
6147 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6151 void
6152 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6154 run_script(t, JS_HINTING);
6157 void
6158 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6160 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6161 progress == 100 ? 0 : (double)progress / 100);
6162 if (show_url == 0) {
6163 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6164 progress == 100 ? 0 : (double)progress / 100);
6167 update_statusbar_position(NULL, NULL);
6171 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6172 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6173 WebKitWebPolicyDecision *pd, struct tab *t)
6175 char *uri;
6176 WebKitWebNavigationReason reason;
6177 struct domain *d = NULL;
6179 if (t == NULL) {
6180 show_oops(NULL, "webview_npd_cb invalid parameters");
6181 return (FALSE);
6184 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6185 t->ctrl_click,
6186 webkit_network_request_get_uri(request));
6188 uri = (char *)webkit_network_request_get_uri(request);
6190 /* if this is an xtp url, we don't load anything else */
6191 if (parse_xtp_url(t, uri))
6192 return (TRUE);
6194 if (t->ctrl_click) {
6195 t->ctrl_click = 0;
6196 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6197 webkit_web_policy_decision_ignore(pd);
6198 return (TRUE); /* we made the decission */
6202 * This is a little hairy but it comes down to this:
6203 * when we run in whitelist mode we have to assist the browser in
6204 * opening the URL that it would have opened in a new tab.
6206 reason = webkit_web_navigation_action_get_reason(na);
6207 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6208 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6209 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6210 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6211 load_uri(t, uri);
6212 webkit_web_policy_decision_use(pd);
6213 return (TRUE); /* we made the decision */
6216 return (FALSE);
6219 WebKitWebView *
6220 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6222 struct tab *tt;
6223 struct domain *d = NULL;
6224 const gchar *uri;
6225 WebKitWebView *webview = NULL;
6227 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6228 webkit_web_view_get_uri(wv));
6230 if (tabless) {
6231 /* open in current tab */
6232 webview = t->wv;
6233 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6234 uri = webkit_web_view_get_uri(wv);
6235 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6236 return (NULL);
6238 tt = create_new_tab(NULL, NULL, 1, -1);
6239 webview = tt->wv;
6240 } else if (enable_scripts == 1) {
6241 tt = create_new_tab(NULL, NULL, 1, -1);
6242 webview = tt->wv;
6245 return (webview);
6248 gboolean
6249 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6251 const gchar *uri;
6252 struct domain *d = NULL;
6254 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6256 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6257 uri = webkit_web_view_get_uri(wv);
6258 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6259 return (FALSE);
6261 delete_tab(t);
6262 } else if (enable_scripts == 1)
6263 delete_tab(t);
6265 return (TRUE);
6269 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6271 /* we can not eat the event without throwing gtk off so defer it */
6273 /* catch middle click */
6274 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6275 t->ctrl_click = 1;
6276 goto done;
6279 /* catch ctrl click */
6280 if (e->type == GDK_BUTTON_RELEASE &&
6281 CLEAN(e->state) == GDK_CONTROL_MASK)
6282 t->ctrl_click = 1;
6283 else
6284 t->ctrl_click = 0;
6285 done:
6286 return (XT_CB_PASSTHROUGH);
6290 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6292 struct mime_type *m;
6294 m = find_mime_type(mime_type);
6295 if (m == NULL)
6296 return (1);
6297 if (m->mt_download)
6298 return (1);
6300 switch (fork()) {
6301 case -1:
6302 show_oops(t, "can't fork mime handler");
6303 /* NOTREACHED */
6304 case 0:
6305 break;
6306 default:
6307 return (0);
6310 /* child */
6311 execlp(m->mt_action, m->mt_action,
6312 webkit_network_request_get_uri(request), (void *)NULL);
6314 _exit(0);
6316 /* NOTREACHED */
6317 return (0);
6320 const gchar *
6321 get_mime_type(char *file)
6323 const char *mime_type;
6324 GFileInfo *fi;
6325 GFile *gf;
6327 if (g_str_has_prefix(file, "file://"))
6328 file += strlen("file://");
6330 gf = g_file_new_for_path(file);
6331 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6332 NULL, NULL);
6333 mime_type = g_file_info_get_content_type(fi);
6334 g_object_unref(fi);
6335 g_object_unref(gf);
6337 return (mime_type);
6341 run_download_mimehandler(char *mime_type, char *file)
6343 struct mime_type *m;
6345 m = find_mime_type(mime_type);
6346 if (m == NULL)
6347 return (1);
6349 switch (fork()) {
6350 case -1:
6351 show_oops(NULL, "can't fork download mime handler");
6352 return (1);
6353 /* NOTREACHED */
6354 case 0:
6355 break;
6356 default:
6357 return (0);
6360 /* child */
6361 if (g_str_has_prefix(file, "file://"))
6362 file += strlen("file://");
6363 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6365 _exit(0);
6367 /* NOTREACHED */
6368 return (0);
6371 void
6372 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6373 WebKitWebView *wv)
6375 WebKitDownloadStatus status;
6376 const gchar *file = NULL, *mime = NULL;
6378 if (download == NULL)
6379 return;
6380 status = webkit_download_get_status(download);
6381 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6382 return;
6384 file = webkit_download_get_destination_uri(download);
6385 if (file == NULL)
6386 return;
6387 mime = get_mime_type((char *)file);
6388 if (mime == NULL)
6389 return;
6391 run_download_mimehandler((char *)mime, (char *)file);
6395 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6396 WebKitNetworkRequest *request, char *mime_type,
6397 WebKitWebPolicyDecision *decision, struct tab *t)
6399 if (t == NULL) {
6400 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6401 return (FALSE);
6404 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6405 t->tab_id, mime_type);
6407 if (run_mimehandler(t, mime_type, request) == 0) {
6408 webkit_web_policy_decision_ignore(decision);
6409 focus_webview(t);
6410 return (TRUE);
6413 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6414 webkit_web_policy_decision_download(decision);
6415 return (TRUE);
6418 return (FALSE);
6422 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6423 struct tab *t)
6425 struct stat sb;
6426 const gchar *suggested_name;
6427 gchar *filename = NULL;
6428 char *uri = NULL;
6429 struct download *download_entry;
6430 int i, ret = TRUE;
6432 if (wk_download == NULL || t == NULL) {
6433 show_oops(NULL, "%s invalid parameters", __func__);
6434 return (FALSE);
6437 suggested_name = webkit_download_get_suggested_filename(wk_download);
6438 if (suggested_name == NULL)
6439 return (FALSE); /* abort download */
6441 i = 0;
6442 do {
6443 if (filename) {
6444 g_free(filename);
6445 filename = NULL;
6447 if (i) {
6448 g_free(uri);
6449 uri = NULL;
6450 filename = g_strdup_printf("%d%s", i, suggested_name);
6452 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6453 filename : suggested_name);
6454 i++;
6455 } while (!stat(uri + strlen("file://"), &sb));
6457 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6458 "local %s\n", __func__, t->tab_id, filename, uri);
6460 webkit_download_set_destination_uri(wk_download, uri);
6462 if (webkit_download_get_status(wk_download) ==
6463 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6464 show_oops(t, "%s: download failed to start", __func__);
6465 ret = FALSE;
6466 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6467 } else {
6468 /* connect "download first" mime handler */
6469 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6470 G_CALLBACK(download_status_changed_cb), NULL);
6472 download_entry = g_malloc(sizeof(struct download));
6473 download_entry->download = wk_download;
6474 download_entry->tab = t;
6475 download_entry->id = next_download_id++;
6476 RB_INSERT(download_list, &downloads, download_entry);
6477 /* get from history */
6478 g_object_ref(wk_download);
6479 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6480 show_oops(t, "Download of '%s' started...",
6481 basename((char *)webkit_download_get_destination_uri(wk_download)));
6484 if (uri)
6485 g_free(uri);
6487 if (filename)
6488 g_free(filename);
6490 /* sync other download manager tabs */
6491 update_download_tabs(NULL);
6494 * NOTE: never redirect/render the current tab before this
6495 * function returns. This will cause the download to never start.
6497 return (ret); /* start download */
6500 void
6501 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6503 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6505 if (t == NULL) {
6506 show_oops(NULL, "webview_hover_cb");
6507 return;
6510 if (uri)
6511 set_status(t, uri, XT_STATUS_LINK);
6512 else {
6513 if (t->status)
6514 set_status(t, t->status, XT_STATUS_NOTHING);
6518 gboolean
6519 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6521 struct key_binding *k;
6523 TAILQ_FOREACH(k, &kbl, entry)
6524 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6525 if (k->mask == 0) {
6526 if ((e->state & (CTRL | MOD1)) == 0)
6527 return (cmd_execute(t, k->cmd));
6528 } else if ((e->state & k->mask) == k->mask) {
6529 return (cmd_execute(t, k->cmd));
6533 return (XT_CB_PASSTHROUGH);
6537 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6539 char s[2], buf[128];
6540 const char *errstr = NULL;
6541 long long link;
6543 /* don't use w directly; use t->whatever instead */
6545 if (t == NULL) {
6546 show_oops(NULL, "wv_keypress_after_cb");
6547 return (XT_CB_PASSTHROUGH);
6550 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6551 e->keyval, e->state, t);
6553 if (t->hints_on) {
6554 /* ESC */
6555 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6556 disable_hints(t);
6557 return (XT_CB_HANDLED);
6560 /* RETURN */
6561 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6562 link = strtonum(t->hint_num, 1, 1000, &errstr);
6563 if (errstr) {
6564 /* we have a string */
6565 } else {
6566 /* we have a number */
6567 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6568 t->hint_num);
6569 run_script(t, buf);
6571 disable_hints(t);
6574 /* BACKSPACE */
6575 /* XXX unfuck this */
6576 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6577 if (t->hint_mode == XT_HINT_NUMERICAL) {
6578 /* last input was numerical */
6579 int l;
6580 l = strlen(t->hint_num);
6581 if (l > 0) {
6582 l--;
6583 if (l == 0) {
6584 disable_hints(t);
6585 enable_hints(t);
6586 } else {
6587 t->hint_num[l] = '\0';
6588 goto num;
6591 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6592 /* last input was alphanumerical */
6593 int l;
6594 l = strlen(t->hint_buf);
6595 if (l > 0) {
6596 l--;
6597 if (l == 0) {
6598 disable_hints(t);
6599 enable_hints(t);
6600 } else {
6601 t->hint_buf[l] = '\0';
6602 goto anum;
6605 } else {
6606 /* bogus */
6607 disable_hints(t);
6611 /* numerical input */
6612 if (CLEAN(e->state) == 0 &&
6613 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6614 snprintf(s, sizeof s, "%c", e->keyval);
6615 strlcat(t->hint_num, s, sizeof t->hint_num);
6616 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6617 t->hint_num);
6618 num:
6619 link = strtonum(t->hint_num, 1, 1000, &errstr);
6620 if (errstr) {
6621 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6622 disable_hints(t);
6623 } else {
6624 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6625 t->hint_num);
6626 t->hint_mode = XT_HINT_NUMERICAL;
6627 run_script(t, buf);
6630 /* empty the counter buffer */
6631 bzero(t->hint_buf, sizeof t->hint_buf);
6632 return (XT_CB_HANDLED);
6635 /* alphanumerical input */
6636 if (
6637 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6638 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6639 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6640 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6641 snprintf(s, sizeof s, "%c", e->keyval);
6642 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6643 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6644 t->hint_buf);
6645 anum:
6646 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6647 run_script(t, buf);
6649 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6650 t->hint_buf);
6651 t->hint_mode = XT_HINT_ALPHANUM;
6652 run_script(t, buf);
6654 /* empty the counter buffer */
6655 bzero(t->hint_num, sizeof t->hint_num);
6656 return (XT_CB_HANDLED);
6659 return (XT_CB_HANDLED);
6660 } else {
6661 /* prefix input*/
6662 snprintf(s, sizeof s, "%c", e->keyval);
6663 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6664 cmd_prefix = 10 * cmd_prefix + atoi(s);
6668 return (handle_keypress(t, e, 0));
6672 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6674 hide_oops(t);
6676 /* Hide buffers, if they are visible, with escape. */
6677 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6678 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6679 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6680 hide_buffers(t);
6681 return (XT_CB_HANDLED);
6684 return (XT_CB_PASSTHROUGH);
6688 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6690 const gchar *c = gtk_entry_get_text(w);
6691 GdkColor color;
6692 int forward = TRUE;
6694 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6695 e->keyval, e->state, t);
6697 if (t == NULL) {
6698 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6699 return (XT_CB_PASSTHROUGH);
6702 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6703 e->keyval, e->state, t);
6705 if (c[0] == ':')
6706 goto done;
6707 if (strlen(c) == 1) {
6708 webkit_web_view_unmark_text_matches(t->wv);
6709 goto done;
6712 if (c[0] == '/')
6713 forward = TRUE;
6714 else if (c[0] == '?')
6715 forward = FALSE;
6716 else
6717 goto done;
6719 /* search */
6720 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6721 FALSE) {
6722 /* not found, mark red */
6723 gdk_color_parse(XT_COLOR_RED, &color);
6724 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6725 /* unmark and remove selection */
6726 webkit_web_view_unmark_text_matches(t->wv);
6727 /* my kingdom for a way to unselect text in webview */
6728 } else {
6729 /* found, highlight all */
6730 webkit_web_view_unmark_text_matches(t->wv);
6731 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6732 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6733 gdk_color_parse(XT_COLOR_WHITE, &color);
6734 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6736 done:
6737 return (XT_CB_PASSTHROUGH);
6740 gboolean
6741 match_uri(const gchar *uri, const gchar *key) {
6742 gchar *voffset;
6743 size_t len;
6744 gboolean match = FALSE;
6746 len = strlen(key);
6748 if (!strncmp(key, uri, len))
6749 match = TRUE;
6750 else {
6751 voffset = strstr(uri, "/") + 2;
6752 if (!strncmp(key, voffset, len))
6753 match = TRUE;
6754 else if (g_str_has_prefix(voffset, "www.")) {
6755 voffset = voffset + strlen("www.");
6756 if (!strncmp(key, voffset, len))
6757 match = TRUE;
6761 return (match);
6764 void
6765 cmd_getlist(int id, char *key)
6767 int i, dep, c = 0;
6768 struct history *h;
6770 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6771 RB_FOREACH_REVERSE(h, history_list, &hl)
6772 if (match_uri(h->uri, key)) {
6773 cmd_status.list[c] = (char *)h->uri;
6774 if (++c > 255)
6775 break;
6778 cmd_status.len = c;
6779 return;
6782 dep = (id == -1) ? 0 : cmds[id].level + 1;
6784 for (i = id + 1; i < LENGTH(cmds); i++) {
6785 if(cmds[i].level < dep)
6786 break;
6787 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6788 cmd_status.list[c++] = cmds[i].cmd;
6792 cmd_status.len = c;
6795 char *
6796 cmd_getnext(int dir)
6798 cmd_status.index += dir;
6800 if (cmd_status.index < 0)
6801 cmd_status.index = cmd_status.len - 1;
6802 else if (cmd_status.index >= cmd_status.len)
6803 cmd_status.index = 0;
6805 return cmd_status.list[cmd_status.index];
6809 cmd_tokenize(char *s, char *tokens[])
6811 int i = 0;
6812 char *tok, *last;
6813 size_t len = strlen(s);
6814 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6816 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6817 tokens[i] = tok;
6819 if (blank && i < 3)
6820 tokens[i++] = "";
6822 return (i);
6825 void
6826 cmd_complete(struct tab *t, char *str, int dir)
6828 GtkEntry *w = GTK_ENTRY(t->cmd);
6829 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6830 char *tok, *match, *s = g_strdup(str);
6831 char *tokens[3];
6832 char res[XT_MAX_URL_LENGTH + 32] = ":";
6833 char *sc = s;
6835 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6837 /* copy prefix*/
6838 for (i = 0; isdigit(s[i]); i++)
6839 res[i + 1] = s[i];
6841 for (; isspace(s[i]); i++)
6842 res[i + 1] = s[i];
6844 s += i;
6846 levels = cmd_tokenize(s, tokens);
6848 for (i = 0; i < levels - 1; i++) {
6849 tok = tokens[i];
6850 matchcount = 0;
6851 for (j = c; j < LENGTH(cmds); j++) {
6852 if (cmds[j].level < dep)
6853 break;
6854 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6855 matchcount++;
6856 c = j + 1;
6857 if (strlen(tok) == strlen(cmds[j].cmd)) {
6858 matchcount = 1;
6859 break;
6864 if (matchcount == 1) {
6865 strlcat(res, tok, sizeof res);
6866 strlcat(res, " ", sizeof res);
6867 dep++;
6868 } else {
6869 g_free(sc);
6870 return;
6873 parent = c - 1;
6876 if (cmd_status.index == -1)
6877 cmd_getlist(parent, tokens[i]);
6879 if (cmd_status.len > 0) {
6880 match = cmd_getnext(dir);
6881 strlcat(res, match, sizeof res);
6882 gtk_entry_set_text(w, res);
6883 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6886 g_free(sc);
6889 gboolean
6890 cmd_execute(struct tab *t, char *str)
6892 struct cmd *cmd = NULL;
6893 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6894 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6895 struct karg arg = {0, NULL, -1};
6896 int rv = XT_CB_PASSTHROUGH;
6898 sc = s;
6900 /* copy prefix*/
6901 for (j = 0; j<3 && isdigit(s[j]); j++)
6902 prefixstr[j]=s[j];
6904 prefixstr[j]='\0';
6906 s += j;
6907 while (isspace(s[0]))
6908 s++;
6910 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6911 prefix = atoi(prefixstr);
6912 else
6913 s = sc;
6915 for (tok = strtok_r(s, " ", &last); tok;
6916 tok = strtok_r(NULL, " ", &last)) {
6917 matchcount = 0;
6918 for (j = c; j < LENGTH(cmds); j++) {
6919 if (cmds[j].level < dep)
6920 break;
6921 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6922 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6923 matchcount++;
6924 c = j + 1;
6925 cmd = &cmds[j];
6926 if (len == strlen(cmds[j].cmd)) {
6927 matchcount = 1;
6928 break;
6932 if (matchcount == 1) {
6933 if (cmd->type > 0)
6934 goto execute_cmd;
6935 dep++;
6936 } else {
6937 show_oops(t, "Invalid command: %s", str);
6938 goto done;
6941 execute_cmd:
6942 arg.i = cmd->arg;
6944 if (prefix != -1)
6945 arg.p = prefix;
6946 else if (cmd_prefix > 0)
6947 arg.p = cmd_prefix;
6949 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6950 show_oops(t, "No prefix allowed: %s", str);
6951 goto done;
6953 if (cmd->type > 1)
6954 arg.s = last ? g_strdup(last) : g_strdup("");
6955 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6956 arg.p = atoi(arg.s);
6957 if (arg.p <= 0) {
6958 if (arg.s[0]=='0')
6959 show_oops(t, "Zero count");
6960 else
6961 show_oops(t, "Trailing characters");
6962 goto done;
6966 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6968 cmd->func(t, &arg);
6970 rv = XT_CB_HANDLED;
6971 done:
6972 if (j > 0)
6973 cmd_prefix = 0;
6974 g_free(sc);
6975 if (arg.s)
6976 g_free(arg.s);
6978 return (rv);
6982 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6984 if (t == NULL) {
6985 show_oops(NULL, "entry_key_cb invalid parameters");
6986 return (XT_CB_PASSTHROUGH);
6989 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6990 e->keyval, e->state, t);
6992 hide_oops(t);
6994 if (e->keyval == GDK_Escape) {
6995 /* don't use focus_webview(t) because we want to type :cmds */
6996 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6999 return (handle_keypress(t, e, 1));
7003 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7005 int rv = XT_CB_HANDLED;
7006 const gchar *c = gtk_entry_get_text(w);
7008 if (t == NULL) {
7009 show_oops(NULL, "cmd_keypress_cb parameters");
7010 return (XT_CB_PASSTHROUGH);
7013 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7014 e->keyval, e->state, t);
7016 /* sanity */
7017 if (c == NULL)
7018 e->keyval = GDK_Escape;
7019 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7020 e->keyval = GDK_Escape;
7022 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
7023 cmd_status.index = -1;
7025 switch (e->keyval) {
7026 case GDK_Tab:
7027 if (c[0] == ':')
7028 cmd_complete(t, (char *)&c[1], 1);
7029 goto done;
7030 case GDK_ISO_Left_Tab:
7031 if (c[0] == ':')
7032 cmd_complete(t, (char *)&c[1], -1);
7034 goto done;
7035 case GDK_BackSpace:
7036 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7037 break;
7038 /* FALLTHROUGH */
7039 case GDK_Escape:
7040 hide_cmd(t);
7041 focus_webview(t);
7043 /* cancel search */
7044 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7045 webkit_web_view_unmark_text_matches(t->wv);
7046 goto done;
7049 rv = XT_CB_PASSTHROUGH;
7050 done:
7051 return (rv);
7055 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7057 if (t == NULL) {
7058 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7059 return (XT_CB_PASSTHROUGH);
7061 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7063 hide_cmd(t);
7064 hide_oops(t);
7066 if (show_url == 0 || t->focus_wv)
7067 focus_webview(t);
7068 else
7069 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7071 return (XT_CB_PASSTHROUGH);
7074 void
7075 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7077 char *s;
7078 const gchar *c = gtk_entry_get_text(entry);
7080 if (t == NULL) {
7081 show_oops(NULL, "cmd_activate_cb invalid parameters");
7082 return;
7085 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7087 hide_cmd(t);
7089 /* sanity */
7090 if (c == NULL)
7091 goto done;
7092 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7093 goto done;
7094 if (strlen(c) < 2)
7095 goto done;
7096 s = (char *)&c[1];
7098 if (c[0] == '/' || c[0] == '?') {
7099 if (t->search_text) {
7100 g_free(t->search_text);
7101 t->search_text = NULL;
7104 t->search_text = g_strdup(s);
7105 if (global_search)
7106 g_free(global_search);
7107 global_search = g_strdup(s);
7108 t->search_forward = c[0] == '/';
7110 goto done;
7113 cmd_execute(t, s);
7115 done:
7116 return;
7119 void
7120 backward_cb(GtkWidget *w, struct tab *t)
7122 struct karg a;
7124 if (t == NULL) {
7125 show_oops(NULL, "backward_cb invalid parameters");
7126 return;
7129 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7131 a.i = XT_NAV_BACK;
7132 navaction(t, &a);
7135 void
7136 forward_cb(GtkWidget *w, struct tab *t)
7138 struct karg a;
7140 if (t == NULL) {
7141 show_oops(NULL, "forward_cb invalid parameters");
7142 return;
7145 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7147 a.i = XT_NAV_FORWARD;
7148 navaction(t, &a);
7151 void
7152 home_cb(GtkWidget *w, struct tab *t)
7154 if (t == NULL) {
7155 show_oops(NULL, "home_cb invalid parameters");
7156 return;
7159 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7161 load_uri(t, home);
7164 void
7165 stop_cb(GtkWidget *w, struct tab *t)
7167 WebKitWebFrame *frame;
7169 if (t == NULL) {
7170 show_oops(NULL, "stop_cb invalid parameters");
7171 return;
7174 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7176 frame = webkit_web_view_get_main_frame(t->wv);
7177 if (frame == NULL) {
7178 show_oops(t, "stop_cb: no frame");
7179 return;
7182 webkit_web_frame_stop_loading(frame);
7183 abort_favicon_download(t);
7186 void
7187 setup_webkit(struct tab *t)
7189 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7190 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7191 FALSE, (char *)NULL);
7192 else
7193 warnx("webkit does not have \"enable-dns-prefetching\" property");
7194 g_object_set(G_OBJECT(t->settings),
7195 "user-agent", t->user_agent, (char *)NULL);
7196 g_object_set(G_OBJECT(t->settings),
7197 "enable-scripts", enable_scripts, (char *)NULL);
7198 g_object_set(G_OBJECT(t->settings),
7199 "enable-plugins", enable_plugins, (char *)NULL);
7200 g_object_set(G_OBJECT(t->settings),
7201 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7202 g_object_set(G_OBJECT(t->settings),
7203 "enable-html5-database", FALSE, (char *)NULL);
7204 g_object_set(G_OBJECT(t->settings),
7205 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7206 g_object_set(G_OBJECT(t->settings),
7207 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7208 g_object_set(G_OBJECT(t->settings),
7209 "spell_checking_languages", spell_check_languages, (char *)NULL);
7210 g_object_set(G_OBJECT(t->wv),
7211 "full-content-zoom", TRUE, (char *)NULL);
7212 adjustfont_webkit(t, XT_FONT_SET);
7214 webkit_web_view_set_settings(t->wv, t->settings);
7217 gboolean
7218 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7220 struct tab *ti, *t = NULL;
7221 gdouble view_size, value, max;
7222 gchar *position;
7224 TAILQ_FOREACH(ti, &tabs, entry)
7225 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7226 t = ti;
7227 break;
7230 if (t == NULL)
7231 return FALSE;
7233 if (adjustment == NULL)
7234 adjustment = gtk_scrolled_window_get_vadjustment(
7235 GTK_SCROLLED_WINDOW(t->browser_win));
7237 view_size = gtk_adjustment_get_page_size(adjustment);
7238 value = gtk_adjustment_get_value(adjustment);
7239 max = gtk_adjustment_get_upper(adjustment) - view_size;
7241 if (max == 0)
7242 position = g_strdup("All");
7243 else if (value == max)
7244 position = g_strdup("Bot");
7245 else if (value == 0)
7246 position = g_strdup("Top");
7247 else
7248 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7250 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7251 g_free(position);
7253 return (TRUE);
7256 GtkWidget *
7257 create_browser(struct tab *t)
7259 GtkWidget *w;
7260 gchar *strval;
7261 GtkAdjustment *adjustment;
7263 if (t == NULL) {
7264 show_oops(NULL, "create_browser invalid parameters");
7265 return (NULL);
7268 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7269 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7270 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7271 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7273 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7274 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7275 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7277 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7278 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7280 /* set defaults */
7281 t->settings = webkit_web_settings_new();
7283 if (user_agent == NULL) {
7284 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7285 (char *)NULL);
7286 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7287 g_free(strval);
7288 } else
7289 t->user_agent = g_strdup(user_agent);
7291 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7293 adjustment =
7294 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7295 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7296 G_CALLBACK(update_statusbar_position), NULL);
7298 setup_webkit(t);
7300 return (w);
7303 GtkWidget *
7304 create_window(void)
7306 GtkWidget *w;
7308 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7309 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7310 gtk_widget_set_name(w, "xxxterm");
7311 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7312 g_signal_connect(G_OBJECT(w), "delete_event",
7313 G_CALLBACK (gtk_main_quit), NULL);
7315 return (w);
7318 GtkWidget *
7319 create_kiosk_toolbar(struct tab *t)
7321 GtkWidget *toolbar = NULL, *b;
7323 b = gtk_hbox_new(FALSE, 0);
7324 toolbar = b;
7325 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7327 /* backward button */
7328 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7329 gtk_widget_set_sensitive(t->backward, FALSE);
7330 g_signal_connect(G_OBJECT(t->backward), "clicked",
7331 G_CALLBACK(backward_cb), t);
7332 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7334 /* forward button */
7335 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7336 gtk_widget_set_sensitive(t->forward, FALSE);
7337 g_signal_connect(G_OBJECT(t->forward), "clicked",
7338 G_CALLBACK(forward_cb), t);
7339 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7341 /* home button */
7342 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7343 gtk_widget_set_sensitive(t->gohome, true);
7344 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7345 G_CALLBACK(home_cb), t);
7346 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7348 /* create widgets but don't use them */
7349 t->uri_entry = gtk_entry_new();
7350 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7351 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7352 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7354 return (toolbar);
7357 GtkWidget *
7358 create_toolbar(struct tab *t)
7360 GtkWidget *toolbar = NULL, *b, *eb1;
7362 b = gtk_hbox_new(FALSE, 0);
7363 toolbar = b;
7364 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7366 if (fancy_bar) {
7367 /* backward button */
7368 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7369 gtk_widget_set_sensitive(t->backward, FALSE);
7370 g_signal_connect(G_OBJECT(t->backward), "clicked",
7371 G_CALLBACK(backward_cb), t);
7372 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7374 /* forward button */
7375 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7376 gtk_widget_set_sensitive(t->forward, FALSE);
7377 g_signal_connect(G_OBJECT(t->forward), "clicked",
7378 G_CALLBACK(forward_cb), t);
7379 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7380 FALSE, 0);
7382 /* stop button */
7383 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7384 gtk_widget_set_sensitive(t->stop, FALSE);
7385 g_signal_connect(G_OBJECT(t->stop), "clicked",
7386 G_CALLBACK(stop_cb), t);
7387 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7388 FALSE, 0);
7390 /* JS button */
7391 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7392 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7393 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7394 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7395 G_CALLBACK(js_toggle_cb), t);
7396 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7399 t->uri_entry = gtk_entry_new();
7400 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7401 G_CALLBACK(activate_uri_entry_cb), t);
7402 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7403 G_CALLBACK(entry_key_cb), t);
7404 completion_add(t);
7405 eb1 = gtk_hbox_new(FALSE, 0);
7406 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7407 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7408 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7410 /* search entry */
7411 if (fancy_bar && search_string) {
7412 GtkWidget *eb2;
7413 t->search_entry = gtk_entry_new();
7414 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7415 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7416 G_CALLBACK(activate_search_entry_cb), t);
7417 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7418 G_CALLBACK(entry_key_cb), t);
7419 gtk_widget_set_size_request(t->search_entry, -1, -1);
7420 eb2 = gtk_hbox_new(FALSE, 0);
7421 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7422 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7424 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7426 return (toolbar);
7429 GtkWidget *
7430 create_buffers(struct tab *t)
7432 GtkCellRenderer *renderer;
7433 GtkWidget *view;
7435 view = gtk_tree_view_new();
7437 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7439 renderer = gtk_cell_renderer_text_new();
7440 gtk_tree_view_insert_column_with_attributes
7441 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7443 renderer = gtk_cell_renderer_text_new();
7444 gtk_tree_view_insert_column_with_attributes
7445 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE, NULL);
7447 gtk_tree_view_set_model
7448 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7450 return view;
7453 void
7454 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7455 GtkTreeViewColumn *col, struct tab *t)
7457 GtkTreeIter iter;
7458 guint id;
7460 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7462 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path)) {
7463 gtk_tree_model_get
7464 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7465 set_current_tab(id - 1);
7468 hide_buffers(t);
7471 /* after tab reordering/creation/removal */
7472 void
7473 recalc_tabs(void)
7475 struct tab *t;
7476 int maxid = 0;
7477 int curid = 0;
7479 TAILQ_FOREACH(t, &tabs, entry) {
7480 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7481 if (t->tab_id > maxid)
7482 maxid = t->tab_id;
7484 gtk_widget_show(t->tab_elems.sep);
7487 curid = gtk_notebook_get_current_page(notebook);
7488 TAILQ_FOREACH(t, &tabs, entry) {
7489 if (t->tab_id == maxid) {
7490 gtk_widget_hide(t->tab_elems.sep);
7491 break;
7496 /* after active tab change */
7497 void
7498 recolor_compact_tabs(void)
7500 struct tab *t;
7501 int curid = 0;
7502 GdkColor color;
7504 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7505 TAILQ_FOREACH(t, &tabs, entry)
7506 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7508 curid = gtk_notebook_get_current_page(notebook);
7509 TAILQ_FOREACH(t, &tabs, entry)
7510 if (t->tab_id == curid) {
7511 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7512 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7513 break;
7517 void
7518 set_current_tab(int page_num)
7520 gtk_notebook_set_current_page(notebook, page_num);
7521 recolor_compact_tabs();
7525 undo_close_tab_save(struct tab *t)
7527 int m, n;
7528 const gchar *uri;
7529 struct undo *u1, *u2;
7530 GList *items;
7531 WebKitWebHistoryItem *item;
7533 if ((uri = get_uri(t)) == NULL)
7534 return (1);
7536 u1 = g_malloc0(sizeof(struct undo));
7537 u1->uri = g_strdup(uri);
7539 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7541 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7542 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7543 u1->back = n;
7545 /* forward history */
7546 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7548 while (items) {
7549 item = items->data;
7550 u1->history = g_list_prepend(u1->history,
7551 webkit_web_history_item_copy(item));
7552 items = g_list_next(items);
7555 /* current item */
7556 if (m) {
7557 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7558 u1->history = g_list_prepend(u1->history,
7559 webkit_web_history_item_copy(item));
7562 /* back history */
7563 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7565 while (items) {
7566 item = items->data;
7567 u1->history = g_list_prepend(u1->history,
7568 webkit_web_history_item_copy(item));
7569 items = g_list_next(items);
7572 TAILQ_INSERT_HEAD(&undos, u1, entry);
7574 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7575 u2 = TAILQ_LAST(&undos, undo_tailq);
7576 TAILQ_REMOVE(&undos, u2, entry);
7577 g_free(u2->uri);
7578 g_list_free(u2->history);
7579 g_free(u2);
7580 } else
7581 undo_count++;
7583 return (0);
7586 void
7587 delete_tab(struct tab *t)
7589 struct karg a;
7591 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7593 if (t == NULL)
7594 return;
7596 TAILQ_REMOVE(&tabs, t, entry);
7598 /* Halt all webkit activity. */
7599 abort_favicon_download(t);
7600 webkit_web_view_stop_loading(t->wv);
7602 /* Save the tab, so we can undo the close. */
7603 undo_close_tab_save(t);
7605 if (browser_mode == XT_BM_KIOSK) {
7606 gtk_widget_destroy(t->uri_entry);
7607 gtk_widget_destroy(t->stop);
7608 gtk_widget_destroy(t->js_toggle);
7611 gtk_widget_destroy(t->tab_elems.eventbox);
7612 gtk_widget_destroy(t->vbox);
7614 g_free(t->user_agent);
7615 g_free(t->stylesheet);
7616 g_free(t);
7618 if (TAILQ_EMPTY(&tabs)) {
7619 if (browser_mode == XT_BM_KIOSK)
7620 create_new_tab(home, NULL, 1, -1);
7621 else
7622 create_new_tab(NULL, NULL, 1, -1);
7625 /* recreate session */
7626 if (session_autosave) {
7627 a.s = NULL;
7628 save_tabs(t, &a);
7631 recalc_tabs();
7632 recolor_compact_tabs();
7635 void
7636 adjustfont_webkit(struct tab *t, int adjust)
7638 gfloat zoom;
7640 if (t == NULL) {
7641 show_oops(NULL, "adjustfont_webkit invalid parameters");
7642 return;
7645 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7646 if (adjust == XT_FONT_SET) {
7647 t->font_size = default_font_size;
7648 zoom = default_zoom_level;
7649 t->font_size += adjust;
7650 g_object_set(G_OBJECT(t->settings), "default-font-size",
7651 t->font_size, (char *)NULL);
7652 g_object_get(G_OBJECT(t->settings), "default-font-size",
7653 &t->font_size, (char *)NULL);
7654 } else {
7655 t->font_size += adjust;
7656 zoom += adjust/25.0;
7657 if (zoom < 0.0) {
7658 zoom = 0.04;
7661 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7662 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7665 gboolean
7666 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
7668 struct tab *t = (struct tab *) data;
7670 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
7672 switch (event->button) {
7673 case 1:
7674 set_current_tab(t->tab_id);
7675 break;
7676 case 2:
7677 delete_tab(t);
7678 break;
7681 return TRUE;
7684 gboolean
7685 page_reordered_cb(GtkWidget *nb, GtkWidget *eventbox, guint pn, gpointer data)
7687 struct tab *t = (struct tab *) data;
7689 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
7691 recalc_tabs();
7692 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
7693 t->tab_id);
7695 return TRUE;
7698 void
7699 append_tab(struct tab *t)
7701 if (t == NULL)
7702 return;
7704 TAILQ_INSERT_TAIL(&tabs, t, entry);
7705 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7708 struct tab *
7709 create_new_tab(char *title, struct undo *u, int focus, int position)
7711 struct tab *t;
7712 int load = 1, id;
7713 GtkWidget *b, *bb;
7714 WebKitWebHistoryItem *item;
7715 GList *items;
7716 GdkColor color;
7718 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7720 if (tabless && !TAILQ_EMPTY(&tabs)) {
7721 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7722 return (NULL);
7725 t = g_malloc0(sizeof *t);
7727 if (title == NULL) {
7728 title = "(untitled)";
7729 load = 0;
7732 t->vbox = gtk_vbox_new(FALSE, 0);
7734 /* label + button for tab */
7735 b = gtk_hbox_new(FALSE, 0);
7736 t->tab_content = b;
7738 #if GTK_CHECK_VERSION(2, 20, 0)
7739 t->spinner = gtk_spinner_new();
7740 #endif
7741 t->label = gtk_label_new(title);
7742 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7743 gtk_widget_set_size_request(t->label, 100, 0);
7744 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7745 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7746 gtk_widget_set_size_request(b, 130, 0);
7748 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7749 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7750 #if GTK_CHECK_VERSION(2, 20, 0)
7751 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7752 #endif
7754 /* toolbar */
7755 if (browser_mode == XT_BM_KIOSK)
7756 t->toolbar = create_kiosk_toolbar(t);
7757 else
7758 t->toolbar = create_toolbar(t);
7760 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7762 /* browser */
7763 t->browser_win = create_browser(t);
7764 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7766 /* oops message for user feedback */
7767 t->oops = gtk_entry_new();
7768 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7769 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7770 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7771 gdk_color_parse(XT_COLOR_RED, &color);
7772 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7773 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7775 /* command entry */
7776 t->cmd = gtk_entry_new();
7777 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7778 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7779 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7780 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7782 /* status bar */
7783 t->statusbar_box = gtk_hbox_new(FALSE, 0);
7785 t->sbe.statusbar = gtk_entry_new();
7786 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
7787 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
7788 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
7790 t->sbe.position = gtk_entry_new();
7791 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.position), NULL);
7792 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.position), FALSE);
7793 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.position), FALSE);
7795 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.position), 1.0);
7796 gtk_widget_set_size_request(t->sbe.position, 40, -1);
7798 gdk_color_parse(XT_COLOR_BLACK, &color);
7799 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
7800 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &color);
7801 gdk_color_parse(XT_COLOR_WHITE, &color);
7802 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
7803 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &color);
7805 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
7806 TRUE, FALSE);
7807 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.position, FALSE,
7808 FALSE, FALSE);
7810 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
7812 /* buffer list */
7813 t->buffers = create_buffers(t);
7814 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
7816 /* xtp meaning is normal by default */
7817 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7819 /* set empty favicon */
7820 xt_icon_from_name(t, "text-html");
7822 /* and show it all */
7823 gtk_widget_show_all(b);
7824 gtk_widget_show_all(t->vbox);
7826 /* compact tab bar */
7827 t->tab_elems.label = gtk_label_new(title);
7828 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
7829 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
7830 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
7832 t->tab_elems.eventbox = gtk_event_box_new();
7833 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
7834 t->tab_elems.sep = gtk_vseparator_new();
7836 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
7837 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
7838 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7839 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7840 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
7841 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
7843 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
7844 TRUE, 0);
7845 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
7846 FALSE, 0);
7847 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
7848 t->tab_elems.box);
7850 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
7851 TRUE, 0);
7852 gtk_widget_show_all(t->tab_elems.eventbox);
7854 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7855 append_tab(t);
7856 else {
7857 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7858 if (id > gtk_notebook_get_n_pages(notebook))
7859 append_tab(t);
7860 else {
7861 TAILQ_INSERT_TAIL(&tabs, t, entry);
7862 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7863 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, id);
7864 recalc_tabs();
7868 #if GTK_CHECK_VERSION(2, 20, 0)
7869 /* turn spinner off if we are a new tab without uri */
7870 if (!load) {
7871 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7872 gtk_widget_hide(t->spinner);
7874 #endif
7875 /* make notebook tabs reorderable */
7876 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7878 /* compact tabs clickable */
7879 g_signal_connect(GTK_OBJECT(t->tab_elems.eventbox),
7880 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
7882 g_signal_connect(GTK_OBJECT(notebook),
7883 "page_reordered", G_CALLBACK(page_reordered_cb), t);
7885 g_object_connect(G_OBJECT(t->cmd),
7886 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7887 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7888 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7889 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7890 (char *)NULL);
7892 /* reuse wv_button_cb to hide oops */
7893 g_object_connect(G_OBJECT(t->oops),
7894 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7895 (char *)NULL);
7897 g_signal_connect(t->buffers,
7898 "row-activated", G_CALLBACK(row_activated_cb), t);
7899 g_object_connect(G_OBJECT(t->buffers),
7900 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
7902 g_object_connect(G_OBJECT(t->wv),
7903 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7904 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7905 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7906 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7907 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7908 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7909 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7910 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7911 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7912 "signal::event", G_CALLBACK(webview_event_cb), t,
7913 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7914 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7915 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7916 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7917 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
7918 (char *)NULL);
7919 g_signal_connect(t->wv,
7920 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7921 g_signal_connect(t->wv,
7922 "notify::title", G_CALLBACK(notify_title_cb), t);
7924 /* hijack the unused keys as if we were the browser */
7925 g_object_connect(G_OBJECT(t->toolbar),
7926 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7927 (char *)NULL);
7929 g_signal_connect(G_OBJECT(bb), "button_press_event",
7930 G_CALLBACK(tab_close_cb), t);
7932 /* hide stuff */
7933 hide_cmd(t);
7934 hide_oops(t);
7935 hide_buffers(t);
7936 url_set_visibility();
7937 statusbar_set_visibility();
7939 if (focus) {
7940 set_current_tab(t->tab_id);
7941 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7942 t->tab_id);
7945 if (load) {
7946 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7947 load_uri(t, title);
7948 } else {
7949 if (show_url == 1)
7950 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7951 else
7952 focus_webview(t);
7955 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7956 /* restore the tab's history */
7957 if (u && u->history) {
7958 items = u->history;
7959 while (items) {
7960 item = items->data;
7961 webkit_web_back_forward_list_add_item(t->bfl, item);
7962 items = g_list_next(items);
7965 item = g_list_nth_data(u->history, u->back);
7966 if (item)
7967 webkit_web_view_go_to_back_forward_item(t->wv, item);
7969 g_list_free(items);
7970 g_list_free(u->history);
7971 } else
7972 webkit_web_back_forward_list_clear(t->bfl);
7974 recalc_tabs();
7975 recolor_compact_tabs();
7976 return (t);
7979 void
7980 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7981 gpointer *udata)
7983 struct tab *t;
7984 const gchar *uri;
7986 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7988 if (gtk_notebook_get_current_page(notebook) == -1)
7989 recalc_tabs();
7991 TAILQ_FOREACH(t, &tabs, entry) {
7992 if (t->tab_id == pn) {
7993 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7994 "%d\n", pn);
7996 uri = webkit_web_view_get_title(t->wv);
7997 if (uri == NULL)
7998 uri = XT_NAME;
7999 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8001 hide_cmd(t);
8002 hide_oops(t);
8004 if (t->focus_wv) {
8005 /* can't use focus_webview here */
8006 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8012 void
8013 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8014 gpointer *udata)
8016 recalc_tabs();
8019 void
8020 menuitem_response(struct tab *t)
8022 gtk_notebook_set_current_page(notebook, t->tab_id);
8025 gboolean
8026 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8028 GtkWidget *menu, *menu_items;
8029 GdkEventButton *bevent;
8030 const gchar *uri;
8031 struct tab *ti;
8033 if (event->type == GDK_BUTTON_PRESS) {
8034 bevent = (GdkEventButton *) event;
8035 menu = gtk_menu_new();
8037 TAILQ_FOREACH(ti, &tabs, entry) {
8038 if ((uri = get_uri(ti)) == NULL)
8039 /* XXX make sure there is something to print */
8040 /* XXX add gui pages in here to look purdy */
8041 uri = "(untitled)";
8042 menu_items = gtk_menu_item_new_with_label(uri);
8043 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8044 gtk_widget_show(menu_items);
8046 g_signal_connect_swapped((menu_items),
8047 "activate", G_CALLBACK(menuitem_response),
8048 (gpointer)ti);
8051 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8052 bevent->button, bevent->time);
8054 /* unref object so it'll free itself when popped down */
8055 #if !GTK_CHECK_VERSION(3, 0, 0)
8056 /* XXX does not need unref with gtk+3? */
8057 g_object_ref_sink(menu);
8058 g_object_unref(menu);
8059 #endif
8061 return (TRUE /* eat event */);
8064 return (FALSE /* propagate */);
8068 icon_size_map(int icon_size)
8070 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8071 icon_size > GTK_ICON_SIZE_DIALOG)
8072 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8074 return (icon_size);
8077 GtkWidget *
8078 create_button(char *name, char *stockid, int size)
8080 GtkWidget *button, *image;
8081 gchar *rcstring;
8082 int gtk_icon_size;
8084 rcstring = g_strdup_printf(
8085 "style \"%s-style\"\n"
8086 "{\n"
8087 " GtkWidget::focus-padding = 0\n"
8088 " GtkWidget::focus-line-width = 0\n"
8089 " xthickness = 0\n"
8090 " ythickness = 0\n"
8091 "}\n"
8092 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8093 gtk_rc_parse_string(rcstring);
8094 g_free(rcstring);
8095 button = gtk_button_new();
8096 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8097 gtk_icon_size = icon_size_map(size ? size : icon_size);
8099 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8100 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8101 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8102 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8103 gtk_widget_set_name(button, name);
8104 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8106 return (button);
8109 void
8110 button_set_stockid(GtkWidget *button, char *stockid)
8112 GtkWidget *image;
8114 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8115 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8116 gtk_button_set_image(GTK_BUTTON(button), image);
8119 void
8120 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8122 GtkClipboard *clipboard;
8123 gchar *p = NULL, *s = NULL;
8126 * This code is very aggressive!
8127 * It basically ensures that the primary and regular clipboard are
8128 * always set the same. This obviously messes with standard X protocol
8129 * but those clowns should have come up with something better.
8132 if (btn_down)
8133 return;
8135 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8136 p = gtk_clipboard_wait_for_text(primary);
8137 if (p == NULL) {
8138 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
8139 p = gtk_clipboard_wait_for_text(clipboard);
8140 if (p)
8141 gtk_clipboard_set_text(primary, p, -1);
8142 } else {
8143 DNPRINTF(XT_D_CLIP, "primary got selection\n");
8144 s = gtk_clipboard_wait_for_text(clipboard);
8145 if (s) {
8147 * if s and p are the same the string was set by
8148 * clipb_clipboard_cb so do nothing in that case
8149 * to prevent endless loop
8151 if (!strcmp(s, p))
8152 goto done;
8154 gtk_clipboard_set_text(clipboard, p, -1);
8156 done:
8157 if (p)
8158 g_free(p);
8159 if (s)
8160 g_free(s);
8163 void
8164 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
8166 GtkClipboard *primary;
8167 gchar *p = NULL, *s = NULL;
8169 if (btn_down)
8170 return;
8172 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
8174 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8175 p = gtk_clipboard_wait_for_text(clipboard);
8176 if (p) {
8177 s = gtk_clipboard_wait_for_text(primary);
8178 if (s) {
8180 * if s and p are the same the string was set by
8181 * clipb_primary_cb so do nothing in that case
8182 * to prevent endless loop and deselection of text
8184 if (!strcmp(s, p))
8185 goto done;
8187 gtk_clipboard_set_text(primary, p, -1);
8189 done:
8190 if (p)
8191 g_free(p);
8192 if (s)
8193 g_free(s);
8196 void
8197 create_canvas(void)
8199 GtkWidget *vbox;
8200 GList *l = NULL;
8201 GdkPixbuf *pb;
8202 char file[PATH_MAX];
8203 int i;
8205 vbox = gtk_vbox_new(FALSE, 0);
8206 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8207 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8208 #if !GTK_CHECK_VERSION(3, 0, 0)
8209 /* XXX seems to be needed with gtk+2 */
8210 gtk_notebook_set_tab_hborder(notebook, 0);
8211 gtk_notebook_set_tab_vborder(notebook, 0);
8212 #endif
8213 gtk_notebook_set_scrollable(notebook, TRUE);
8214 gtk_notebook_set_show_border(notebook, FALSE);
8215 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8217 abtn = gtk_button_new();
8218 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8219 gtk_widget_set_size_request(arrow, -1, -1);
8220 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8221 gtk_widget_set_size_request(abtn, -1, 20);
8223 #if GTK_CHECK_VERSION(2, 20, 0)
8224 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8225 #endif
8226 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8228 /* compact tab bar */
8229 tab_bar = gtk_hbox_new(TRUE, 0);
8231 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8232 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8233 gtk_widget_set_size_request(vbox, -1, -1);
8235 g_object_connect(G_OBJECT(notebook),
8236 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8237 (char *)NULL);
8238 g_object_connect(G_OBJECT(notebook),
8239 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
8240 (char *)NULL);
8241 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8242 G_CALLBACK(arrow_cb), NULL);
8244 main_window = create_window();
8245 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8246 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
8248 /* icons */
8249 for (i = 0; i < LENGTH(icons); i++) {
8250 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8251 pb = gdk_pixbuf_new_from_file(file, NULL);
8252 l = g_list_append(l, pb);
8254 gtk_window_set_default_icon_list(l);
8256 /* clipboard */
8257 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8258 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8259 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
8260 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
8262 gtk_widget_show_all(abtn);
8263 gtk_widget_show_all(main_window);
8264 notebook_tab_set_visibility();
8267 void
8268 set_hook(void **hook, char *name)
8270 if (hook == NULL)
8271 errx(1, "set_hook");
8273 if (*hook == NULL) {
8274 *hook = dlsym(RTLD_NEXT, name);
8275 if (*hook == NULL)
8276 errx(1, "can't hook %s", name);
8280 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8281 gboolean
8282 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8284 g_return_val_if_fail(cookie1, FALSE);
8285 g_return_val_if_fail(cookie2, FALSE);
8287 return (!strcmp (cookie1->name, cookie2->name) &&
8288 !strcmp (cookie1->value, cookie2->value) &&
8289 !strcmp (cookie1->path, cookie2->path) &&
8290 !strcmp (cookie1->domain, cookie2->domain));
8293 void
8294 transfer_cookies(void)
8296 GSList *cf;
8297 SoupCookie *sc, *pc;
8299 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8301 for (;cf; cf = cf->next) {
8302 pc = cf->data;
8303 sc = soup_cookie_copy(pc);
8304 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8307 soup_cookies_free(cf);
8310 void
8311 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8313 GSList *cf;
8314 SoupCookie *ci;
8316 print_cookie("soup_cookie_jar_delete_cookie", c);
8318 if (cookies_enabled == 0)
8319 return;
8321 if (jar == NULL || c == NULL)
8322 return;
8324 /* find and remove from persistent jar */
8325 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8327 for (;cf; cf = cf->next) {
8328 ci = cf->data;
8329 if (soup_cookie_equal(ci, c)) {
8330 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8331 break;
8335 soup_cookies_free(cf);
8337 /* delete from session jar */
8338 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8341 void
8342 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8344 struct domain *d = NULL;
8345 SoupCookie *c;
8346 FILE *r_cookie_f;
8348 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8349 jar, p_cookiejar, s_cookiejar);
8351 if (cookies_enabled == 0)
8352 return;
8354 /* see if we are up and running */
8355 if (p_cookiejar == NULL) {
8356 _soup_cookie_jar_add_cookie(jar, cookie);
8357 return;
8359 /* disallow p_cookiejar adds, shouldn't happen */
8360 if (jar == p_cookiejar)
8361 return;
8363 /* sanity */
8364 if (jar == NULL || cookie == NULL)
8365 return;
8367 if (enable_cookie_whitelist &&
8368 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8369 blocked_cookies++;
8370 DNPRINTF(XT_D_COOKIE,
8371 "soup_cookie_jar_add_cookie: reject %s\n",
8372 cookie->domain);
8373 if (save_rejected_cookies) {
8374 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8375 show_oops(NULL, "can't open reject cookie file");
8376 return;
8378 fseek(r_cookie_f, 0, SEEK_END);
8379 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8380 cookie->http_only ? "#HttpOnly_" : "",
8381 cookie->domain,
8382 *cookie->domain == '.' ? "TRUE" : "FALSE",
8383 cookie->path,
8384 cookie->secure ? "TRUE" : "FALSE",
8385 cookie->expires ?
8386 (gulong)soup_date_to_time_t(cookie->expires) :
8388 cookie->name,
8389 cookie->value);
8390 fflush(r_cookie_f);
8391 fclose(r_cookie_f);
8393 if (!allow_volatile_cookies)
8394 return;
8397 if (cookie->expires == NULL && session_timeout) {
8398 soup_cookie_set_expires(cookie,
8399 soup_date_new_from_now(session_timeout));
8400 print_cookie("modified add cookie", cookie);
8403 /* see if we are white listed for persistence */
8404 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8405 /* add to persistent jar */
8406 c = soup_cookie_copy(cookie);
8407 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8408 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8411 /* add to session jar */
8412 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8413 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8416 void
8417 setup_cookies(void)
8419 char file[PATH_MAX];
8421 set_hook((void *)&_soup_cookie_jar_add_cookie,
8422 "soup_cookie_jar_add_cookie");
8423 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8424 "soup_cookie_jar_delete_cookie");
8426 if (cookies_enabled == 0)
8427 return;
8430 * the following code is intricate due to overriding several libsoup
8431 * functions.
8432 * do not alter order of these operations.
8435 /* rejected cookies */
8436 if (save_rejected_cookies)
8437 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8439 /* persistent cookies */
8440 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8441 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8443 /* session cookies */
8444 s_cookiejar = soup_cookie_jar_new();
8445 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8446 cookie_policy, (void *)NULL);
8447 transfer_cookies();
8449 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8452 void
8453 setup_proxy(char *uri)
8455 if (proxy_uri) {
8456 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8457 soup_uri_free(proxy_uri);
8458 proxy_uri = NULL;
8460 if (http_proxy) {
8461 if (http_proxy != uri) {
8462 g_free(http_proxy);
8463 http_proxy = NULL;
8467 if (uri) {
8468 http_proxy = g_strdup(uri);
8469 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8470 proxy_uri = soup_uri_new(http_proxy);
8471 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8476 send_cmd_to_socket(char *cmd)
8478 int s, len, rv = 1;
8479 struct sockaddr_un sa;
8481 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8482 warnx("%s: socket", __func__);
8483 return (rv);
8486 sa.sun_family = AF_UNIX;
8487 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8488 work_dir, XT_SOCKET_FILE);
8489 len = SUN_LEN(&sa);
8491 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8492 warnx("%s: connect", __func__);
8493 goto done;
8496 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8497 warnx("%s: send", __func__);
8498 goto done;
8501 rv = 0;
8502 done:
8503 close(s);
8504 return (rv);
8507 gboolean
8508 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8510 int s, n;
8511 char str[XT_MAX_URL_LENGTH];
8512 socklen_t t = sizeof(struct sockaddr_un);
8513 struct sockaddr_un sa;
8514 struct passwd *p;
8515 uid_t uid;
8516 gid_t gid;
8517 struct tab *tt;
8518 gint fd = g_io_channel_unix_get_fd(source);
8520 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8521 warn("accept");
8522 return (FALSE);
8525 if (getpeereid(s, &uid, &gid) == -1) {
8526 warn("getpeereid");
8527 return (FALSE);
8529 if (uid != getuid() || gid != getgid()) {
8530 warnx("unauthorized user");
8531 return (FALSE);
8534 p = getpwuid(uid);
8535 if (p == NULL) {
8536 warnx("not a valid user");
8537 return (FALSE);
8540 n = recv(s, str, sizeof(str), 0);
8541 if (n <= 0)
8542 return (FALSE);
8544 tt = TAILQ_LAST(&tabs, tab_list);
8545 cmd_execute(tt, str);
8546 return (TRUE);
8550 is_running(void)
8552 int s, len, rv = 1;
8553 struct sockaddr_un sa;
8555 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8556 warn("is_running: socket");
8557 return (-1);
8560 sa.sun_family = AF_UNIX;
8561 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8562 work_dir, XT_SOCKET_FILE);
8563 len = SUN_LEN(&sa);
8565 /* connect to see if there is a listener */
8566 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8567 rv = 0; /* not running */
8568 else
8569 rv = 1; /* already running */
8571 close(s);
8573 return (rv);
8577 build_socket(void)
8579 int s, len;
8580 struct sockaddr_un sa;
8582 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8583 warn("build_socket: socket");
8584 return (-1);
8587 sa.sun_family = AF_UNIX;
8588 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8589 work_dir, XT_SOCKET_FILE);
8590 len = SUN_LEN(&sa);
8592 /* connect to see if there is a listener */
8593 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8594 /* no listener so we will */
8595 unlink(sa.sun_path);
8597 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8598 warn("build_socket: bind");
8599 goto done;
8602 if (listen(s, 1) == -1) {
8603 warn("build_socket: listen");
8604 goto done;
8607 return (s);
8610 done:
8611 close(s);
8612 return (-1);
8615 gboolean
8616 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8617 GtkTreeIter *iter, struct tab *t)
8619 gchar *value;
8621 gtk_tree_model_get(model, iter, 0, &value, -1);
8622 load_uri(t, value);
8623 g_free(value);
8625 return (FALSE);
8628 gboolean
8629 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8630 GtkTreeIter *iter, struct tab *t)
8632 gchar *value;
8634 gtk_tree_model_get(model, iter, 0, &value, -1);
8635 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8636 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8637 g_free(value);
8639 return (TRUE);
8642 void
8643 completion_add_uri(const gchar *uri)
8645 GtkTreeIter iter;
8647 /* add uri to list_store */
8648 gtk_list_store_append(completion_model, &iter);
8649 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8652 gboolean
8653 completion_match(GtkEntryCompletion *completion, const gchar *key,
8654 GtkTreeIter *iter, gpointer user_data)
8656 gchar *value;
8657 gboolean match = FALSE;
8659 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8660 -1);
8662 if (value == NULL)
8663 return FALSE;
8665 match = match_uri(value, key);
8667 g_free(value);
8668 return (match);
8671 void
8672 completion_add(struct tab *t)
8674 /* enable completion for tab */
8675 t->completion = gtk_entry_completion_new();
8676 gtk_entry_completion_set_text_column(t->completion, 0);
8677 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8678 gtk_entry_completion_set_model(t->completion,
8679 GTK_TREE_MODEL(completion_model));
8680 gtk_entry_completion_set_match_func(t->completion, completion_match,
8681 NULL, NULL);
8682 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8683 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8684 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8685 G_CALLBACK(completion_select_cb), t);
8686 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8687 G_CALLBACK(completion_hover_cb), t);
8690 void
8691 xxx_dir(char *dir)
8693 struct stat sb;
8695 if (stat(dir, &sb)) {
8696 if (mkdir(dir, S_IRWXU) == -1)
8697 err(1, "mkdir %s", dir);
8698 if (stat(dir, &sb))
8699 err(1, "stat %s", dir);
8701 if (S_ISDIR(sb.st_mode) == 0)
8702 errx(1, "%s not a dir", dir);
8703 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8704 warnx("fixing invalid permissions on %s", dir);
8705 if (chmod(dir, S_IRWXU) == -1)
8706 err(1, "chmod %s", dir);
8710 void
8711 usage(void)
8713 fprintf(stderr,
8714 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8715 exit(0);
8720 main(int argc, char *argv[])
8722 struct stat sb;
8723 int c, s, optn = 0, opte = 0, focus = 1;
8724 char conf[PATH_MAX] = { '\0' };
8725 char file[PATH_MAX];
8726 char *env_proxy = NULL;
8727 FILE *f = NULL;
8728 struct karg a;
8729 struct sigaction sact;
8730 GIOChannel *channel;
8731 struct rlimit rlp;
8733 start_argv = argv;
8735 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8737 /* fiddle with ulimits */
8738 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8739 warn("getrlimit");
8740 else {
8741 /* just use them all */
8742 rlp.rlim_cur = rlp.rlim_max;
8743 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8744 warn("setrlimit");
8745 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8746 warn("getrlimit");
8747 else if (rlp.rlim_cur <= 256)
8748 warnx("%s requires at least 256 file descriptors",
8749 __progname);
8752 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8753 switch (c) {
8754 case 'S':
8755 show_url = 0;
8756 break;
8757 case 'T':
8758 show_tabs = 0;
8759 break;
8760 case 'V':
8761 errx(0 , "Version: %s", version);
8762 break;
8763 case 'f':
8764 strlcpy(conf, optarg, sizeof(conf));
8765 break;
8766 case 's':
8767 strlcpy(named_session, optarg, sizeof(named_session));
8768 break;
8769 case 't':
8770 tabless = 1;
8771 break;
8772 case 'n':
8773 optn = 1;
8774 break;
8775 case 'e':
8776 opte = 1;
8777 break;
8778 default:
8779 usage();
8780 /* NOTREACHED */
8783 argc -= optind;
8784 argv += optind;
8786 RB_INIT(&hl);
8787 RB_INIT(&js_wl);
8788 RB_INIT(&downloads);
8790 TAILQ_INIT(&tabs);
8791 TAILQ_INIT(&mtl);
8792 TAILQ_INIT(&aliases);
8793 TAILQ_INIT(&undos);
8794 TAILQ_INIT(&kbl);
8796 init_keybindings();
8798 gnutls_global_init();
8800 /* generate session keys for xtp pages */
8801 generate_xtp_session_key(&dl_session_key);
8802 generate_xtp_session_key(&hl_session_key);
8803 generate_xtp_session_key(&cl_session_key);
8804 generate_xtp_session_key(&fl_session_key);
8806 /* prepare gtk */
8807 gtk_init(&argc, &argv);
8808 if (!g_thread_supported())
8809 g_thread_init(NULL);
8811 /* signals */
8812 bzero(&sact, sizeof(sact));
8813 sigemptyset(&sact.sa_mask);
8814 sact.sa_handler = sigchild;
8815 sact.sa_flags = SA_NOCLDSTOP;
8816 sigaction(SIGCHLD, &sact, NULL);
8818 /* set download dir */
8819 pwd = getpwuid(getuid());
8820 if (pwd == NULL)
8821 errx(1, "invalid user %d", getuid());
8822 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8824 /* set default string settings */
8825 home = g_strdup("https://www.cyphertite.com");
8826 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8827 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8828 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
8829 cmd_font_name = g_strdup("monospace normal 9");
8830 statusbar_font_name = g_strdup("monospace normal 9");
8833 /* read config file */
8834 if (strlen(conf) == 0)
8835 snprintf(conf, sizeof conf, "%s/.%s",
8836 pwd->pw_dir, XT_CONF_FILE);
8837 config_parse(conf, 0);
8839 /* init fonts */
8840 cmd_font = pango_font_description_from_string(cmd_font_name);
8841 statusbar_font = pango_font_description_from_string(statusbar_font_name);
8843 /* working directory */
8844 if (strlen(work_dir) == 0)
8845 snprintf(work_dir, sizeof work_dir, "%s/%s",
8846 pwd->pw_dir, XT_DIR);
8847 xxx_dir(work_dir);
8849 /* icon cache dir */
8850 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8851 xxx_dir(cache_dir);
8853 /* certs dir */
8854 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8855 xxx_dir(certs_dir);
8857 /* sessions dir */
8858 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8859 work_dir, XT_SESSIONS_DIR);
8860 xxx_dir(sessions_dir);
8862 /* runtime settings that can override config file */
8863 if (runtime_settings[0] != '\0')
8864 config_parse(runtime_settings, 1);
8866 /* download dir */
8867 if (!strcmp(download_dir, pwd->pw_dir))
8868 strlcat(download_dir, "/downloads", sizeof download_dir);
8869 xxx_dir(download_dir);
8871 /* favorites file */
8872 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8873 if (stat(file, &sb)) {
8874 warnx("favorites file doesn't exist, creating it");
8875 if ((f = fopen(file, "w")) == NULL)
8876 err(1, "favorites");
8877 fclose(f);
8880 /* cookies */
8881 session = webkit_get_default_session();
8882 setup_cookies();
8884 /* certs */
8885 if (ssl_ca_file) {
8886 if (stat(ssl_ca_file, &sb)) {
8887 warnx("no CA file: %s", ssl_ca_file);
8888 g_free(ssl_ca_file);
8889 ssl_ca_file = NULL;
8890 } else
8891 g_object_set(session,
8892 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8893 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8894 (void *)NULL);
8897 /* proxy */
8898 env_proxy = getenv("http_proxy");
8899 if (env_proxy)
8900 setup_proxy(env_proxy);
8901 else
8902 setup_proxy(http_proxy);
8904 if (opte) {
8905 send_cmd_to_socket(argv[0]);
8906 exit(0);
8909 /* set some connection parameters */
8910 /* XXX webkit 1.4.X overwrites these values! */
8911 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
8912 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8913 g_object_set(session, "max-conns-per-host", max_host_connections,
8914 (char *)NULL);
8916 /* see if there is already an xxxterm running */
8917 if (single_instance && is_running()) {
8918 optn = 1;
8919 warnx("already running");
8922 char *cmd = NULL;
8923 if (optn) {
8924 while (argc) {
8925 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8926 send_cmd_to_socket(cmd);
8927 if (cmd)
8928 g_free(cmd);
8930 argc--;
8931 argv++;
8933 exit(0);
8936 /* uri completion */
8937 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8939 /* buffers */
8940 buffers_store = gtk_list_store_new
8941 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
8943 /* go graphical */
8944 create_canvas();
8945 notebook_tab_set_visibility();
8947 if (save_global_history)
8948 restore_global_history();
8950 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8951 restore_saved_tabs();
8952 else {
8953 a.s = named_session;
8954 a.i = XT_SES_DONOTHING;
8955 open_tabs(NULL, &a);
8958 while (argc) {
8959 create_new_tab(argv[0], NULL, focus, -1);
8960 focus = 0;
8962 argc--;
8963 argv++;
8966 if (TAILQ_EMPTY(&tabs))
8967 create_new_tab(home, NULL, 1, -1);
8969 if (enable_socket)
8970 if ((s = build_socket()) != -1) {
8971 channel = g_io_channel_unix_new(s);
8972 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8975 gtk_main();
8977 gnutls_global_deinit();
8979 return (0);