document compact bar
[xxxterm.git] / xxxterm.c
blob483f808315ab4d4bae88283510b5c2e5603e5322
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;
186 GtkWidget *cmd;
187 GtkWidget *buffers;
188 GtkWidget *oops;
189 GtkWidget *backward;
190 GtkWidget *forward;
191 GtkWidget *stop;
192 GtkWidget *gohome;
193 GtkWidget *js_toggle;
194 GtkEntryCompletion *completion;
195 guint tab_id;
196 WebKitWebView *wv;
198 WebKitWebHistoryItem *item;
199 WebKitWebBackForwardList *bfl;
201 /* favicon */
202 WebKitNetworkRequest *icon_request;
203 WebKitDownload *icon_download;
204 gchar *icon_dest_uri;
206 /* adjustments for browser */
207 GtkScrollbar *sb_h;
208 GtkScrollbar *sb_v;
209 GtkAdjustment *adjust_h;
210 GtkAdjustment *adjust_v;
212 /* flags */
213 int focus_wv;
214 int ctrl_click;
215 gchar *status;
216 int xtp_meaning; /* identifies dls/favorites */
218 /* hints */
219 int hints_on;
220 int hint_mode;
221 #define XT_HINT_NONE (0)
222 #define XT_HINT_NUMERICAL (1)
223 #define XT_HINT_ALPHANUM (2)
224 char hint_buf[128];
225 char hint_num[128];
227 /* custom stylesheet */
228 int styled;
229 char *stylesheet;
231 /* search */
232 char *search_text;
233 int search_forward;
235 /* settings */
236 WebKitWebSettings *settings;
237 int font_size;
238 gchar *user_agent;
240 TAILQ_HEAD(tab_list, tab);
242 struct history {
243 RB_ENTRY(history) entry;
244 const gchar *uri;
245 const gchar *title;
247 RB_HEAD(history_list, history);
249 struct download {
250 RB_ENTRY(download) entry;
251 int id;
252 WebKitDownload *download;
253 struct tab *tab;
255 RB_HEAD(download_list, download);
257 struct domain {
258 RB_ENTRY(domain) entry;
259 gchar *d;
260 int handy; /* app use */
262 RB_HEAD(domain_list, domain);
264 struct undo {
265 TAILQ_ENTRY(undo) entry;
266 gchar *uri;
267 GList *history;
268 int back; /* Keeps track of how many back
269 * history items there are. */
271 TAILQ_HEAD(undo_tailq, undo);
273 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
274 int next_download_id = 1;
276 struct karg {
277 int i;
278 char *s;
279 int p;
282 /* defines */
283 #define XT_NAME ("XXXTerm")
284 #define XT_DIR (".xxxterm")
285 #define XT_CACHE_DIR ("cache")
286 #define XT_CERT_DIR ("certs/")
287 #define XT_SESSIONS_DIR ("sessions/")
288 #define XT_CONF_FILE ("xxxterm.conf")
289 #define XT_FAVS_FILE ("favorites")
290 #define XT_SAVED_TABS_FILE ("main_session")
291 #define XT_RESTART_TABS_FILE ("restart_tabs")
292 #define XT_SOCKET_FILE ("socket")
293 #define XT_HISTORY_FILE ("history")
294 #define XT_REJECT_FILE ("rejected.txt")
295 #define XT_COOKIE_FILE ("cookies.txt")
296 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
297 #define XT_CB_HANDLED (TRUE)
298 #define XT_CB_PASSTHROUGH (FALSE)
299 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
300 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
301 #define XT_DLMAN_REFRESH "10"
302 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
303 "td{overflow: hidden;" \
304 " padding: 2px 2px 2px 2px;" \
305 " border: 1px solid black;" \
306 " vertical-align:top;" \
307 " word-wrap: break-word}\n" \
308 "tr:hover{background: #ffff99}\n" \
309 "th{background-color: #cccccc;" \
310 " border: 1px solid black}\n" \
311 "table{width: 100%%;" \
312 " border: 1px black solid;" \
313 " border-collapse:collapse}\n" \
314 ".progress-outer{" \
315 "border: 1px solid black;" \
316 " height: 8px;" \
317 " width: 90%%}\n" \
318 ".progress-inner{float: left;" \
319 " height: 8px;" \
320 " background: green}\n" \
321 ".dlstatus{font-size: small;" \
322 " text-align: center}\n" \
323 "</style>\n"
324 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
325 #define XT_MAX_UNDO_CLOSE_TAB (32)
326 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
327 #define XT_PRINT_EXTRA_MARGIN 10
329 /* colors */
330 #define XT_COLOR_RED "#cc0000"
331 #define XT_COLOR_YELLOW "#ffff66"
332 #define XT_COLOR_BLUE "lightblue"
333 #define XT_COLOR_GREEN "#99ff66"
334 #define XT_COLOR_WHITE "white"
335 #define XT_COLOR_BLACK "black"
337 #define XT_COLOR_CT_BACKGROUND "#000000"
338 #define XT_COLOR_CT_INACTIVE "#dddddd"
339 #define XT_COLOR_CT_ACTIVE "#bbbb00"
340 #define XT_COLOR_CT_SEPARATOR "#555555"
343 * xxxterm "protocol" (xtp)
344 * We use this for managing stuff like downloads and favorites. They
345 * make magical HTML pages in memory which have xxxt:// links in order
346 * to communicate with xxxterm's internals. These links take the format:
347 * xxxt://class/session_key/action/arg
349 * Don't begin xtp class/actions as 0. atoi returns that on error.
351 * Typically we have not put addition of items in this framework, as
352 * adding items is either done via an ex-command or via a keybinding instead.
355 #define XT_XTP_STR "xxxt://"
357 /* XTP classes (xxxt://<class>) */
358 #define XT_XTP_INVALID 0 /* invalid */
359 #define XT_XTP_DL 1 /* downloads */
360 #define XT_XTP_HL 2 /* history */
361 #define XT_XTP_CL 3 /* cookies */
362 #define XT_XTP_FL 4 /* favorites */
364 /* XTP download actions */
365 #define XT_XTP_DL_LIST 1
366 #define XT_XTP_DL_CANCEL 2
367 #define XT_XTP_DL_REMOVE 3
369 /* XTP history actions */
370 #define XT_XTP_HL_LIST 1
371 #define XT_XTP_HL_REMOVE 2
373 /* XTP cookie actions */
374 #define XT_XTP_CL_LIST 1
375 #define XT_XTP_CL_REMOVE 2
377 /* XTP cookie actions */
378 #define XT_XTP_FL_LIST 1
379 #define XT_XTP_FL_REMOVE 2
381 /* actions */
382 #define XT_MOVE_INVALID (0)
383 #define XT_MOVE_DOWN (1)
384 #define XT_MOVE_UP (2)
385 #define XT_MOVE_BOTTOM (3)
386 #define XT_MOVE_TOP (4)
387 #define XT_MOVE_PAGEDOWN (5)
388 #define XT_MOVE_PAGEUP (6)
389 #define XT_MOVE_HALFDOWN (7)
390 #define XT_MOVE_HALFUP (8)
391 #define XT_MOVE_LEFT (9)
392 #define XT_MOVE_FARLEFT (10)
393 #define XT_MOVE_RIGHT (11)
394 #define XT_MOVE_FARRIGHT (12)
396 #define XT_TAB_LAST (-4)
397 #define XT_TAB_FIRST (-3)
398 #define XT_TAB_PREV (-2)
399 #define XT_TAB_NEXT (-1)
400 #define XT_TAB_INVALID (0)
401 #define XT_TAB_NEW (1)
402 #define XT_TAB_DELETE (2)
403 #define XT_TAB_DELQUIT (3)
404 #define XT_TAB_OPEN (4)
405 #define XT_TAB_UNDO_CLOSE (5)
406 #define XT_TAB_SHOW (6)
407 #define XT_TAB_HIDE (7)
408 #define XT_TAB_NEXTSTYLE (8)
410 #define XT_NAV_INVALID (0)
411 #define XT_NAV_BACK (1)
412 #define XT_NAV_FORWARD (2)
413 #define XT_NAV_RELOAD (3)
414 #define XT_NAV_RELOAD_CACHE (4)
416 #define XT_FOCUS_INVALID (0)
417 #define XT_FOCUS_URI (1)
418 #define XT_FOCUS_SEARCH (2)
420 #define XT_SEARCH_INVALID (0)
421 #define XT_SEARCH_NEXT (1)
422 #define XT_SEARCH_PREV (2)
424 #define XT_PASTE_CURRENT_TAB (0)
425 #define XT_PASTE_NEW_TAB (1)
427 #define XT_FONT_SET (0)
429 #define XT_URL_SHOW (1)
430 #define XT_URL_HIDE (2)
432 #define XT_STATUSBAR_SHOW (1)
433 #define XT_STATUSBAR_HIDE (2)
435 #define XT_WL_TOGGLE (1<<0)
436 #define XT_WL_ENABLE (1<<1)
437 #define XT_WL_DISABLE (1<<2)
438 #define XT_WL_FQDN (1<<3) /* default */
439 #define XT_WL_TOPLEVEL (1<<4)
440 #define XT_WL_PERSISTENT (1<<5)
441 #define XT_WL_SESSION (1<<6)
442 #define XT_WL_RELOAD (1<<7)
444 #define XT_SHOW (1<<7)
445 #define XT_DELETE (1<<8)
446 #define XT_SAVE (1<<9)
447 #define XT_OPEN (1<<10)
449 #define XT_CMD_OPEN (0)
450 #define XT_CMD_OPEN_CURRENT (1)
451 #define XT_CMD_TABNEW (2)
452 #define XT_CMD_TABNEW_CURRENT (3)
454 #define XT_STATUS_NOTHING (0)
455 #define XT_STATUS_LINK (1)
456 #define XT_STATUS_URI (2)
457 #define XT_STATUS_LOADING (3)
459 #define XT_SES_DONOTHING (0)
460 #define XT_SES_CLOSETABS (1)
462 #define XT_BM_NORMAL (0)
463 #define XT_BM_WHITELIST (1)
464 #define XT_BM_KIOSK (2)
466 #define XT_PREFIX (1<<0)
467 #define XT_USERARG (1<<1)
468 #define XT_URLARG (1<<2)
469 #define XT_INTARG (1<<3)
471 #define XT_TABS_NORMAL 0
472 #define XT_TABS_COMPACT 1
474 /* mime types */
475 struct mime_type {
476 char *mt_type;
477 char *mt_action;
478 int mt_default;
479 int mt_download;
480 TAILQ_ENTRY(mime_type) entry;
482 TAILQ_HEAD(mime_type_list, mime_type);
484 /* uri aliases */
485 struct alias {
486 char *a_name;
487 char *a_uri;
488 TAILQ_ENTRY(alias) entry;
490 TAILQ_HEAD(alias_list, alias);
492 /* settings that require restart */
493 int tabless = 0; /* allow only 1 tab */
494 int enable_socket = 0;
495 int single_instance = 0; /* only allow one xxxterm to run */
496 int fancy_bar = 1; /* fancy toolbar */
497 int browser_mode = XT_BM_NORMAL;
498 int enable_localstorage = 0;
500 /* runtime settings */
501 int show_tabs = 1; /* show tabs on notebook */
502 int tab_style = XT_TABS_NORMAL; /* tab bar style */
503 int show_url = 1; /* show url toolbar on notebook */
504 int show_statusbar = 0; /* vimperator style status bar */
505 int ctrl_click_focus = 0; /* ctrl click gets focus */
506 int cookies_enabled = 1; /* enable cookies */
507 int read_only_cookies = 0; /* enable to not write cookies */
508 int enable_scripts = 1;
509 int enable_plugins = 0;
510 int default_font_size = 12;
511 gfloat default_zoom_level = 1.0;
512 int window_height = 768;
513 int window_width = 1024;
514 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
515 int refresh_interval = 10; /* download refresh interval */
516 int enable_cookie_whitelist = 0;
517 int enable_js_whitelist = 0;
518 int session_timeout = 3600; /* cookie session timeout */
519 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
520 char *ssl_ca_file = NULL;
521 char *resource_dir = NULL;
522 gboolean ssl_strict_certs = FALSE;
523 int append_next = 1; /* append tab after current tab */
524 char *home = NULL;
525 char *search_string = NULL;
526 char *http_proxy = NULL;
527 char download_dir[PATH_MAX];
528 char runtime_settings[PATH_MAX]; /* override of settings */
529 int allow_volatile_cookies = 0;
530 int save_global_history = 0; /* save global history to disk */
531 char *user_agent = NULL;
532 int save_rejected_cookies = 0;
533 int session_autosave = 0;
534 int guess_search = 0;
535 int dns_prefetch = FALSE;
536 gint max_connections = 25;
537 gint max_host_connections = 5;
538 gint enable_spell_checking = 0;
539 char *spell_check_languages = NULL;
541 char *cmd_font_name = NULL;
542 char *statusbar_font_name = NULL;
543 PangoFontDescription *cmd_font;
544 PangoFontDescription *statusbar_font;
546 struct settings;
547 struct key_binding;
548 int set_browser_mode(struct settings *, char *);
549 int set_cookie_policy(struct settings *, char *);
550 int set_download_dir(struct settings *, char *);
551 int set_runtime_dir(struct settings *, char *);
552 int set_tab_style(struct settings *, char *);
553 int set_work_dir(struct settings *, char *);
554 int add_alias(struct settings *, char *);
555 int add_mime_type(struct settings *, char *);
556 int add_cookie_wl(struct settings *, char *);
557 int add_js_wl(struct settings *, char *);
558 int add_kb(struct settings *, char *);
559 void button_set_stockid(GtkWidget *, char *);
560 GtkWidget * create_button(char *, char *, int);
562 char *get_browser_mode(struct settings *);
563 char *get_cookie_policy(struct settings *);
564 char *get_download_dir(struct settings *);
565 char *get_runtime_dir(struct settings *);
566 char *get_tab_style(struct settings *);
567 char *get_work_dir(struct settings *);
569 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
570 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
571 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
572 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
573 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
575 void recalc_tabs(void);
576 void recolor_compact_tabs(void);
577 void set_current_tab(int page_num);
579 struct special {
580 int (*set)(struct settings *, char *);
581 char *(*get)(struct settings *);
582 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
585 struct special s_browser_mode = {
586 set_browser_mode,
587 get_browser_mode,
588 NULL
591 struct special s_cookie = {
592 set_cookie_policy,
593 get_cookie_policy,
594 NULL
597 struct special s_alias = {
598 add_alias,
599 NULL,
600 walk_alias
603 struct special s_mime = {
604 add_mime_type,
605 NULL,
606 walk_mime_type
609 struct special s_js = {
610 add_js_wl,
611 NULL,
612 walk_js_wl
615 struct special s_kb = {
616 add_kb,
617 NULL,
618 walk_kb
621 struct special s_cookie_wl = {
622 add_cookie_wl,
623 NULL,
624 walk_cookie_wl
627 struct special s_download_dir = {
628 set_download_dir,
629 get_download_dir,
630 NULL
633 struct special s_work_dir = {
634 set_work_dir,
635 get_work_dir,
636 NULL
639 struct special s_tab_style = {
640 set_tab_style,
641 get_tab_style,
642 NULL
645 struct settings {
646 char *name;
647 int type;
648 #define XT_S_INVALID (0)
649 #define XT_S_INT (1)
650 #define XT_S_STR (2)
651 #define XT_S_FLOAT (3)
652 uint32_t flags;
653 #define XT_SF_RESTART (1<<0)
654 #define XT_SF_RUNTIME (1<<1)
655 int *ival;
656 char **sval;
657 struct special *s;
658 gfloat *fval;
659 } rs[] = {
660 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
661 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
662 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
663 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
664 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
665 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
666 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
667 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
668 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
669 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
670 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
671 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
672 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
673 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
674 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
675 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
676 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
677 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
678 { "home", XT_S_STR, 0, NULL, &home, NULL },
679 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
680 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
681 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
682 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
683 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
684 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
685 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
686 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
687 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
688 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
689 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
690 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
691 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
692 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
693 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
694 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
695 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
696 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
697 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
698 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
699 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
700 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
701 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
702 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
704 /* font settings */
705 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
706 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
708 /* runtime settings */
709 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
710 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
711 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
712 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
713 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
716 int about(struct tab *, struct karg *);
717 int blank(struct tab *, struct karg *);
718 int ca_cmd(struct tab *, struct karg *);
719 int cookie_show_wl(struct tab *, struct karg *);
720 int js_show_wl(struct tab *, struct karg *);
721 int help(struct tab *, struct karg *);
722 int set(struct tab *, struct karg *);
723 int stats(struct tab *, struct karg *);
724 int marco(struct tab *, struct karg *);
725 const char * marco_message(int *);
726 int xtp_page_cl(struct tab *, struct karg *);
727 int xtp_page_dl(struct tab *, struct karg *);
728 int xtp_page_fl(struct tab *, struct karg *);
729 int xtp_page_hl(struct tab *, struct karg *);
730 void xt_icon_from_file(struct tab *, char *);
731 const gchar *get_uri(struct tab *);
732 const gchar *get_title(struct tab *);
734 #define XT_URI_ABOUT ("about:")
735 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
736 #define XT_URI_ABOUT_ABOUT ("about")
737 #define XT_URI_ABOUT_BLANK ("blank")
738 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
739 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
740 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
741 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
742 #define XT_URI_ABOUT_FAVORITES ("favorites")
743 #define XT_URI_ABOUT_HELP ("help")
744 #define XT_URI_ABOUT_HISTORY ("history")
745 #define XT_URI_ABOUT_JSWL ("jswl")
746 #define XT_URI_ABOUT_SET ("set")
747 #define XT_URI_ABOUT_STATS ("stats")
748 #define XT_URI_ABOUT_MARCO ("marco")
750 struct about_type {
751 char *name;
752 int (*func)(struct tab *, struct karg *);
753 } about_list[] = {
754 { XT_URI_ABOUT_ABOUT, about },
755 { XT_URI_ABOUT_BLANK, blank },
756 { XT_URI_ABOUT_CERTS, ca_cmd },
757 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
758 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
759 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
760 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
761 { XT_URI_ABOUT_HELP, help },
762 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
763 { XT_URI_ABOUT_JSWL, js_show_wl },
764 { XT_URI_ABOUT_SET, set },
765 { XT_URI_ABOUT_STATS, stats },
766 { XT_URI_ABOUT_MARCO, marco },
769 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
770 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
771 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
772 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
773 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
774 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
775 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
777 /* globals */
778 extern char *__progname;
779 char **start_argv;
780 struct passwd *pwd;
781 GtkWidget *main_window;
782 GtkNotebook *notebook;
783 GtkWidget *tab_bar;
784 GtkWidget *arrow, *abtn;
785 struct tab_list tabs;
786 struct history_list hl;
787 struct download_list downloads;
788 struct domain_list c_wl;
789 struct domain_list js_wl;
790 struct undo_tailq undos;
791 struct keybinding_list kbl;
792 int undo_count;
793 int updating_dl_tabs = 0;
794 int updating_hl_tabs = 0;
795 int updating_cl_tabs = 0;
796 int updating_fl_tabs = 0;
797 char *global_search;
798 uint64_t blocked_cookies = 0;
799 char named_session[PATH_MAX];
800 int icon_size_map(int);
802 GtkListStore *completion_model;
803 void completion_add(struct tab *);
804 void completion_add_uri(const gchar *);
805 GtkListStore *buffers_store;
806 void xxx_dir(char *);
808 void
809 sigchild(int sig)
811 int saved_errno, status;
812 pid_t pid;
814 saved_errno = errno;
816 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
817 if (pid == -1) {
818 if (errno == EINTR)
819 continue;
820 if (errno != ECHILD) {
822 clog_warn("sigchild: waitpid:");
825 break;
828 if (WIFEXITED(status)) {
829 if (WEXITSTATUS(status) != 0) {
831 clog_warnx("sigchild: child exit status: %d",
832 WEXITSTATUS(status));
835 } else {
837 clog_warnx("sigchild: child is terminated abnormally");
842 errno = saved_errno;
846 is_g_object_setting(GObject *o, char *str)
848 guint n_props = 0, i;
849 GParamSpec **proplist;
851 if (! G_IS_OBJECT(o))
852 return (0);
854 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
855 &n_props);
857 for (i=0; i < n_props; i++) {
858 if (! strcmp(proplist[i]->name, str))
859 return (1);
861 return (0);
864 gchar *
865 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
867 gchar *r;
869 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
870 "<head>\n"
871 "<title>%s</title>\n"
872 "%s"
873 "%s"
874 "</head>\n"
875 "<body>\n"
876 "<h1>%s</h1>\n"
877 "%s\n</body>\n"
878 "</html>",
879 title,
880 addstyles ? XT_PAGE_STYLE : "",
881 head,
882 title,
883 body);
885 return r;
889 * Display a web page from a HTML string in memory, rather than from a URL
891 void
892 load_webkit_string(struct tab *t, const char *str, gchar *title)
894 char file[PATH_MAX];
895 int i;
897 /* we set this to indicate we want to manually do navaction */
898 if (t->bfl)
899 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
901 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
902 if (title) {
903 /* set t->xtp_meaning */
904 for (i = 0; i < LENGTH(about_list); i++)
905 if (!strcmp(title, about_list[i].name)) {
906 t->xtp_meaning = i;
907 break;
910 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
911 #if GTK_CHECK_VERSION(2, 20, 0)
912 gtk_spinner_stop(GTK_SPINNER(t->spinner));
913 gtk_widget_hide(t->spinner);
914 #endif
915 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
916 xt_icon_from_file(t, file);
920 struct tab *
921 get_current_tab(void)
923 struct tab *t;
925 TAILQ_FOREACH(t, &tabs, entry) {
926 if (t->tab_id == gtk_notebook_get_current_page(notebook))
927 return (t);
930 warnx("%s: no current tab", __func__);
932 return (NULL);
935 void
936 set_status(struct tab *t, gchar *s, int status)
938 gchar *type = NULL;
940 if (s == NULL)
941 return;
943 switch (status) {
944 case XT_STATUS_LOADING:
945 type = g_strdup_printf("Loading: %s", s);
946 s = type;
947 break;
948 case XT_STATUS_LINK:
949 type = g_strdup_printf("Link: %s", s);
950 if (!t->status)
951 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
952 s = type;
953 break;
954 case XT_STATUS_URI:
955 type = g_strdup_printf("%s", s);
956 if (!t->status) {
957 t->status = g_strdup(type);
959 s = type;
960 if (!t->status)
961 t->status = g_strdup(s);
962 break;
963 case XT_STATUS_NOTHING:
964 /* FALL THROUGH */
965 default:
966 break;
968 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
969 if (type)
970 g_free(type);
973 void
974 hide_cmd(struct tab *t)
976 gtk_widget_hide(t->cmd);
979 void
980 show_cmd(struct tab *t)
982 gtk_widget_hide(t->oops);
983 gtk_widget_show(t->cmd);
986 void
987 hide_buffers(struct tab *t)
989 gtk_widget_hide(t->buffers);
990 gtk_list_store_clear(buffers_store);
993 enum {
994 COL_ID = 0,
995 COL_TITLE,
996 NUM_COLS
1000 sort_tabs_by_page_num(struct tab ***stabs)
1002 int num_tabs = 0;
1003 struct tab *t;
1005 num_tabs = gtk_notebook_get_n_pages(notebook);
1007 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1009 TAILQ_FOREACH(t, &tabs, entry)
1010 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1012 return (num_tabs);
1015 void
1016 buffers_make_list(void)
1018 int i, num_tabs;
1019 const gchar *title = NULL;
1020 GtkTreeIter iter;
1021 struct tab **stabs = NULL;
1023 num_tabs = sort_tabs_by_page_num(&stabs);
1025 for (i = 0; i < num_tabs; i++)
1026 if (stabs[i]) {
1027 gtk_list_store_append(buffers_store, &iter);
1028 title = get_title(stabs[i]);
1029 gtk_list_store_set(buffers_store, &iter,
1030 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1031 * rather than 0. */
1032 COL_TITLE, title,
1033 -1);
1036 g_free(stabs);
1039 void
1040 show_buffers(struct tab *t)
1042 buffers_make_list();
1043 gtk_widget_show(t->buffers);
1044 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1047 void
1048 toggle_buffers(struct tab *t)
1050 if (gtk_widget_get_visible(t->buffers))
1051 hide_buffers(t);
1052 else
1053 show_buffers(t);
1057 buffers(struct tab *t, struct karg *args)
1059 show_buffers(t);
1061 return (0);
1064 void
1065 hide_oops(struct tab *t)
1067 gtk_widget_hide(t->oops);
1070 void
1071 show_oops(struct tab *at, const char *fmt, ...)
1073 va_list ap;
1074 char *msg;
1075 struct tab *t = NULL;
1077 if (fmt == NULL)
1078 return;
1080 if (at == NULL) {
1081 if ((t = get_current_tab()) == NULL)
1082 return;
1083 } else
1084 t = at;
1086 va_start(ap, fmt);
1087 if (vasprintf(&msg, fmt, ap) == -1)
1088 errx(1, "show_oops failed");
1089 va_end(ap);
1091 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1092 gtk_widget_hide(t->cmd);
1093 gtk_widget_show(t->oops);
1096 char *
1097 get_as_string(struct settings *s)
1099 char *r = NULL;
1101 if (s == NULL)
1102 return (NULL);
1104 if (s->s) {
1105 if (s->s->get)
1106 r = s->s->get(s);
1107 else
1108 warnx("get_as_string skip %s\n", s->name);
1109 } else if (s->type == XT_S_INT)
1110 r = g_strdup_printf("%d", *s->ival);
1111 else if (s->type == XT_S_STR)
1112 r = g_strdup(*s->sval);
1113 else if (s->type == XT_S_FLOAT)
1114 r = g_strdup_printf("%f", *s->fval);
1115 else
1116 r = g_strdup_printf("INVALID TYPE");
1118 return (r);
1121 void
1122 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1124 int i;
1125 char *s;
1127 for (i = 0; i < LENGTH(rs); i++) {
1128 if (rs[i].s && rs[i].s->walk)
1129 rs[i].s->walk(&rs[i], cb, cb_args);
1130 else {
1131 s = get_as_string(&rs[i]);
1132 cb(&rs[i], s, cb_args);
1133 g_free(s);
1139 set_browser_mode(struct settings *s, char *val)
1141 if (!strcmp(val, "whitelist")) {
1142 browser_mode = XT_BM_WHITELIST;
1143 allow_volatile_cookies = 0;
1144 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1145 cookies_enabled = 1;
1146 enable_cookie_whitelist = 1;
1147 read_only_cookies = 0;
1148 save_rejected_cookies = 0;
1149 session_timeout = 3600;
1150 enable_scripts = 0;
1151 enable_js_whitelist = 1;
1152 enable_localstorage = 0;
1153 } else if (!strcmp(val, "normal")) {
1154 browser_mode = XT_BM_NORMAL;
1155 allow_volatile_cookies = 0;
1156 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1157 cookies_enabled = 1;
1158 enable_cookie_whitelist = 0;
1159 read_only_cookies = 0;
1160 save_rejected_cookies = 0;
1161 session_timeout = 3600;
1162 enable_scripts = 1;
1163 enable_js_whitelist = 0;
1164 enable_localstorage = 1;
1165 } else if (!strcmp(val, "kiosk")) {
1166 browser_mode = XT_BM_KIOSK;
1167 allow_volatile_cookies = 0;
1168 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1169 cookies_enabled = 1;
1170 enable_cookie_whitelist = 0;
1171 read_only_cookies = 0;
1172 save_rejected_cookies = 0;
1173 session_timeout = 3600;
1174 enable_scripts = 1;
1175 enable_js_whitelist = 0;
1176 enable_localstorage = 1;
1177 show_tabs = 0;
1178 tabless = 1;
1179 } else
1180 return (1);
1182 return (0);
1185 char *
1186 get_browser_mode(struct settings *s)
1188 char *r = NULL;
1190 if (browser_mode == XT_BM_WHITELIST)
1191 r = g_strdup("whitelist");
1192 else if (browser_mode == XT_BM_NORMAL)
1193 r = g_strdup("normal");
1194 else if (browser_mode == XT_BM_KIOSK)
1195 r = g_strdup("kiosk");
1196 else
1197 return (NULL);
1199 return (r);
1203 set_cookie_policy(struct settings *s, char *val)
1205 if (!strcmp(val, "no3rdparty"))
1206 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1207 else if (!strcmp(val, "accept"))
1208 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1209 else if (!strcmp(val, "reject"))
1210 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1211 else
1212 return (1);
1214 return (0);
1217 char *
1218 get_cookie_policy(struct settings *s)
1220 char *r = NULL;
1222 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1223 r = g_strdup("no3rdparty");
1224 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1225 r = g_strdup("accept");
1226 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1227 r = g_strdup("reject");
1228 else
1229 return (NULL);
1231 return (r);
1234 char *
1235 get_download_dir(struct settings *s)
1237 if (download_dir[0] == '\0')
1238 return (0);
1239 return (g_strdup(download_dir));
1243 set_download_dir(struct settings *s, char *val)
1245 if (val[0] == '~')
1246 snprintf(download_dir, sizeof download_dir, "%s/%s",
1247 pwd->pw_dir, &val[1]);
1248 else
1249 strlcpy(download_dir, val, sizeof download_dir);
1251 return (0);
1255 * Session IDs.
1256 * We use these to prevent people putting xxxt:// URLs on
1257 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1259 #define XT_XTP_SES_KEY_SZ 8
1260 #define XT_XTP_SES_KEY_HEX_FMT \
1261 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1262 char *dl_session_key; /* downloads */
1263 char *hl_session_key; /* history list */
1264 char *cl_session_key; /* cookie list */
1265 char *fl_session_key; /* favorites list */
1267 char work_dir[PATH_MAX];
1268 char certs_dir[PATH_MAX];
1269 char cache_dir[PATH_MAX];
1270 char sessions_dir[PATH_MAX];
1271 char cookie_file[PATH_MAX];
1272 SoupURI *proxy_uri = NULL;
1273 SoupSession *session;
1274 SoupCookieJar *s_cookiejar;
1275 SoupCookieJar *p_cookiejar;
1276 char rc_fname[PATH_MAX];
1278 struct mime_type_list mtl;
1279 struct alias_list aliases;
1281 /* protos */
1282 struct tab *create_new_tab(char *, struct undo *, int, int);
1283 void delete_tab(struct tab *);
1284 void adjustfont_webkit(struct tab *, int);
1285 int run_script(struct tab *, char *);
1286 int download_rb_cmp(struct download *, struct download *);
1287 gboolean cmd_execute(struct tab *t, char *str);
1290 history_rb_cmp(struct history *h1, struct history *h2)
1292 return (strcmp(h1->uri, h2->uri));
1294 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1297 domain_rb_cmp(struct domain *d1, struct domain *d2)
1299 return (strcmp(d1->d, d2->d));
1301 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1303 char *
1304 get_work_dir(struct settings *s)
1306 if (work_dir[0] == '\0')
1307 return (0);
1308 return (g_strdup(work_dir));
1312 set_work_dir(struct settings *s, char *val)
1314 if (val[0] == '~')
1315 snprintf(work_dir, sizeof work_dir, "%s/%s",
1316 pwd->pw_dir, &val[1]);
1317 else
1318 strlcpy(work_dir, val, sizeof work_dir);
1320 return (0);
1323 char *
1324 get_tab_style(struct settings *s)
1326 if (tab_style == XT_TABS_NORMAL)
1327 return (g_strdup("normal"));
1328 else
1329 return (g_strdup("compact"));
1333 set_tab_style(struct settings *s, char *val)
1335 if (!strcmp(val, "normal"))
1336 tab_style = XT_TABS_NORMAL;
1337 else if (!strcmp(val, "compact"))
1338 tab_style = XT_TABS_COMPACT;
1339 else
1340 return (1);
1342 return (0);
1346 * generate a session key to secure xtp commands.
1347 * pass in a ptr to the key in question and it will
1348 * be modified in place.
1350 void
1351 generate_xtp_session_key(char **key)
1353 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1355 /* free old key */
1356 if (*key)
1357 g_free(*key);
1359 /* make a new one */
1360 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1361 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1362 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1363 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1365 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1369 * validate a xtp session key.
1370 * return 1 if OK
1373 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1375 if (strcmp(trusted, untrusted) != 0) {
1376 show_oops(t, "%s: xtp session key mismatch possible spoof",
1377 __func__);
1378 return (0);
1381 return (1);
1385 download_rb_cmp(struct download *e1, struct download *e2)
1387 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1389 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1391 struct valid_url_types {
1392 char *type;
1393 } vut[] = {
1394 { "http://" },
1395 { "https://" },
1396 { "ftp://" },
1397 { "file://" },
1398 { XT_XTP_STR },
1402 valid_url_type(char *url)
1404 int i;
1406 for (i = 0; i < LENGTH(vut); i++)
1407 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1408 return (0);
1410 return (1);
1413 void
1414 print_cookie(char *msg, SoupCookie *c)
1416 if (c == NULL)
1417 return;
1419 if (msg)
1420 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1421 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1422 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1423 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1424 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1425 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1426 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1427 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1428 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1429 DNPRINTF(XT_D_COOKIE, "====================================\n");
1432 void
1433 walk_alias(struct settings *s,
1434 void (*cb)(struct settings *, char *, void *), void *cb_args)
1436 struct alias *a;
1437 char *str;
1439 if (s == NULL || cb == NULL) {
1440 show_oops(NULL, "walk_alias invalid parameters");
1441 return;
1444 TAILQ_FOREACH(a, &aliases, entry) {
1445 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1446 cb(s, str, cb_args);
1447 g_free(str);
1451 char *
1452 match_alias(char *url_in)
1454 struct alias *a;
1455 char *arg;
1456 char *url_out = NULL, *search, *enc_arg;
1458 search = g_strdup(url_in);
1459 arg = search;
1460 if (strsep(&arg, " \t") == NULL) {
1461 show_oops(NULL, "match_alias: NULL URL");
1462 goto done;
1465 TAILQ_FOREACH(a, &aliases, entry) {
1466 if (!strcmp(search, a->a_name))
1467 break;
1470 if (a != NULL) {
1471 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1472 a->a_name);
1473 if (arg != NULL) {
1474 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1475 url_out = g_strdup_printf(a->a_uri, enc_arg);
1476 g_free(enc_arg);
1477 } else
1478 url_out = g_strdup_printf(a->a_uri, "");
1480 done:
1481 g_free(search);
1482 return (url_out);
1485 char *
1486 guess_url_type(char *url_in)
1488 struct stat sb;
1489 char *url_out = NULL, *enc_search = NULL;
1491 url_out = match_alias(url_in);
1492 if (url_out != NULL)
1493 return (url_out);
1495 if (guess_search) {
1497 * If there is no dot nor slash in the string and it isn't a
1498 * path to a local file and doesn't resolves to an IP, assume
1499 * that the user wants to search for the string.
1502 if (strchr(url_in, '.') == NULL &&
1503 strchr(url_in, '/') == NULL &&
1504 stat(url_in, &sb) != 0 &&
1505 gethostbyname(url_in) == NULL) {
1507 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1508 url_out = g_strdup_printf(search_string, enc_search);
1509 g_free(enc_search);
1510 return (url_out);
1514 /* XXX not sure about this heuristic */
1515 if (stat(url_in, &sb) == 0)
1516 url_out = g_strdup_printf("file://%s", url_in);
1517 else
1518 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1520 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1522 return (url_out);
1525 void
1526 load_uri(struct tab *t, gchar *uri)
1528 struct karg args;
1529 gchar *newuri = NULL;
1530 int i;
1532 if (uri == NULL)
1533 return;
1535 /* Strip leading spaces. */
1536 while (*uri && isspace(*uri))
1537 uri++;
1539 if (strlen(uri) == 0) {
1540 blank(t, NULL);
1541 return;
1544 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1546 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1547 for (i = 0; i < LENGTH(about_list); i++)
1548 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1549 bzero(&args, sizeof args);
1550 about_list[i].func(t, &args);
1551 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1552 FALSE);
1553 return;
1555 show_oops(t, "invalid about page");
1556 return;
1559 if (valid_url_type(uri)) {
1560 newuri = guess_url_type(uri);
1561 uri = newuri;
1564 set_status(t, (char *)uri, XT_STATUS_LOADING);
1565 webkit_web_view_load_uri(t->wv, uri);
1567 if (newuri)
1568 g_free(newuri);
1571 const gchar *
1572 get_uri(struct tab *t)
1574 const gchar *uri = NULL;
1576 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
1577 uri = webkit_web_view_get_uri(t->wv);
1578 else
1579 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, about_list[t->xtp_meaning].name);
1581 return uri;
1584 const gchar *
1585 get_title(struct tab *t)
1587 const gchar *set = NULL, *title = NULL;
1589 title = webkit_web_view_get_title(t->wv);
1590 set = title ? title : get_uri(t);
1591 if (!set || t->xtp_meaning == XT_XTP_TAB_MEANING_BL) {
1592 set = "(untitled)";
1594 return set;
1598 add_alias(struct settings *s, char *line)
1600 char *l, *alias;
1601 struct alias *a = NULL;
1603 if (s == NULL || line == NULL) {
1604 show_oops(NULL, "add_alias invalid parameters");
1605 return (1);
1608 l = line;
1609 a = g_malloc(sizeof(*a));
1611 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1612 show_oops(NULL, "add_alias: incomplete alias definition");
1613 goto bad;
1615 if (strlen(alias) == 0 || strlen(l) == 0) {
1616 show_oops(NULL, "add_alias: invalid alias definition");
1617 goto bad;
1620 a->a_name = g_strdup(alias);
1621 a->a_uri = g_strdup(l);
1623 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1625 TAILQ_INSERT_TAIL(&aliases, a, entry);
1627 return (0);
1628 bad:
1629 if (a)
1630 g_free(a);
1631 return (1);
1635 add_mime_type(struct settings *s, char *line)
1637 char *mime_type;
1638 char *l;
1639 struct mime_type *m = NULL;
1640 int downloadfirst = 0;
1642 /* XXX this could be smarter */
1644 if (line == NULL || strlen(line) == 0) {
1645 show_oops(NULL, "add_mime_type invalid parameters");
1646 return (1);
1649 l = line;
1650 if (*l == '@') {
1651 downloadfirst = 1;
1652 l++;
1654 m = g_malloc(sizeof(*m));
1656 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1657 show_oops(NULL, "add_mime_type: invalid mime_type");
1658 goto bad;
1660 if (mime_type[strlen(mime_type) - 1] == '*') {
1661 mime_type[strlen(mime_type) - 1] = '\0';
1662 m->mt_default = 1;
1663 } else
1664 m->mt_default = 0;
1666 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1667 show_oops(NULL, "add_mime_type: invalid mime_type");
1668 goto bad;
1671 m->mt_type = g_strdup(mime_type);
1672 m->mt_action = g_strdup(l);
1673 m->mt_download = downloadfirst;
1675 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1676 m->mt_type, m->mt_action, m->mt_default);
1678 TAILQ_INSERT_TAIL(&mtl, m, entry);
1680 return (0);
1681 bad:
1682 if (m)
1683 g_free(m);
1684 return (1);
1687 struct mime_type *
1688 find_mime_type(char *mime_type)
1690 struct mime_type *m, *def = NULL, *rv = NULL;
1692 TAILQ_FOREACH(m, &mtl, entry) {
1693 if (m->mt_default &&
1694 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1695 def = m;
1697 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1698 rv = m;
1699 break;
1703 if (rv == NULL)
1704 rv = def;
1706 return (rv);
1709 void
1710 walk_mime_type(struct settings *s,
1711 void (*cb)(struct settings *, char *, void *), void *cb_args)
1713 struct mime_type *m;
1714 char *str;
1716 if (s == NULL || cb == NULL) {
1717 show_oops(NULL, "walk_mime_type invalid parameters");
1718 return;
1721 TAILQ_FOREACH(m, &mtl, entry) {
1722 str = g_strdup_printf("%s%s --> %s",
1723 m->mt_type,
1724 m->mt_default ? "*" : "",
1725 m->mt_action);
1726 cb(s, str, cb_args);
1727 g_free(str);
1731 void
1732 wl_add(char *str, struct domain_list *wl, int handy)
1734 struct domain *d;
1735 int add_dot = 0;
1737 if (str == NULL || wl == NULL || strlen(str) < 2)
1738 return;
1740 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1742 /* treat *.moo.com the same as .moo.com */
1743 if (str[0] == '*' && str[1] == '.')
1744 str = &str[1];
1745 else if (str[0] == '.')
1746 str = &str[0];
1747 else
1748 add_dot = 1;
1750 d = g_malloc(sizeof *d);
1751 if (add_dot)
1752 d->d = g_strdup_printf(".%s", str);
1753 else
1754 d->d = g_strdup(str);
1755 d->handy = handy;
1757 if (RB_INSERT(domain_list, wl, d))
1758 goto unwind;
1760 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1761 return;
1762 unwind:
1763 if (d) {
1764 if (d->d)
1765 g_free(d->d);
1766 g_free(d);
1771 add_cookie_wl(struct settings *s, char *entry)
1773 wl_add(entry, &c_wl, 1);
1774 return (0);
1777 void
1778 walk_cookie_wl(struct settings *s,
1779 void (*cb)(struct settings *, char *, void *), void *cb_args)
1781 struct domain *d;
1783 if (s == NULL || cb == NULL) {
1784 show_oops(NULL, "walk_cookie_wl invalid parameters");
1785 return;
1788 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1789 cb(s, d->d, cb_args);
1792 void
1793 walk_js_wl(struct settings *s,
1794 void (*cb)(struct settings *, char *, void *), void *cb_args)
1796 struct domain *d;
1798 if (s == NULL || cb == NULL) {
1799 show_oops(NULL, "walk_js_wl invalid parameters");
1800 return;
1803 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1804 cb(s, d->d, cb_args);
1808 add_js_wl(struct settings *s, char *entry)
1810 wl_add(entry, &js_wl, 1 /* persistent */);
1811 return (0);
1814 struct domain *
1815 wl_find(const gchar *search, struct domain_list *wl)
1817 int i;
1818 struct domain *d = NULL, dfind;
1819 gchar *s = NULL;
1821 if (search == NULL || wl == NULL)
1822 return (NULL);
1823 if (strlen(search) < 2)
1824 return (NULL);
1826 if (search[0] != '.')
1827 s = g_strdup_printf(".%s", search);
1828 else
1829 s = g_strdup(search);
1831 for (i = strlen(s) - 1; i >= 0; i--) {
1832 if (s[i] == '.') {
1833 dfind.d = &s[i];
1834 d = RB_FIND(domain_list, wl, &dfind);
1835 if (d)
1836 goto done;
1840 done:
1841 if (s)
1842 g_free(s);
1844 return (d);
1847 struct domain *
1848 wl_find_uri(const gchar *s, struct domain_list *wl)
1850 int i;
1851 char *ss;
1852 struct domain *r;
1854 if (s == NULL || wl == NULL)
1855 return (NULL);
1857 if (!strncmp(s, "http://", strlen("http://")))
1858 s = &s[strlen("http://")];
1859 else if (!strncmp(s, "https://", strlen("https://")))
1860 s = &s[strlen("https://")];
1862 if (strlen(s) < 2)
1863 return (NULL);
1865 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1866 /* chop string at first slash */
1867 if (s[i] == '/' || s[i] == '\0') {
1868 ss = g_strdup(s);
1869 ss[i] = '\0';
1870 r = wl_find(ss, wl);
1871 g_free(ss);
1872 return (r);
1875 return (NULL);
1878 char *
1879 get_toplevel_domain(char *domain)
1881 char *s;
1882 int found = 0;
1884 if (domain == NULL)
1885 return (NULL);
1886 if (strlen(domain) < 2)
1887 return (NULL);
1889 s = &domain[strlen(domain) - 1];
1890 while (s != domain) {
1891 if (*s == '.') {
1892 found++;
1893 if (found == 2)
1894 return (s);
1896 s--;
1899 if (found)
1900 return (domain);
1902 return (NULL);
1906 settings_add(char *var, char *val)
1908 int i, rv, *p;
1909 gfloat *f;
1910 char **s;
1912 /* get settings */
1913 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1914 if (strcmp(var, rs[i].name))
1915 continue;
1917 if (rs[i].s) {
1918 if (rs[i].s->set(&rs[i], val))
1919 errx(1, "invalid value for %s: %s", var, val);
1920 rv = 1;
1921 break;
1922 } else
1923 switch (rs[i].type) {
1924 case XT_S_INT:
1925 p = rs[i].ival;
1926 *p = atoi(val);
1927 rv = 1;
1928 break;
1929 case XT_S_STR:
1930 s = rs[i].sval;
1931 if (s == NULL)
1932 errx(1, "invalid sval for %s",
1933 rs[i].name);
1934 if (*s)
1935 g_free(*s);
1936 *s = g_strdup(val);
1937 rv = 1;
1938 break;
1939 case XT_S_FLOAT:
1940 f = rs[i].fval;
1941 *f = atof(val);
1942 rv = 1;
1943 break;
1944 case XT_S_INVALID:
1945 default:
1946 errx(1, "invalid type for %s", var);
1948 break;
1950 return (rv);
1953 #define WS "\n= \t"
1954 void
1955 config_parse(char *filename, int runtime)
1957 FILE *config, *f;
1958 char *line, *cp, *var, *val;
1959 size_t len, lineno = 0;
1960 int handled;
1961 char file[PATH_MAX];
1962 struct stat sb;
1964 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1966 if (filename == NULL)
1967 return;
1969 if (runtime && runtime_settings[0] != '\0') {
1970 snprintf(file, sizeof file, "%s/%s",
1971 work_dir, runtime_settings);
1972 if (stat(file, &sb)) {
1973 warnx("runtime file doesn't exist, creating it");
1974 if ((f = fopen(file, "w")) == NULL)
1975 err(1, "runtime");
1976 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1977 fclose(f);
1979 } else
1980 strlcpy(file, filename, sizeof file);
1982 if ((config = fopen(file, "r")) == NULL) {
1983 warn("config_parse: cannot open %s", filename);
1984 return;
1987 for (;;) {
1988 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1989 if (feof(config) || ferror(config))
1990 break;
1992 cp = line;
1993 cp += (long)strspn(cp, WS);
1994 if (cp[0] == '\0') {
1995 /* empty line */
1996 free(line);
1997 continue;
2000 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2001 errx(1, "invalid config file entry: %s", line);
2003 cp += (long)strspn(cp, WS);
2005 if ((val = strsep(&cp, "\0")) == NULL)
2006 break;
2008 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2009 handled = settings_add(var, val);
2010 if (handled == 0)
2011 errx(1, "invalid conf file entry: %s=%s", var, val);
2013 free(line);
2016 fclose(config);
2019 char *
2020 js_ref_to_string(JSContextRef context, JSValueRef ref)
2022 char *s = NULL;
2023 size_t l;
2024 JSStringRef jsref;
2026 jsref = JSValueToStringCopy(context, ref, NULL);
2027 if (jsref == NULL)
2028 return (NULL);
2030 l = JSStringGetMaximumUTF8CStringSize(jsref);
2031 s = g_malloc(l);
2032 if (s)
2033 JSStringGetUTF8CString(jsref, s, l);
2034 JSStringRelease(jsref);
2036 return (s);
2039 void
2040 disable_hints(struct tab *t)
2042 bzero(t->hint_buf, sizeof t->hint_buf);
2043 bzero(t->hint_num, sizeof t->hint_num);
2044 run_script(t, "vimprobable_clear()");
2045 t->hints_on = 0;
2046 t->hint_mode = XT_HINT_NONE;
2049 void
2050 enable_hints(struct tab *t)
2052 bzero(t->hint_buf, sizeof t->hint_buf);
2053 run_script(t, "vimprobable_show_hints()");
2054 t->hints_on = 1;
2055 t->hint_mode = XT_HINT_NONE;
2058 #define XT_JS_OPEN ("open;")
2059 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2060 #define XT_JS_FIRE ("fire;")
2061 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2062 #define XT_JS_FOUND ("found;")
2063 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2066 run_script(struct tab *t, char *s)
2068 JSGlobalContextRef ctx;
2069 WebKitWebFrame *frame;
2070 JSStringRef str;
2071 JSValueRef val, exception;
2072 char *es, buf[128];
2074 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2075 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2077 frame = webkit_web_view_get_main_frame(t->wv);
2078 ctx = webkit_web_frame_get_global_context(frame);
2080 str = JSStringCreateWithUTF8CString(s);
2081 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2082 NULL, 0, &exception);
2083 JSStringRelease(str);
2085 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2086 if (val == NULL) {
2087 es = js_ref_to_string(ctx, exception);
2088 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2089 g_free(es);
2090 return (1);
2091 } else {
2092 es = js_ref_to_string(ctx, val);
2093 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2095 /* handle return value right here */
2096 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2097 disable_hints(t);
2098 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
2101 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2102 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2103 &es[XT_JS_FIRE_LEN]);
2104 run_script(t, buf);
2105 disable_hints(t);
2108 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2109 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2110 disable_hints(t);
2113 g_free(es);
2116 return (0);
2120 hint(struct tab *t, struct karg *args)
2123 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2125 if (t->hints_on == 0)
2126 enable_hints(t);
2127 else
2128 disable_hints(t);
2130 return (0);
2133 void
2134 apply_style(struct tab *t)
2136 g_object_set(G_OBJECT(t->settings),
2137 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2141 userstyle(struct tab *t, struct karg *args)
2143 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2145 if (t->styled) {
2146 t->styled = 0;
2147 g_object_set(G_OBJECT(t->settings),
2148 "user-stylesheet-uri", NULL, (char *)NULL);
2149 } else {
2150 t->styled = 1;
2151 apply_style(t);
2153 return (0);
2157 * Doesn't work fully, due to the following bug:
2158 * https://bugs.webkit.org/show_bug.cgi?id=51747
2161 restore_global_history(void)
2163 char file[PATH_MAX];
2164 FILE *f;
2165 struct history *h;
2166 gchar *uri;
2167 gchar *title;
2168 const char delim[3] = {'\\', '\\', '\0'};
2170 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2172 if ((f = fopen(file, "r")) == NULL) {
2173 warnx("%s: fopen", __func__);
2174 return (1);
2177 for (;;) {
2178 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2179 if (feof(f) || ferror(f))
2180 break;
2182 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2183 if (feof(f) || ferror(f)) {
2184 free(uri);
2185 warnx("%s: broken history file\n", __func__);
2186 return (1);
2189 if (uri && strlen(uri) && title && strlen(title)) {
2190 webkit_web_history_item_new_with_data(uri, title);
2191 h = g_malloc(sizeof(struct history));
2192 h->uri = g_strdup(uri);
2193 h->title = g_strdup(title);
2194 RB_INSERT(history_list, &hl, h);
2195 completion_add_uri(h->uri);
2196 } else {
2197 warnx("%s: failed to restore history\n", __func__);
2198 free(uri);
2199 free(title);
2200 return (1);
2203 free(uri);
2204 free(title);
2205 uri = NULL;
2206 title = NULL;
2209 return (0);
2213 save_global_history_to_disk(struct tab *t)
2215 char file[PATH_MAX];
2216 FILE *f;
2217 struct history *h;
2219 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2221 if ((f = fopen(file, "w")) == NULL) {
2222 show_oops(t, "%s: global history file: %s",
2223 __func__, strerror(errno));
2224 return (1);
2227 RB_FOREACH_REVERSE(h, history_list, &hl) {
2228 if (h->uri && h->title)
2229 fprintf(f, "%s\n%s\n", h->uri, h->title);
2232 fclose(f);
2234 return (0);
2238 quit(struct tab *t, struct karg *args)
2240 if (save_global_history)
2241 save_global_history_to_disk(t);
2243 gtk_main_quit();
2245 return (1);
2249 open_tabs(struct tab *t, struct karg *a)
2251 char file[PATH_MAX];
2252 FILE *f = NULL;
2253 char *uri = NULL;
2254 int rv = 1;
2255 struct tab *ti, *tt;
2257 if (a == NULL)
2258 goto done;
2260 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2261 if ((f = fopen(file, "r")) == NULL)
2262 goto done;
2264 ti = TAILQ_LAST(&tabs, tab_list);
2266 for (;;) {
2267 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2268 if (feof(f) || ferror(f))
2269 break;
2271 /* retrieve session name */
2272 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2273 strlcpy(named_session,
2274 &uri[strlen(XT_SAVE_SESSION_ID)],
2275 sizeof named_session);
2276 continue;
2279 if (uri && strlen(uri))
2280 create_new_tab(uri, NULL, 1, -1);
2282 free(uri);
2283 uri = NULL;
2286 /* close open tabs */
2287 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2288 for (;;) {
2289 tt = TAILQ_FIRST(&tabs);
2290 if (tt != ti) {
2291 delete_tab(tt);
2292 continue;
2294 delete_tab(tt);
2295 break;
2297 recalc_tabs();
2300 rv = 0;
2301 done:
2302 if (f)
2303 fclose(f);
2305 return (rv);
2309 restore_saved_tabs(void)
2311 char file[PATH_MAX];
2312 int unlink_file = 0;
2313 struct stat sb;
2314 struct karg a;
2315 int rv = 0;
2317 snprintf(file, sizeof file, "%s/%s",
2318 sessions_dir, XT_RESTART_TABS_FILE);
2319 if (stat(file, &sb) == -1)
2320 a.s = XT_SAVED_TABS_FILE;
2321 else {
2322 unlink_file = 1;
2323 a.s = XT_RESTART_TABS_FILE;
2326 a.i = XT_SES_DONOTHING;
2327 rv = open_tabs(NULL, &a);
2329 if (unlink_file)
2330 unlink(file);
2332 return (rv);
2336 save_tabs(struct tab *t, struct karg *a)
2338 char file[PATH_MAX];
2339 FILE *f;
2340 int num_tabs = 0, i;
2341 struct tab **stabs = NULL;
2343 if (a == NULL)
2344 return (1);
2345 if (a->s == NULL)
2346 snprintf(file, sizeof file, "%s/%s",
2347 sessions_dir, named_session);
2348 else
2349 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2351 if ((f = fopen(file, "w")) == NULL) {
2352 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2353 return (1);
2356 /* save session name */
2357 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2359 /* Save tabs, in the order they are arranged in the notebook. */
2360 num_tabs = sort_tabs_by_page_num(&stabs);
2362 for (i = 0; i < num_tabs; i++)
2363 if (stabs[i] && get_uri(stabs[i]) != NULL)
2364 fprintf(f, "%s\n", get_uri(stabs[i]));
2366 g_free(stabs);
2368 /* try and make sure this gets to disk NOW. XXX Backup first? */
2369 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2370 show_oops(t, "May not have managed to save session: %s",
2371 strerror(errno));
2374 fclose(f);
2376 return (0);
2380 save_tabs_and_quit(struct tab *t, struct karg *args)
2382 struct karg a;
2384 a.s = NULL;
2385 save_tabs(t, &a);
2386 quit(t, NULL);
2388 return (1);
2392 yank_uri(struct tab *t, struct karg *args)
2394 const gchar *uri;
2395 GtkClipboard *clipboard;
2397 if ((uri = get_uri(t)) == NULL)
2398 return (1);
2400 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2401 gtk_clipboard_set_text(clipboard, uri, -1);
2403 return (0);
2407 paste_uri(struct tab *t, struct karg *args)
2409 GtkClipboard *clipboard;
2410 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2411 gint len;
2412 gchar *p = NULL, *uri;
2414 /* try primary clipboard first */
2415 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2416 p = gtk_clipboard_wait_for_text(clipboard);
2418 /* if it failed get whatever text is in cut_buffer0 */
2419 if (p == NULL)
2420 if (gdk_property_get(gdk_get_default_root_window(),
2421 atom,
2422 gdk_atom_intern("STRING", FALSE),
2424 65536 /* picked out of my butt */,
2425 FALSE,
2426 NULL,
2427 NULL,
2428 &len,
2429 (guchar **)&p)) {
2430 /* yes sir, we need to NUL the string */
2431 p[len] = '\0';
2434 if (p) {
2435 uri = p;
2436 while (*uri && isspace(*uri))
2437 uri++;
2438 if (strlen(uri) == 0) {
2439 show_oops(t, "empty paste buffer");
2440 goto done;
2442 if (guess_search == 0 && valid_url_type(uri)) {
2443 /* we can be clever and paste this in search box */
2444 show_oops(t, "not a valid URL");
2445 goto done;
2448 if (args->i == XT_PASTE_CURRENT_TAB)
2449 load_uri(t, uri);
2450 else if (args->i == XT_PASTE_NEW_TAB)
2451 create_new_tab(uri, NULL, 1, -1);
2454 done:
2455 if (p)
2456 g_free(p);
2458 return (0);
2461 char *
2462 find_domain(const gchar *s, int add_dot)
2464 int i;
2465 char *r = NULL, *ss = NULL;
2467 if (s == NULL)
2468 return (NULL);
2470 if (!strncmp(s, "http://", strlen("http://")))
2471 s = &s[strlen("http://")];
2472 else if (!strncmp(s, "https://", strlen("https://")))
2473 s = &s[strlen("https://")];
2475 if (strlen(s) < 2)
2476 return (NULL);
2478 ss = g_strdup(s);
2479 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2480 /* chop string at first slash */
2481 if (ss[i] == '/' || ss[i] == '\0') {
2482 ss[i] = '\0';
2483 if (add_dot)
2484 r = g_strdup_printf(".%s", ss);
2485 else
2486 r = g_strdup(ss);
2487 break;
2489 g_free(ss);
2491 return (r);
2495 toggle_cwl(struct tab *t, struct karg *args)
2497 struct domain *d;
2498 const gchar *uri;
2499 char *dom = NULL, *dom_toggle = NULL;
2500 int es;
2502 if (args == NULL)
2503 return (1);
2505 uri = get_uri(t);
2506 dom = find_domain(uri, 1);
2507 d = wl_find(dom, &c_wl);
2509 if (d == NULL)
2510 es = 0;
2511 else
2512 es = 1;
2514 if (args->i & XT_WL_TOGGLE)
2515 es = !es;
2516 else if ((args->i & XT_WL_ENABLE) && es != 1)
2517 es = 1;
2518 else if ((args->i & XT_WL_DISABLE) && es != 0)
2519 es = 0;
2521 if (args->i & XT_WL_TOPLEVEL)
2522 dom_toggle = get_toplevel_domain(dom);
2523 else
2524 dom_toggle = dom;
2526 if (es)
2527 /* enable cookies for domain */
2528 wl_add(dom_toggle, &c_wl, 0);
2529 else
2530 /* disable cookies for domain */
2531 RB_REMOVE(domain_list, &c_wl, d);
2533 if (args->i & XT_WL_RELOAD)
2534 webkit_web_view_reload(t->wv);
2536 g_free(dom);
2537 return (0);
2541 toggle_js(struct tab *t, struct karg *args)
2543 int es;
2544 const gchar *uri;
2545 struct domain *d;
2546 char *dom = NULL, *dom_toggle = NULL;
2548 if (args == NULL)
2549 return (1);
2551 g_object_get(G_OBJECT(t->settings),
2552 "enable-scripts", &es, (char *)NULL);
2553 if (args->i & XT_WL_TOGGLE)
2554 es = !es;
2555 else if ((args->i & XT_WL_ENABLE) && es != 1)
2556 es = 1;
2557 else if ((args->i & XT_WL_DISABLE) && es != 0)
2558 es = 0;
2559 else
2560 return (1);
2562 uri = get_uri(t);
2563 dom = find_domain(uri, 1);
2565 if (uri == NULL || dom == NULL) {
2566 show_oops(t, "Can't toggle domain in JavaScript white list");
2567 goto done;
2570 if (args->i & XT_WL_TOPLEVEL)
2571 dom_toggle = get_toplevel_domain(dom);
2572 else
2573 dom_toggle = dom;
2575 if (es) {
2576 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2577 wl_add(dom_toggle, &js_wl, 0 /* session */);
2578 } else {
2579 d = wl_find(dom_toggle, &js_wl);
2580 if (d)
2581 RB_REMOVE(domain_list, &js_wl, d);
2582 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2584 g_object_set(G_OBJECT(t->settings),
2585 "enable-scripts", es, (char *)NULL);
2586 g_object_set(G_OBJECT(t->settings),
2587 "javascript-can-open-windows-automatically", es, (char *)NULL);
2588 webkit_web_view_set_settings(t->wv, t->settings);
2590 if (args->i & XT_WL_RELOAD)
2591 webkit_web_view_reload(t->wv);
2592 done:
2593 if (dom)
2594 g_free(dom);
2595 return (0);
2598 void
2599 js_toggle_cb(GtkWidget *w, struct tab *t)
2601 struct karg a;
2603 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2604 toggle_cwl(t, &a);
2606 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2607 toggle_js(t, &a);
2611 toggle_src(struct tab *t, struct karg *args)
2613 gboolean mode;
2615 if (t == NULL)
2616 return (0);
2618 mode = webkit_web_view_get_view_source_mode(t->wv);
2619 webkit_web_view_set_view_source_mode(t->wv, !mode);
2620 webkit_web_view_reload(t->wv);
2622 return (0);
2625 void
2626 focus_webview(struct tab *t)
2628 if (t == NULL)
2629 return;
2631 /* only grab focus if we are visible */
2632 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2633 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2637 focus(struct tab *t, struct karg *args)
2639 if (t == NULL || args == NULL)
2640 return (1);
2642 if (show_url == 0)
2643 return (0);
2645 if (args->i == XT_FOCUS_URI)
2646 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2647 else if (args->i == XT_FOCUS_SEARCH)
2648 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2650 return (0);
2654 stats(struct tab *t, struct karg *args)
2656 char *page, *body, *s, line[64 * 1024];
2657 uint64_t line_count = 0;
2658 FILE *r_cookie_f;
2660 if (t == NULL)
2661 show_oops(NULL, "stats invalid parameters");
2663 line[0] = '\0';
2664 if (save_rejected_cookies) {
2665 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2666 for (;;) {
2667 s = fgets(line, sizeof line, r_cookie_f);
2668 if (s == NULL || feof(r_cookie_f) ||
2669 ferror(r_cookie_f))
2670 break;
2671 line_count++;
2673 fclose(r_cookie_f);
2674 snprintf(line, sizeof line,
2675 "<br/>Cookies blocked(*) total: %llu", line_count);
2676 } else
2677 show_oops(t, "Can't open blocked cookies file: %s",
2678 strerror(errno));
2681 body = g_strdup_printf(
2682 "Cookies blocked(*) this session: %llu"
2683 "%s"
2684 "<p><small><b>*</b> results vary based on settings</small></p>",
2685 blocked_cookies,
2686 line);
2688 page = get_html_page("Statistics", body, "", 0);
2689 g_free(body);
2691 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2692 g_free(page);
2694 return (0);
2698 marco(struct tab *t, struct karg *args)
2700 char *page, line[64 * 1024];
2701 int len;
2703 if (t == NULL)
2704 show_oops(NULL, "marco invalid parameters");
2706 line[0] = '\0';
2707 snprintf(line, sizeof line, "%s", marco_message(&len));
2709 page = get_html_page("Marco Sez...", line, "", 0);
2711 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2712 g_free(page);
2714 return (0);
2718 blank(struct tab *t, struct karg *args)
2720 if (t == NULL)
2721 show_oops(NULL, "blank invalid parameters");
2723 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2725 return (0);
2728 about(struct tab *t, struct karg *args)
2730 char *page, *body;
2732 if (t == NULL)
2733 show_oops(NULL, "about invalid parameters");
2735 body = g_strdup_printf("<b>Version: %s</b><p>"
2736 "Authors:"
2737 "<ul>"
2738 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2739 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2740 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2741 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2742 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2743 "</ul>"
2744 "Copyrights and licenses can be found on the XXXterm "
2745 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2746 version
2749 page = get_html_page("About", body, "", 0);
2750 g_free(body);
2752 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2753 g_free(page);
2755 return (0);
2759 help(struct tab *t, struct karg *args)
2761 char *page, *head, *body;
2763 if (t == NULL)
2764 show_oops(NULL, "help invalid parameters");
2766 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2767 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2768 "</head>\n";
2769 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2770 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2771 "cgi-bin/man-cgi?xxxterm</a>";
2773 page = get_html_page("XXXterm", body, head, FALSE);
2775 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2776 g_free(page);
2778 return (0);
2782 * update all favorite tabs apart from one. Pass NULL if
2783 * you want to update all.
2785 void
2786 update_favorite_tabs(struct tab *apart_from)
2788 struct tab *t;
2789 if (!updating_fl_tabs) {
2790 updating_fl_tabs = 1; /* stop infinite recursion */
2791 TAILQ_FOREACH(t, &tabs, entry)
2792 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2793 && (t != apart_from))
2794 xtp_page_fl(t, NULL);
2795 updating_fl_tabs = 0;
2799 /* show a list of favorites (bookmarks) */
2801 xtp_page_fl(struct tab *t, struct karg *args)
2803 char file[PATH_MAX];
2804 FILE *f;
2805 char *uri = NULL, *title = NULL;
2806 size_t len, lineno = 0;
2807 int i, failed = 0;
2808 char *body, *tmp, *page = NULL;
2809 const char delim[3] = {'\\', '\\', '\0'};
2811 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2813 if (t == NULL)
2814 warn("%s: bad param", __func__);
2816 /* new session key */
2817 if (!updating_fl_tabs)
2818 generate_xtp_session_key(&fl_session_key);
2820 /* open favorites */
2821 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2822 if ((f = fopen(file, "r")) == NULL) {
2823 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2824 return (1);
2827 /* body */
2828 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2829 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2830 "<th style='width: 40px'>Rm</th></tr>\n");
2832 for (i = 1;;) {
2833 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2834 if (feof(f) || ferror(f))
2835 break;
2836 if (len == 0) {
2837 free(title);
2838 title = NULL;
2839 continue;
2842 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2843 if (feof(f) || ferror(f)) {
2844 show_oops(t, "favorites file corrupt");
2845 failed = 1;
2846 break;
2849 tmp = body;
2850 body = g_strdup_printf("%s<tr>"
2851 "<td>%d</td>"
2852 "<td><a href='%s'>%s</a></td>"
2853 "<td style='text-align: center'>"
2854 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2855 "</tr>\n",
2856 body, i, uri, title,
2857 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2859 g_free(tmp);
2861 free(uri);
2862 uri = NULL;
2863 free(title);
2864 title = NULL;
2865 i++;
2867 fclose(f);
2869 /* if none, say so */
2870 if (i == 1) {
2871 tmp = body;
2872 body = g_strdup_printf("%s<tr>"
2873 "<td colspan='3' style='text-align: center'>"
2874 "No favorites - To add one use the 'favadd' command."
2875 "</td></tr>", body);
2876 g_free(tmp);
2879 tmp = body;
2880 body = g_strdup_printf("%s</table>", body);
2881 g_free(tmp);
2883 if (uri)
2884 free(uri);
2885 if (title)
2886 free(title);
2888 /* render */
2889 if (!failed) {
2890 page = get_html_page("Favorites", body, "", 1);
2891 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2892 g_free(page);
2895 update_favorite_tabs(t);
2897 if (body)
2898 g_free(body);
2900 return (failed);
2903 void
2904 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2905 size_t cert_count, char *title)
2907 gnutls_datum_t cinfo;
2908 char *tmp, *body;
2909 int i;
2911 body = g_strdup("");
2913 for (i = 0; i < cert_count; i++) {
2914 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2915 &cinfo))
2916 return;
2918 tmp = body;
2919 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2920 body, i, cinfo.data);
2921 gnutls_free(cinfo.data);
2922 g_free(tmp);
2925 tmp = get_html_page(title, body, "", 0);
2926 g_free(body);
2928 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2929 g_free(tmp);
2933 ca_cmd(struct tab *t, struct karg *args)
2935 FILE *f = NULL;
2936 int rv = 1, certs = 0, certs_read;
2937 struct stat sb;
2938 gnutls_datum_t dt;
2939 gnutls_x509_crt_t *c = NULL;
2940 char *certs_buf = NULL, *s;
2942 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2943 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2944 return (1);
2947 if (fstat(fileno(f), &sb) == -1) {
2948 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2949 goto done;
2952 certs_buf = g_malloc(sb.st_size + 1);
2953 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2954 show_oops(t, "Can't read CA file: %s", strerror(errno));
2955 goto done;
2957 certs_buf[sb.st_size] = '\0';
2959 s = certs_buf;
2960 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2961 certs++;
2962 s += strlen("BEGIN CERTIFICATE");
2965 bzero(&dt, sizeof dt);
2966 dt.data = (unsigned char *)certs_buf;
2967 dt.size = sb.st_size;
2968 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2969 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
2970 GNUTLS_X509_FMT_PEM, 0);
2971 if (certs_read <= 0) {
2972 show_oops(t, "No cert(s) available");
2973 goto done;
2975 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2976 done:
2977 if (c)
2978 g_free(c);
2979 if (certs_buf)
2980 g_free(certs_buf);
2981 if (f)
2982 fclose(f);
2984 return (rv);
2988 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2990 SoupURI *su = NULL;
2991 struct addrinfo hints, *res = NULL, *ai;
2992 int s = -1, on;
2993 char port[8];
2995 if (uri && !g_str_has_prefix(uri, "https://"))
2996 goto done;
2998 su = soup_uri_new(uri);
2999 if (su == NULL)
3000 goto done;
3001 if (!SOUP_URI_VALID_FOR_HTTP(su))
3002 goto done;
3004 snprintf(port, sizeof port, "%d", su->port);
3005 bzero(&hints, sizeof(struct addrinfo));
3006 hints.ai_flags = AI_CANONNAME;
3007 hints.ai_family = AF_UNSPEC;
3008 hints.ai_socktype = SOCK_STREAM;
3010 if (getaddrinfo(su->host, port, &hints, &res))
3011 goto done;
3013 for (ai = res; ai; ai = ai->ai_next) {
3014 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3015 continue;
3017 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3018 if (s < 0)
3019 goto done;
3020 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3021 sizeof(on)) == -1)
3022 goto done;
3024 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3025 goto done;
3028 if (domain)
3029 strlcpy(domain, su->host, domain_sz);
3030 done:
3031 if (su)
3032 soup_uri_free(su);
3033 if (res)
3034 freeaddrinfo(res);
3036 return (s);
3040 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3042 if (gsession)
3043 gnutls_deinit(gsession);
3044 if (xcred)
3045 gnutls_certificate_free_credentials(xcred);
3047 return (0);
3051 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3052 gnutls_certificate_credentials_t *xc)
3054 gnutls_certificate_credentials_t xcred;
3055 gnutls_session_t gsession;
3056 int rv = 1;
3058 if (gs == NULL || xc == NULL)
3059 goto done;
3061 *gs = NULL;
3062 *xc = NULL;
3064 gnutls_certificate_allocate_credentials(&xcred);
3065 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3066 GNUTLS_X509_FMT_PEM);
3067 gnutls_init(&gsession, GNUTLS_CLIENT);
3068 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3069 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3070 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3071 if ((rv = gnutls_handshake(gsession)) < 0) {
3072 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3074 gnutls_error_is_fatal(rv),
3075 gnutls_strerror_name(rv));
3076 stop_tls(gsession, xcred);
3077 goto done;
3080 gnutls_credentials_type_t cred;
3081 cred = gnutls_auth_get_type(gsession);
3082 if (cred != GNUTLS_CRD_CERTIFICATE) {
3083 stop_tls(gsession, xcred);
3084 goto done;
3087 *gs = gsession;
3088 *xc = xcred;
3089 rv = 0;
3090 done:
3091 return (rv);
3095 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3096 size_t *cert_count)
3098 unsigned int len;
3099 const gnutls_datum_t *cl;
3100 gnutls_x509_crt_t *all_certs;
3101 int i, rv = 1;
3103 if (certs == NULL || cert_count == NULL)
3104 goto done;
3105 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3106 goto done;
3107 cl = gnutls_certificate_get_peers(gsession, &len);
3108 if (len == 0)
3109 goto done;
3111 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3112 for (i = 0; i < len; i++) {
3113 gnutls_x509_crt_init(&all_certs[i]);
3114 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3115 GNUTLS_X509_FMT_PEM < 0)) {
3116 g_free(all_certs);
3117 goto done;
3121 *certs = all_certs;
3122 *cert_count = len;
3123 rv = 0;
3124 done:
3125 return (rv);
3128 void
3129 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3131 int i;
3133 for (i = 0; i < cert_count; i++)
3134 gnutls_x509_crt_deinit(certs[i]);
3135 g_free(certs);
3138 void
3139 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3140 size_t cert_count, char *domain)
3142 size_t cert_buf_sz;
3143 char cert_buf[64 * 1024], file[PATH_MAX];
3144 int i;
3145 FILE *f;
3146 GdkColor color;
3148 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3149 return;
3151 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3152 if ((f = fopen(file, "w")) == NULL) {
3153 show_oops(t, "Can't create cert file %s %s",
3154 file, strerror(errno));
3155 return;
3158 for (i = 0; i < cert_count; i++) {
3159 cert_buf_sz = sizeof cert_buf;
3160 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3161 cert_buf, &cert_buf_sz)) {
3162 show_oops(t, "gnutls_x509_crt_export failed");
3163 goto done;
3165 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3166 show_oops(t, "Can't write certs: %s", strerror(errno));
3167 goto done;
3171 /* not the best spot but oh well */
3172 gdk_color_parse("lightblue", &color);
3173 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3174 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3175 gdk_color_parse(XT_COLOR_BLACK, &color);
3176 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3177 done:
3178 fclose(f);
3182 load_compare_cert(struct tab *t, struct karg *args)
3184 const gchar *uri;
3185 char domain[8182], file[PATH_MAX];
3186 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3187 int s = -1, rv = 1, i;
3188 size_t cert_count;
3189 FILE *f = NULL;
3190 size_t cert_buf_sz;
3191 gnutls_session_t gsession;
3192 gnutls_x509_crt_t *certs;
3193 gnutls_certificate_credentials_t xcred;
3195 if (t == NULL)
3196 return (1);
3198 if ((uri = get_uri(t)) == NULL)
3199 return (1);
3201 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3202 return (1);
3204 /* go ssl/tls */
3205 if (start_tls(t, s, &gsession, &xcred)) {
3206 show_oops(t, "Start TLS failed");
3207 goto done;
3210 /* get certs */
3211 if (get_connection_certs(gsession, &certs, &cert_count)) {
3212 show_oops(t, "Can't get connection certificates");
3213 goto done;
3216 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3217 if ((f = fopen(file, "r")) == NULL)
3218 goto freeit;
3220 for (i = 0; i < cert_count; i++) {
3221 cert_buf_sz = sizeof cert_buf;
3222 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3223 cert_buf, &cert_buf_sz)) {
3224 goto freeit;
3226 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3227 rv = -1; /* critical */
3228 goto freeit;
3230 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3231 rv = -1; /* critical */
3232 goto freeit;
3236 rv = 0;
3237 freeit:
3238 if (f)
3239 fclose(f);
3240 free_connection_certs(certs, cert_count);
3241 done:
3242 /* we close the socket first for speed */
3243 if (s != -1)
3244 close(s);
3245 stop_tls(gsession, xcred);
3247 return (rv);
3251 cert_cmd(struct tab *t, struct karg *args)
3253 const gchar *uri;
3254 char domain[8182];
3255 int s = -1;
3256 size_t cert_count;
3257 gnutls_session_t gsession;
3258 gnutls_x509_crt_t *certs;
3259 gnutls_certificate_credentials_t xcred;
3261 if (t == NULL)
3262 return (1);
3264 if (ssl_ca_file == NULL) {
3265 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3266 return (1);
3269 if ((uri = get_uri(t)) == NULL) {
3270 show_oops(t, "Invalid URI");
3271 return (1);
3274 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3275 show_oops(t, "Invalid certificate URI: %s", uri);
3276 return (1);
3279 /* go ssl/tls */
3280 if (start_tls(t, s, &gsession, &xcred)) {
3281 show_oops(t, "Start TLS failed");
3282 goto done;
3285 /* get certs */
3286 if (get_connection_certs(gsession, &certs, &cert_count)) {
3287 show_oops(t, "get_connection_certs failed");
3288 goto done;
3291 if (args->i & XT_SHOW)
3292 show_certs(t, certs, cert_count, "Certificate Chain");
3293 else if (args->i & XT_SAVE)
3294 save_certs(t, certs, cert_count, domain);
3296 free_connection_certs(certs, cert_count);
3297 done:
3298 /* we close the socket first for speed */
3299 if (s != -1)
3300 close(s);
3301 stop_tls(gsession, xcred);
3303 return (0);
3307 remove_cookie(int index)
3309 int i, rv = 1;
3310 GSList *cf;
3311 SoupCookie *c;
3313 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3315 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3317 for (i = 1; cf; cf = cf->next, i++) {
3318 if (i != index)
3319 continue;
3320 c = cf->data;
3321 print_cookie("remove cookie", c);
3322 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3323 rv = 0;
3324 break;
3327 soup_cookies_free(cf);
3329 return (rv);
3333 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3335 struct domain *d;
3336 char *tmp, *body;
3338 body = g_strdup("");
3340 /* p list */
3341 if (args->i & XT_WL_PERSISTENT) {
3342 tmp = body;
3343 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3344 g_free(tmp);
3345 RB_FOREACH(d, domain_list, wl) {
3346 if (d->handy == 0)
3347 continue;
3348 tmp = body;
3349 body = g_strdup_printf("%s%s<br/>", body, d->d);
3350 g_free(tmp);
3354 /* s list */
3355 if (args->i & XT_WL_SESSION) {
3356 tmp = body;
3357 body = g_strdup_printf("%s<h2>Session</h2>", body);
3358 g_free(tmp);
3359 RB_FOREACH(d, domain_list, wl) {
3360 if (d->handy == 1)
3361 continue;
3362 tmp = body;
3363 body = g_strdup_printf("%s%s<br/>", body, d->d);
3364 g_free(tmp);
3368 tmp = get_html_page(title, body, "", 0);
3369 g_free(body);
3370 if (wl == &js_wl)
3371 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3372 else
3373 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3374 g_free(tmp);
3375 return (0);
3379 wl_save(struct tab *t, struct karg *args, int js)
3381 char file[PATH_MAX];
3382 FILE *f;
3383 char *line = NULL, *lt = NULL;
3384 size_t linelen;
3385 const gchar *uri;
3386 char *dom = NULL, *dom_save = NULL;
3387 struct karg a;
3388 struct domain *d;
3389 GSList *cf;
3390 SoupCookie *ci, *c;
3392 if (t == NULL || args == NULL)
3393 return (1);
3395 if (runtime_settings[0] == '\0')
3396 return (1);
3398 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3399 if ((f = fopen(file, "r+")) == NULL)
3400 return (1);
3402 uri = get_uri(t);
3403 dom = find_domain(uri, 1);
3404 if (uri == NULL || dom == NULL) {
3405 show_oops(t, "Can't add domain to %s white list",
3406 js ? "JavaScript" : "cookie");
3407 goto done;
3410 if (args->i & XT_WL_TOPLEVEL) {
3411 /* save domain */
3412 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3413 show_oops(t, "invalid domain: %s", dom);
3414 goto done;
3416 } else if (args->i & XT_WL_FQDN) {
3417 /* save fqdn */
3418 dom_save = dom;
3419 } else
3420 goto done;
3422 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3424 while (!feof(f)) {
3425 line = fparseln(f, &linelen, NULL, NULL, 0);
3426 if (line == NULL)
3427 continue;
3428 if (!strcmp(line, lt))
3429 goto done;
3430 free(line);
3431 line = NULL;
3434 fprintf(f, "%s\n", lt);
3436 a.i = XT_WL_ENABLE;
3437 a.i |= args->i;
3438 if (js) {
3439 d = wl_find(dom_save, &js_wl);
3440 if (!d) {
3441 settings_add("js_wl", dom_save);
3442 d = wl_find(dom_save, &js_wl);
3444 toggle_js(t, &a);
3445 } else {
3446 d = wl_find(dom_save, &c_wl);
3447 if (!d) {
3448 settings_add("cookie_wl", dom_save);
3449 d = wl_find(dom_save, &c_wl);
3451 toggle_cwl(t, &a);
3453 /* find and add to persistent jar */
3454 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3455 for (;cf; cf = cf->next) {
3456 ci = cf->data;
3457 if (!strcmp(dom_save, ci->domain) ||
3458 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3459 c = soup_cookie_copy(ci);
3460 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3463 soup_cookies_free(cf);
3465 if (d)
3466 d->handy = 1;
3468 done:
3469 if (line)
3470 free(line);
3471 if (dom)
3472 g_free(dom);
3473 if (lt)
3474 g_free(lt);
3475 fclose(f);
3477 return (0);
3481 js_show_wl(struct tab *t, struct karg *args)
3483 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3484 wl_show(t, args, "JavaScript White List", &js_wl);
3486 return (0);
3490 cookie_show_wl(struct tab *t, struct karg *args)
3492 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3493 wl_show(t, args, "Cookie White List", &c_wl);
3495 return (0);
3499 cookie_cmd(struct tab *t, struct karg *args)
3501 if (args->i & XT_SHOW)
3502 wl_show(t, args, "Cookie White List", &c_wl);
3503 else if (args->i & XT_WL_TOGGLE) {
3504 args->i |= XT_WL_RELOAD;
3505 toggle_cwl(t, args);
3506 } else if (args->i & XT_SAVE) {
3507 args->i |= XT_WL_RELOAD;
3508 wl_save(t, args, 0);
3509 } else if (args->i & XT_DELETE)
3510 show_oops(t, "'cookie delete' currently unimplemented");
3512 return (0);
3516 js_cmd(struct tab *t, struct karg *args)
3518 if (args->i & XT_SHOW)
3519 wl_show(t, args, "JavaScript White List", &js_wl);
3520 else if (args->i & XT_SAVE) {
3521 args->i |= XT_WL_RELOAD;
3522 wl_save(t, args, 1);
3523 } else if (args->i & XT_WL_TOGGLE) {
3524 args->i |= XT_WL_RELOAD;
3525 toggle_js(t, args);
3526 } else if (args->i & XT_DELETE)
3527 show_oops(t, "'js delete' currently unimplemented");
3529 return (0);
3533 toplevel_cmd(struct tab *t, struct karg *args)
3535 js_toggle_cb(t->js_toggle, t);
3537 return (0);
3541 add_favorite(struct tab *t, struct karg *args)
3543 char file[PATH_MAX];
3544 FILE *f;
3545 char *line = NULL;
3546 size_t urilen, linelen;
3547 const gchar *uri, *title;
3549 if (t == NULL)
3550 return (1);
3552 /* don't allow adding of xtp pages to favorites */
3553 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3554 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3555 return (1);
3558 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3559 if ((f = fopen(file, "r+")) == NULL) {
3560 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3561 return (1);
3564 title = webkit_web_view_get_title(t->wv);
3565 uri = get_uri(t);
3567 if (title == NULL)
3568 title = uri;
3570 if (title == NULL || uri == NULL) {
3571 show_oops(t, "can't add page to favorites");
3572 goto done;
3575 urilen = strlen(uri);
3577 for (;;) {
3578 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3579 if (feof(f) || ferror(f))
3580 break;
3582 if (linelen == urilen && !strcmp(line, uri))
3583 goto done;
3585 free(line);
3586 line = NULL;
3589 fprintf(f, "\n%s\n%s", title, uri);
3590 done:
3591 if (line)
3592 free(line);
3593 fclose(f);
3595 update_favorite_tabs(NULL);
3597 return (0);
3601 navaction(struct tab *t, struct karg *args)
3603 WebKitWebHistoryItem *item;
3605 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3606 t->tab_id, args->i);
3608 if (t->item) {
3609 if (args->i == XT_NAV_BACK)
3610 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3611 else
3612 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3613 if (item == NULL)
3614 return (XT_CB_PASSTHROUGH);
3615 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3616 t->item = NULL;
3617 return (XT_CB_PASSTHROUGH);
3620 switch (args->i) {
3621 case XT_NAV_BACK:
3622 webkit_web_view_go_back(t->wv);
3623 break;
3624 case XT_NAV_FORWARD:
3625 webkit_web_view_go_forward(t->wv);
3626 break;
3627 case XT_NAV_RELOAD:
3628 webkit_web_view_reload(t->wv);
3629 break;
3630 case XT_NAV_RELOAD_CACHE:
3631 webkit_web_view_reload_bypass_cache(t->wv);
3632 break;
3634 return (XT_CB_PASSTHROUGH);
3638 move(struct tab *t, struct karg *args)
3640 GtkAdjustment *adjust;
3641 double pi, si, pos, ps, upper, lower, max;
3643 switch (args->i) {
3644 case XT_MOVE_DOWN:
3645 case XT_MOVE_UP:
3646 case XT_MOVE_BOTTOM:
3647 case XT_MOVE_TOP:
3648 case XT_MOVE_PAGEDOWN:
3649 case XT_MOVE_PAGEUP:
3650 case XT_MOVE_HALFDOWN:
3651 case XT_MOVE_HALFUP:
3652 adjust = t->adjust_v;
3653 break;
3654 default:
3655 adjust = t->adjust_h;
3656 break;
3659 pos = gtk_adjustment_get_value(adjust);
3660 ps = gtk_adjustment_get_page_size(adjust);
3661 upper = gtk_adjustment_get_upper(adjust);
3662 lower = gtk_adjustment_get_lower(adjust);
3663 si = gtk_adjustment_get_step_increment(adjust);
3664 pi = gtk_adjustment_get_page_increment(adjust);
3665 max = upper - ps;
3667 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3668 "max %f si %f pi %f\n",
3669 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3670 pos, ps, upper, lower, max, si, pi);
3672 switch (args->i) {
3673 case XT_MOVE_DOWN:
3674 case XT_MOVE_RIGHT:
3675 pos += si;
3676 gtk_adjustment_set_value(adjust, MIN(pos, max));
3677 break;
3678 case XT_MOVE_UP:
3679 case XT_MOVE_LEFT:
3680 pos -= si;
3681 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3682 break;
3683 case XT_MOVE_BOTTOM:
3684 case XT_MOVE_FARRIGHT:
3685 gtk_adjustment_set_value(adjust, max);
3686 break;
3687 case XT_MOVE_TOP:
3688 case XT_MOVE_FARLEFT:
3689 gtk_adjustment_set_value(adjust, lower);
3690 break;
3691 case XT_MOVE_PAGEDOWN:
3692 pos += pi;
3693 gtk_adjustment_set_value(adjust, MIN(pos, max));
3694 break;
3695 case XT_MOVE_PAGEUP:
3696 pos -= pi;
3697 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3698 break;
3699 case XT_MOVE_HALFDOWN:
3700 pos += pi / 2;
3701 gtk_adjustment_set_value(adjust, MIN(pos, max));
3702 break;
3703 case XT_MOVE_HALFUP:
3704 pos -= pi / 2;
3705 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3706 break;
3707 default:
3708 return (XT_CB_PASSTHROUGH);
3711 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3713 return (XT_CB_HANDLED);
3716 void
3717 url_set_visibility(void)
3719 struct tab *t;
3721 TAILQ_FOREACH(t, &tabs, entry) {
3722 if (show_url == 0) {
3723 gtk_widget_hide(t->toolbar);
3724 focus_webview(t);
3725 } else
3726 gtk_widget_show(t->toolbar);
3730 void
3731 notebook_tab_set_visibility()
3733 if (show_tabs == 0) {
3734 gtk_widget_hide(tab_bar);
3735 gtk_notebook_set_show_tabs(notebook, FALSE);
3736 } else {
3737 if (tab_style == XT_TABS_NORMAL) {
3738 gtk_widget_hide(tab_bar);
3739 gtk_notebook_set_show_tabs(notebook, TRUE);
3740 } else if (tab_style == XT_TABS_COMPACT) {
3741 gtk_widget_show(tab_bar);
3742 gtk_notebook_set_show_tabs(notebook, FALSE);
3747 void
3748 statusbar_set_visibility(void)
3750 struct tab *t;
3752 TAILQ_FOREACH(t, &tabs, entry) {
3753 if (show_statusbar == 0) {
3754 gtk_widget_hide(t->statusbar);
3755 focus_webview(t);
3756 } else
3757 gtk_widget_show(t->statusbar);
3761 void
3762 url_set(struct tab *t, int enable_url_entry)
3764 GdkPixbuf *pixbuf;
3765 int progress;
3767 show_url = enable_url_entry;
3769 if (enable_url_entry) {
3770 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3771 GTK_ENTRY_ICON_PRIMARY, NULL);
3772 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3773 } else {
3774 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3775 GTK_ENTRY_ICON_PRIMARY);
3776 progress =
3777 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3778 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3779 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3780 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3781 progress);
3786 fullscreen(struct tab *t, struct karg *args)
3788 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3790 if (t == NULL)
3791 return (XT_CB_PASSTHROUGH);
3793 if (show_url == 0) {
3794 url_set(t, 1);
3795 show_tabs = 1;
3796 } else {
3797 url_set(t, 0);
3798 show_tabs = 0;
3801 url_set_visibility();
3802 notebook_tab_set_visibility();
3804 return (XT_CB_HANDLED);
3808 statusaction(struct tab *t, struct karg *args)
3810 int rv = XT_CB_HANDLED;
3812 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3814 if (t == NULL)
3815 return (XT_CB_PASSTHROUGH);
3817 switch (args->i) {
3818 case XT_STATUSBAR_SHOW:
3819 if (show_statusbar == 0) {
3820 show_statusbar = 1;
3821 statusbar_set_visibility();
3823 break;
3824 case XT_STATUSBAR_HIDE:
3825 if (show_statusbar == 1) {
3826 show_statusbar = 0;
3827 statusbar_set_visibility();
3829 break;
3831 return (rv);
3835 urlaction(struct tab *t, struct karg *args)
3837 int rv = XT_CB_HANDLED;
3839 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3841 if (t == NULL)
3842 return (XT_CB_PASSTHROUGH);
3844 switch (args->i) {
3845 case XT_URL_SHOW:
3846 if (show_url == 0) {
3847 url_set(t, 1);
3848 url_set_visibility();
3850 break;
3851 case XT_URL_HIDE:
3852 if (show_url == 1) {
3853 url_set(t, 0);
3854 url_set_visibility();
3856 break;
3858 return (rv);
3862 tabaction(struct tab *t, struct karg *args)
3864 int rv = XT_CB_HANDLED;
3865 char *url = args->s;
3866 struct undo *u;
3867 struct tab *tt;
3869 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3871 if (t == NULL)
3872 return (XT_CB_PASSTHROUGH);
3874 switch (args->i) {
3875 case XT_TAB_NEW:
3876 if (strlen(url) > 0)
3877 create_new_tab(url, NULL, 1, args->p);
3878 else
3879 create_new_tab(NULL, NULL, 1, args->p);
3880 break;
3881 case XT_TAB_DELETE:
3882 if (args->p < 0)
3883 delete_tab(t);
3884 else
3885 TAILQ_FOREACH(tt, &tabs, entry)
3886 if (tt->tab_id == args->p - 1) {
3887 delete_tab(tt);
3888 recalc_tabs();
3889 break;
3891 break;
3892 case XT_TAB_DELQUIT:
3893 if (gtk_notebook_get_n_pages(notebook) > 1)
3894 delete_tab(t);
3895 else
3896 quit(t, args);
3897 break;
3898 case XT_TAB_OPEN:
3899 if (strlen(url) > 0)
3901 else {
3902 rv = XT_CB_PASSTHROUGH;
3903 goto done;
3905 load_uri(t, url);
3906 break;
3907 case XT_TAB_SHOW:
3908 if (show_tabs == 0) {
3909 show_tabs = 1;
3910 notebook_tab_set_visibility();
3912 break;
3913 case XT_TAB_HIDE:
3914 if (show_tabs == 1) {
3915 show_tabs = 0;
3916 notebook_tab_set_visibility();
3918 break;
3919 case XT_TAB_NEXTSTYLE:
3920 if (tab_style == XT_TABS_NORMAL)
3921 tab_style = XT_TABS_COMPACT;
3922 else
3923 tab_style = XT_TABS_NORMAL;
3924 notebook_tab_set_visibility();
3925 break;
3926 case XT_TAB_UNDO_CLOSE:
3927 if (undo_count == 0) {
3928 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3929 goto done;
3930 } else {
3931 undo_count--;
3932 u = TAILQ_FIRST(&undos);
3933 create_new_tab(u->uri, u, 1, -1);
3935 TAILQ_REMOVE(&undos, u, entry);
3936 g_free(u->uri);
3937 /* u->history is freed in create_new_tab() */
3938 g_free(u);
3940 break;
3941 default:
3942 rv = XT_CB_PASSTHROUGH;
3943 goto done;
3946 done:
3947 if (args->s) {
3948 g_free(args->s);
3949 args->s = NULL;
3952 return (rv);
3956 resizetab(struct tab *t, struct karg *args)
3958 if (t == NULL || args == NULL) {
3959 show_oops(NULL, "resizetab invalid parameters");
3960 return (XT_CB_PASSTHROUGH);
3963 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3964 t->tab_id, args->i);
3966 adjustfont_webkit(t, args->i);
3968 return (XT_CB_HANDLED);
3972 movetab(struct tab *t, struct karg *args)
3974 int n, dest;
3976 if (t == NULL || args == NULL) {
3977 show_oops(NULL, "movetab invalid parameters");
3978 return (XT_CB_PASSTHROUGH);
3981 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3982 t->tab_id, args->i);
3984 if (args->i >= XT_TAB_INVALID)
3985 return (XT_CB_PASSTHROUGH);
3987 if (TAILQ_EMPTY(&tabs))
3988 return (XT_CB_PASSTHROUGH);
3990 n = gtk_notebook_get_n_pages(notebook);
3991 dest = gtk_notebook_get_current_page(notebook);
3993 switch (args->i) {
3994 case XT_TAB_NEXT:
3995 if (args->p < 0)
3996 dest = dest == n - 1 ? 0 : dest + 1;
3997 else
3998 dest = args->p - 1;
4000 break;
4001 case XT_TAB_PREV:
4002 if (args->p < 0)
4003 dest -= 1;
4004 else
4005 dest -= args->p % n;
4007 if (dest < 0)
4008 dest += n;
4010 break;
4011 case XT_TAB_FIRST:
4012 dest = 0;
4013 break;
4014 case XT_TAB_LAST:
4015 dest = n - 1;
4016 break;
4017 default:
4018 return (XT_CB_PASSTHROUGH);
4021 if (dest < 0 || dest >= n)
4022 return (XT_CB_PASSTHROUGH);
4023 if (t->tab_id == dest) {
4024 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4025 return (XT_CB_HANDLED);
4028 set_current_tab(dest);
4030 return (XT_CB_HANDLED);
4033 int cmd_prefix = 0;
4036 command(struct tab *t, struct karg *args)
4038 char *s = NULL, *ss = NULL;
4039 GdkColor color;
4040 const gchar *uri;
4042 if (t == NULL || args == NULL) {
4043 show_oops(NULL, "command invalid parameters");
4044 return (XT_CB_PASSTHROUGH);
4047 switch (args->i) {
4048 case '/':
4049 s = "/";
4050 break;
4051 case '?':
4052 s = "?";
4053 break;
4054 case ':':
4055 if (cmd_prefix == 0)
4056 s = ":";
4057 else {
4058 ss = g_strdup_printf(":%d", cmd_prefix);
4059 s = ss;
4060 cmd_prefix = 0;
4062 break;
4063 case XT_CMD_OPEN:
4064 s = ":open ";
4065 break;
4066 case XT_CMD_TABNEW:
4067 s = ":tabnew ";
4068 break;
4069 case XT_CMD_OPEN_CURRENT:
4070 s = ":open ";
4071 /* FALL THROUGH */
4072 case XT_CMD_TABNEW_CURRENT:
4073 if (!s) /* FALL THROUGH? */
4074 s = ":tabnew ";
4075 if ((uri = get_uri(t)) != NULL) {
4076 ss = g_strdup_printf("%s%s", s, uri);
4077 s = ss;
4079 break;
4080 default:
4081 show_oops(t, "command: invalid opcode %d", args->i);
4082 return (XT_CB_PASSTHROUGH);
4085 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4087 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4088 gdk_color_parse(XT_COLOR_WHITE, &color);
4089 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4090 show_cmd(t);
4091 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4092 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4094 if (ss)
4095 g_free(ss);
4097 return (XT_CB_HANDLED);
4101 * Return a new string with a download row (in html)
4102 * appended. Old string is freed.
4104 char *
4105 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4108 WebKitDownloadStatus stat;
4109 char *status_html = NULL, *cmd_html = NULL, *new_html;
4110 gdouble progress;
4111 char cur_sz[FMT_SCALED_STRSIZE];
4112 char tot_sz[FMT_SCALED_STRSIZE];
4113 char *xtp_prefix;
4115 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4117 /* All actions wil take this form:
4118 * xxxt://class/seskey
4120 xtp_prefix = g_strdup_printf("%s%d/%s/",
4121 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4123 stat = webkit_download_get_status(dl->download);
4125 switch (stat) {
4126 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4127 status_html = g_strdup_printf("Finished");
4128 cmd_html = g_strdup_printf(
4129 "<a href='%s%d/%d'>Remove</a>",
4130 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4131 break;
4132 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4133 /* gather size info */
4134 progress = 100 * webkit_download_get_progress(dl->download);
4136 fmt_scaled(
4137 webkit_download_get_current_size(dl->download), cur_sz);
4138 fmt_scaled(
4139 webkit_download_get_total_size(dl->download), tot_sz);
4141 status_html = g_strdup_printf(
4142 "<div style='width: 100%%' align='center'>"
4143 "<div class='progress-outer'>"
4144 "<div class='progress-inner' style='width: %.2f%%'>"
4145 "</div></div></div>"
4146 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4147 progress, cur_sz, tot_sz, progress);
4149 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4150 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4152 break;
4153 /* LLL */
4154 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4155 status_html = g_strdup_printf("Cancelled");
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_ERROR:
4160 status_html = g_strdup_printf("Error!");
4161 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4162 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4163 break;
4164 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4165 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4166 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4167 status_html = g_strdup_printf("Starting");
4168 break;
4169 default:
4170 show_oops(t, "%s: unknown download status", __func__);
4173 new_html = g_strdup_printf(
4174 "%s\n<tr><td>%s</td><td>%s</td>"
4175 "<td style='text-align:center'>%s</td></tr>\n",
4176 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4177 status_html, cmd_html);
4178 g_free(html);
4180 if (status_html)
4181 g_free(status_html);
4183 if (cmd_html)
4184 g_free(cmd_html);
4186 g_free(xtp_prefix);
4188 return new_html;
4192 * update all download tabs apart from one. Pass NULL if
4193 * you want to update all.
4195 void
4196 update_download_tabs(struct tab *apart_from)
4198 struct tab *t;
4199 if (!updating_dl_tabs) {
4200 updating_dl_tabs = 1; /* stop infinite recursion */
4201 TAILQ_FOREACH(t, &tabs, entry)
4202 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4203 && (t != apart_from))
4204 xtp_page_dl(t, NULL);
4205 updating_dl_tabs = 0;
4210 * update all cookie tabs apart from one. Pass NULL if
4211 * you want to update all.
4213 void
4214 update_cookie_tabs(struct tab *apart_from)
4216 struct tab *t;
4217 if (!updating_cl_tabs) {
4218 updating_cl_tabs = 1; /* stop infinite recursion */
4219 TAILQ_FOREACH(t, &tabs, entry)
4220 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4221 && (t != apart_from))
4222 xtp_page_cl(t, NULL);
4223 updating_cl_tabs = 0;
4228 * update all history tabs apart from one. Pass NULL if
4229 * you want to update all.
4231 void
4232 update_history_tabs(struct tab *apart_from)
4234 struct tab *t;
4236 if (!updating_hl_tabs) {
4237 updating_hl_tabs = 1; /* stop infinite recursion */
4238 TAILQ_FOREACH(t, &tabs, entry)
4239 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4240 && (t != apart_from))
4241 xtp_page_hl(t, NULL);
4242 updating_hl_tabs = 0;
4246 /* cookie management XTP page */
4248 xtp_page_cl(struct tab *t, struct karg *args)
4250 char *body, *page, *tmp;
4251 int i = 1; /* all ids start 1 */
4252 GSList *sc, *pc, *pc_start;
4253 SoupCookie *c;
4254 char *type, *table_headers, *last_domain;
4256 DNPRINTF(XT_D_CMD, "%s", __func__);
4258 if (t == NULL) {
4259 show_oops(NULL, "%s invalid parameters", __func__);
4260 return (1);
4263 /* Generate a new session key */
4264 if (!updating_cl_tabs)
4265 generate_xtp_session_key(&cl_session_key);
4267 /* table headers */
4268 table_headers = g_strdup_printf("<table><tr>"
4269 "<th>Type</th>"
4270 "<th>Name</th>"
4271 "<th style='width:200px'>Value</th>"
4272 "<th>Path</th>"
4273 "<th>Expires</th>"
4274 "<th>Secure</th>"
4275 "<th>HTTP<br />only</th>"
4276 "<th style='width:40px'>Rm</th></tr>\n");
4278 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4279 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4280 pc_start = pc;
4282 body = NULL;
4283 last_domain = strdup("");
4284 for (; sc; sc = sc->next) {
4285 c = sc->data;
4287 if (strcmp(last_domain, c->domain) != 0) {
4288 /* new domain */
4289 free(last_domain);
4290 last_domain = strdup(c->domain);
4292 if (body != NULL) {
4293 tmp = body;
4294 body = g_strdup_printf("%s</table>"
4295 "<h2>%s</h2>%s\n",
4296 body, c->domain, table_headers);
4297 g_free(tmp);
4298 } else {
4299 /* first domain */
4300 body = g_strdup_printf("<h2>%s</h2>%s\n",
4301 c->domain, table_headers);
4305 type = "Session";
4306 for (pc = pc_start; pc; pc = pc->next)
4307 if (soup_cookie_equal(pc->data, c)) {
4308 type = "Session + Persistent";
4309 break;
4312 tmp = body;
4313 body = g_strdup_printf(
4314 "%s\n<tr>"
4315 "<td>%s</td>"
4316 "<td style='word-wrap:normal'>%s</td>"
4317 "<td>"
4318 " <textarea rows='4'>%s</textarea>"
4319 "</td>"
4320 "<td>%s</td>"
4321 "<td>%s</td>"
4322 "<td>%d</td>"
4323 "<td>%d</td>"
4324 "<td style='text-align:center'>"
4325 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4326 body,
4327 type,
4328 c->name,
4329 c->value,
4330 c->path,
4331 c->expires ?
4332 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4333 c->secure,
4334 c->http_only,
4336 XT_XTP_STR,
4337 XT_XTP_CL,
4338 cl_session_key,
4339 XT_XTP_CL_REMOVE,
4343 g_free(tmp);
4344 i++;
4347 soup_cookies_free(sc);
4348 soup_cookies_free(pc);
4350 /* small message if there are none */
4351 if (i == 1) {
4352 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4353 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4355 tmp = body;
4356 body = g_strdup_printf("%s</table>", body);
4357 g_free(tmp);
4359 page = get_html_page("Cookie Jar", body, "", TRUE);
4360 g_free(body);
4361 g_free(table_headers);
4362 g_free(last_domain);
4364 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4365 update_cookie_tabs(t);
4367 g_free(page);
4369 return (0);
4373 xtp_page_hl(struct tab *t, struct karg *args)
4375 char *body, *page, *tmp;
4376 struct history *h;
4377 int i = 1; /* all ids start 1 */
4379 DNPRINTF(XT_D_CMD, "%s", __func__);
4381 if (t == NULL) {
4382 show_oops(NULL, "%s invalid parameters", __func__);
4383 return (1);
4386 /* Generate a new session key */
4387 if (!updating_hl_tabs)
4388 generate_xtp_session_key(&hl_session_key);
4390 /* body */
4391 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4392 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4394 RB_FOREACH_REVERSE(h, history_list, &hl) {
4395 tmp = body;
4396 body = g_strdup_printf(
4397 "%s\n<tr>"
4398 "<td><a href='%s'>%s</a></td>"
4399 "<td>%s</td>"
4400 "<td style='text-align: center'>"
4401 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4402 body, h->uri, h->uri, h->title,
4403 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4404 XT_XTP_HL_REMOVE, i);
4406 g_free(tmp);
4407 i++;
4410 /* small message if there are none */
4411 if (i == 1) {
4412 tmp = body;
4413 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4414 "colspan='3'>No History</td></tr>\n", body);
4415 g_free(tmp);
4418 tmp = body;
4419 body = g_strdup_printf("%s</table>", body);
4420 g_free(tmp);
4422 page = get_html_page("History", body, "", TRUE);
4423 g_free(body);
4426 * update all history manager tabs as the xtp session
4427 * key has now changed. No need to update the current tab.
4428 * Already did that above.
4430 update_history_tabs(t);
4432 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4433 g_free(page);
4435 return (0);
4439 * Generate a web page detailing the status of any downloads
4442 xtp_page_dl(struct tab *t, struct karg *args)
4444 struct download *dl;
4445 char *body, *page, *tmp;
4446 char *ref;
4447 int n_dl = 1;
4449 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4451 if (t == NULL) {
4452 show_oops(NULL, "%s invalid parameters", __func__);
4453 return (1);
4457 * Generate a new session key for next page instance.
4458 * This only happens for the top level call to xtp_page_dl()
4459 * in which case updating_dl_tabs is 0.
4461 if (!updating_dl_tabs)
4462 generate_xtp_session_key(&dl_session_key);
4464 /* header - with refresh so as to update */
4465 if (refresh_interval >= 1)
4466 ref = g_strdup_printf(
4467 "<meta http-equiv='refresh' content='%u"
4468 ";url=%s%d/%s/%d' />\n",
4469 refresh_interval,
4470 XT_XTP_STR,
4471 XT_XTP_DL,
4472 dl_session_key,
4473 XT_XTP_DL_LIST);
4474 else
4475 ref = g_strdup("");
4477 body = g_strdup_printf("<div align='center'>"
4478 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4479 "</p><table><tr><th style='width: 60%%'>"
4480 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4481 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4483 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4484 body = xtp_page_dl_row(t, body, dl);
4485 n_dl++;
4488 /* message if no downloads in list */
4489 if (n_dl == 1) {
4490 tmp = body;
4491 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4492 " style='text-align: center'>"
4493 "No downloads</td></tr>\n", body);
4494 g_free(tmp);
4497 tmp = body;
4498 body = g_strdup_printf("%s</table></div>", body);
4499 g_free(tmp);
4501 page = get_html_page("Downloads", body, ref, 1);
4502 g_free(ref);
4503 g_free(body);
4506 * update all download manager tabs as the xtp session
4507 * key has now changed. No need to update the current tab.
4508 * Already did that above.
4510 update_download_tabs(t);
4512 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4513 g_free(page);
4515 return (0);
4519 search(struct tab *t, struct karg *args)
4521 gboolean d;
4523 if (t == NULL || args == NULL) {
4524 show_oops(NULL, "search invalid parameters");
4525 return (1);
4527 if (t->search_text == NULL) {
4528 if (global_search == NULL)
4529 return (XT_CB_PASSTHROUGH);
4530 else {
4531 t->search_text = g_strdup(global_search);
4532 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4533 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4537 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4538 t->tab_id, args->i, t->search_forward, t->search_text);
4540 switch (args->i) {
4541 case XT_SEARCH_NEXT:
4542 d = t->search_forward;
4543 break;
4544 case XT_SEARCH_PREV:
4545 d = !t->search_forward;
4546 break;
4547 default:
4548 return (XT_CB_PASSTHROUGH);
4551 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4553 return (XT_CB_HANDLED);
4556 struct settings_args {
4557 char **body;
4558 int i;
4561 void
4562 print_setting(struct settings *s, char *val, void *cb_args)
4564 char *tmp, *color;
4565 struct settings_args *sa = cb_args;
4567 if (sa == NULL)
4568 return;
4570 if (s->flags & XT_SF_RUNTIME)
4571 color = "#22cc22";
4572 else
4573 color = "#cccccc";
4575 tmp = *sa->body;
4576 *sa->body = g_strdup_printf(
4577 "%s\n<tr>"
4578 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4579 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4580 *sa->body,
4581 color,
4582 s->name,
4583 color,
4586 g_free(tmp);
4587 sa->i++;
4591 set(struct tab *t, struct karg *args)
4593 char *body, *page, *tmp;
4594 int i = 1;
4595 struct settings_args sa;
4597 bzero(&sa, sizeof sa);
4598 sa.body = &body;
4600 /* body */
4601 body = g_strdup_printf("<div align='center'><table><tr>"
4602 "<th align='left'>Setting</th>"
4603 "<th align='left'>Value</th></tr>\n");
4605 settings_walk(print_setting, &sa);
4606 i = sa.i;
4608 /* small message if there are none */
4609 if (i == 1) {
4610 tmp = body;
4611 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4612 "colspan='2'>No settings</td></tr>\n", body);
4613 g_free(tmp);
4616 tmp = body;
4617 body = g_strdup_printf("%s</table></div>", body);
4618 g_free(tmp);
4620 page = get_html_page("Settings", body, "", 0);
4622 g_free(body);
4624 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4626 g_free(page);
4628 return (XT_CB_PASSTHROUGH);
4632 session_save(struct tab *t, char *filename)
4634 struct karg a;
4635 int rv = 1;
4637 if (strlen(filename) == 0)
4638 goto done;
4640 if (filename[0] == '.' || filename[0] == '/')
4641 goto done;
4643 a.s = filename;
4644 if (save_tabs(t, &a))
4645 goto done;
4646 strlcpy(named_session, filename, sizeof named_session);
4648 rv = 0;
4649 done:
4650 return (rv);
4654 session_open(struct tab *t, char *filename)
4656 struct karg a;
4657 int rv = 1;
4659 if (strlen(filename) == 0)
4660 goto done;
4662 if (filename[0] == '.' || filename[0] == '/')
4663 goto done;
4665 a.s = filename;
4666 a.i = XT_SES_CLOSETABS;
4667 if (open_tabs(t, &a))
4668 goto done;
4670 strlcpy(named_session, filename, sizeof named_session);
4672 rv = 0;
4673 done:
4674 return (rv);
4678 session_delete(struct tab *t, char *filename)
4680 char file[PATH_MAX];
4681 int rv = 1;
4683 if (strlen(filename) == 0)
4684 goto done;
4686 if (filename[0] == '.' || filename[0] == '/')
4687 goto done;
4689 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4690 if (unlink(file))
4691 goto done;
4693 if (!strcmp(filename, named_session))
4694 strlcpy(named_session, XT_SAVED_TABS_FILE,
4695 sizeof named_session);
4697 rv = 0;
4698 done:
4699 return (rv);
4703 session_cmd(struct tab *t, struct karg *args)
4705 char *filename = args->s;
4707 if (t == NULL)
4708 return (1);
4710 if (args->i & XT_SHOW)
4711 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4712 XT_SAVED_TABS_FILE : named_session);
4713 else if (args->i & XT_SAVE) {
4714 if (session_save(t, filename)) {
4715 show_oops(t, "Can't save session: %s",
4716 filename ? filename : "INVALID");
4717 goto done;
4719 } else if (args->i & XT_OPEN) {
4720 if (session_open(t, filename)) {
4721 show_oops(t, "Can't open session: %s",
4722 filename ? filename : "INVALID");
4723 goto done;
4725 } else if (args->i & XT_DELETE) {
4726 if (session_delete(t, filename)) {
4727 show_oops(t, "Can't delete session: %s",
4728 filename ? filename : "INVALID");
4729 goto done;
4732 done:
4733 return (XT_CB_PASSTHROUGH);
4737 * Make a hardcopy of the page
4740 print_page(struct tab *t, struct karg *args)
4742 WebKitWebFrame *frame;
4743 GtkPageSetup *ps;
4744 GtkPrintOperation *op;
4745 GtkPrintOperationAction action;
4746 GtkPrintOperationResult print_res;
4747 GError *g_err = NULL;
4748 int marg_l, marg_r, marg_t, marg_b;
4750 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4752 ps = gtk_page_setup_new();
4753 op = gtk_print_operation_new();
4754 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4755 frame = webkit_web_view_get_main_frame(t->wv);
4757 /* the default margins are too small, so we will bump them */
4758 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4759 XT_PRINT_EXTRA_MARGIN;
4760 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4761 XT_PRINT_EXTRA_MARGIN;
4762 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4763 XT_PRINT_EXTRA_MARGIN;
4764 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4765 XT_PRINT_EXTRA_MARGIN;
4767 /* set margins */
4768 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4769 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4770 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4771 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4773 gtk_print_operation_set_default_page_setup(op, ps);
4775 /* this appears to free 'op' and 'ps' */
4776 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4778 /* check it worked */
4779 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4780 show_oops(NULL, "can't print: %s", g_err->message);
4781 g_error_free (g_err);
4782 return (1);
4785 return (0);
4789 go_home(struct tab *t, struct karg *args)
4791 load_uri(t, home);
4792 return (0);
4796 restart(struct tab *t, struct karg *args)
4798 struct karg a;
4800 a.s = XT_RESTART_TABS_FILE;
4801 save_tabs(t, &a);
4802 execvp(start_argv[0], start_argv);
4803 /* NOTREACHED */
4805 return (0);
4808 #define CTRL GDK_CONTROL_MASK
4809 #define MOD1 GDK_MOD1_MASK
4810 #define SHFT GDK_SHIFT_MASK
4812 /* inherent to GTK not all keys will be caught at all times */
4813 /* XXX sort key bindings */
4814 struct key_binding {
4815 char *cmd;
4816 guint mask;
4817 guint use_in_entry;
4818 guint key;
4819 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4820 } keys[] = {
4821 { "cookiejar", MOD1, 0, GDK_j },
4822 { "downloadmgr", MOD1, 0, GDK_d },
4823 { "history", MOD1, 0, GDK_h },
4824 { "print", CTRL, 0, GDK_p },
4825 { "search", 0, 0, GDK_slash },
4826 { "searchb", 0, 0, GDK_question },
4827 { "command", 0, 0, GDK_colon },
4828 { "qa", CTRL, 0, GDK_q },
4829 { "restart", MOD1, 0, GDK_q },
4830 { "js toggle", CTRL, 0, GDK_j },
4831 { "cookie toggle", MOD1, 0, GDK_c },
4832 { "togglesrc", CTRL, 0, GDK_s },
4833 { "yankuri", 0, 0, GDK_y },
4834 { "pasteuricur", 0, 0, GDK_p },
4835 { "pasteurinew", 0, 0, GDK_P },
4836 { "toplevel toggle", 0, 0, GDK_F4 },
4837 { "help", 0, 0, GDK_F1 },
4839 /* search */
4840 { "searchnext", 0, 0, GDK_n },
4841 { "searchprevious", 0, 0, GDK_N },
4843 /* focus */
4844 { "focusaddress", 0, 0, GDK_F6 },
4845 { "focussearch", 0, 0, GDK_F7 },
4847 /* hinting */
4848 { "hinting", 0, 0, GDK_f },
4850 /* custom stylesheet */
4851 { "userstyle", 0, 0, GDK_i },
4853 /* navigation */
4854 { "goback", 0, 0, GDK_BackSpace },
4855 { "goback", MOD1, 0, GDK_Left },
4856 { "goforward", SHFT, 0, GDK_BackSpace },
4857 { "goforward", MOD1, 0, GDK_Right },
4858 { "reload", 0, 0, GDK_F5 },
4859 { "reload", CTRL, 0, GDK_r },
4860 { "reloadforce", CTRL, 0, GDK_R },
4861 { "reload", CTRL, 0, GDK_l },
4862 { "favorites", MOD1, 1, GDK_f },
4864 /* vertical movement */
4865 { "scrolldown", 0, 0, GDK_j },
4866 { "scrolldown", 0, 0, GDK_Down },
4867 { "scrollup", 0, 0, GDK_Up },
4868 { "scrollup", 0, 0, GDK_k },
4869 { "scrollbottom", 0, 0, GDK_G },
4870 { "scrollbottom", 0, 0, GDK_End },
4871 { "scrolltop", 0, 0, GDK_Home },
4872 { "scrolltop", 0, 0, GDK_g },
4873 { "scrollpagedown", 0, 0, GDK_space },
4874 { "scrollpagedown", CTRL, 0, GDK_f },
4875 { "scrollhalfdown", CTRL, 0, GDK_d },
4876 { "scrollpagedown", 0, 0, GDK_Page_Down },
4877 { "scrollpageup", 0, 0, GDK_Page_Up },
4878 { "scrollpageup", CTRL, 0, GDK_b },
4879 { "scrollhalfup", CTRL, 0, GDK_u },
4880 /* horizontal movement */
4881 { "scrollright", 0, 0, GDK_l },
4882 { "scrollright", 0, 0, GDK_Right },
4883 { "scrollleft", 0, 0, GDK_Left },
4884 { "scrollleft", 0, 0, GDK_h },
4885 { "scrollfarright", 0, 0, GDK_dollar },
4886 { "scrollfarleft", 0, 0, GDK_0 },
4888 /* tabs */
4889 { "tabnew", CTRL, 0, GDK_t },
4890 { "999tabnew", CTRL, 0, GDK_T },
4891 { "tabclose", CTRL, 1, GDK_w },
4892 { "tabundoclose", 0, 0, GDK_U },
4893 { "tabnext 1", CTRL, 0, GDK_1 },
4894 { "tabnext 2", CTRL, 0, GDK_2 },
4895 { "tabnext 3", CTRL, 0, GDK_3 },
4896 { "tabnext 4", CTRL, 0, GDK_4 },
4897 { "tabnext 5", CTRL, 0, GDK_5 },
4898 { "tabnext 6", CTRL, 0, GDK_6 },
4899 { "tabnext 7", CTRL, 0, GDK_7 },
4900 { "tabnext 8", CTRL, 0, GDK_8 },
4901 { "tabnext 9", CTRL, 0, GDK_9 },
4902 { "tabnext 10", CTRL, 0, GDK_0 },
4903 { "tabfirst", CTRL, 0, GDK_less },
4904 { "tablast", CTRL, 0, GDK_greater },
4905 { "tabprevious", CTRL, 0, GDK_Left },
4906 { "tabnext", CTRL, 0, GDK_Right },
4907 { "focusout", CTRL, 0, GDK_minus },
4908 { "focusin", CTRL, 0, GDK_plus },
4909 { "focusin", CTRL, 0, GDK_equal },
4911 /* command aliases (handy when -S flag is used) */
4912 { "promptopen", 0, 0, GDK_F9 },
4913 { "promptopencurrent", 0, 0, GDK_F10 },
4914 { "prompttabnew", 0, 0, GDK_F11 },
4915 { "prompttabnewcurrent",0, 0, GDK_F12 },
4917 TAILQ_HEAD(keybinding_list, key_binding);
4919 void
4920 walk_kb(struct settings *s,
4921 void (*cb)(struct settings *, char *, void *), void *cb_args)
4923 struct key_binding *k;
4924 char str[1024];
4926 if (s == NULL || cb == NULL) {
4927 show_oops(NULL, "walk_kb invalid parameters");
4928 return;
4931 TAILQ_FOREACH(k, &kbl, entry) {
4932 if (k->cmd == NULL)
4933 continue;
4934 str[0] = '\0';
4936 /* sanity */
4937 if (gdk_keyval_name(k->key) == NULL)
4938 continue;
4940 strlcat(str, k->cmd, sizeof str);
4941 strlcat(str, ",", sizeof str);
4943 if (k->mask & GDK_SHIFT_MASK)
4944 strlcat(str, "S-", sizeof str);
4945 if (k->mask & GDK_CONTROL_MASK)
4946 strlcat(str, "C-", sizeof str);
4947 if (k->mask & GDK_MOD1_MASK)
4948 strlcat(str, "M1-", sizeof str);
4949 if (k->mask & GDK_MOD2_MASK)
4950 strlcat(str, "M2-", sizeof str);
4951 if (k->mask & GDK_MOD3_MASK)
4952 strlcat(str, "M3-", sizeof str);
4953 if (k->mask & GDK_MOD4_MASK)
4954 strlcat(str, "M4-", sizeof str);
4955 if (k->mask & GDK_MOD5_MASK)
4956 strlcat(str, "M5-", sizeof str);
4958 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4959 cb(s, str, cb_args);
4963 void
4964 init_keybindings(void)
4966 int i;
4967 struct key_binding *k;
4969 for (i = 0; i < LENGTH(keys); i++) {
4970 k = g_malloc0(sizeof *k);
4971 k->cmd = keys[i].cmd;
4972 k->mask = keys[i].mask;
4973 k->use_in_entry = keys[i].use_in_entry;
4974 k->key = keys[i].key;
4975 TAILQ_INSERT_HEAD(&kbl, k, entry);
4977 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4978 k->cmd ? k->cmd : "unnamed key");
4982 void
4983 keybinding_clearall(void)
4985 struct key_binding *k, *next;
4987 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4988 next = TAILQ_NEXT(k, entry);
4989 if (k->cmd == NULL)
4990 continue;
4992 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4993 k->cmd ? k->cmd : "unnamed key");
4994 TAILQ_REMOVE(&kbl, k, entry);
4995 g_free(k);
5000 keybinding_add(char *cmd, char *key, int use_in_entry)
5002 struct key_binding *k;
5003 guint keyval, mask = 0;
5004 int i;
5006 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5008 /* Keys which are to be used in entry have been prefixed with an
5009 * exclamation mark. */
5010 if (use_in_entry)
5011 key++;
5013 /* find modifier keys */
5014 if (strstr(key, "S-"))
5015 mask |= GDK_SHIFT_MASK;
5016 if (strstr(key, "C-"))
5017 mask |= GDK_CONTROL_MASK;
5018 if (strstr(key, "M1-"))
5019 mask |= GDK_MOD1_MASK;
5020 if (strstr(key, "M2-"))
5021 mask |= GDK_MOD2_MASK;
5022 if (strstr(key, "M3-"))
5023 mask |= GDK_MOD3_MASK;
5024 if (strstr(key, "M4-"))
5025 mask |= GDK_MOD4_MASK;
5026 if (strstr(key, "M5-"))
5027 mask |= GDK_MOD5_MASK;
5029 /* find keyname */
5030 for (i = strlen(key) - 1; i > 0; i--)
5031 if (key[i] == '-')
5032 key = &key[i + 1];
5034 /* validate keyname */
5035 keyval = gdk_keyval_from_name(key);
5036 if (keyval == GDK_VoidSymbol) {
5037 warnx("invalid keybinding name %s", key);
5038 return (1);
5040 /* must run this test too, gtk+ doesn't handle 10 for example */
5041 if (gdk_keyval_name(keyval) == NULL) {
5042 warnx("invalid keybinding name %s", key);
5043 return (1);
5046 /* Remove eventual dupes. */
5047 TAILQ_FOREACH(k, &kbl, entry)
5048 if (k->key == keyval && k->mask == mask) {
5049 TAILQ_REMOVE(&kbl, k, entry);
5050 g_free(k);
5051 break;
5054 /* add keyname */
5055 k = g_malloc0(sizeof *k);
5056 k->cmd = g_strdup(cmd);
5057 k->mask = mask;
5058 k->use_in_entry = use_in_entry;
5059 k->key = keyval;
5061 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5062 k->cmd,
5063 k->mask,
5064 k->use_in_entry,
5065 k->key);
5066 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5067 k->cmd, gdk_keyval_name(keyval));
5069 TAILQ_INSERT_HEAD(&kbl, k, entry);
5071 return (0);
5075 add_kb(struct settings *s, char *entry)
5077 char *kb, *key;
5079 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5081 /* clearall is special */
5082 if (!strcmp(entry, "clearall")) {
5083 keybinding_clearall();
5084 return (0);
5087 kb = strstr(entry, ",");
5088 if (kb == NULL)
5089 return (1);
5090 *kb = '\0';
5091 key = kb + 1;
5093 return (keybinding_add(entry, key, key[0] == '!'));
5096 struct cmd {
5097 char *cmd;
5098 int level;
5099 int (*func)(struct tab *, struct karg *);
5100 int arg;
5101 int type;
5102 } cmds[] = {
5103 { "command", 0, command, ':', 0 },
5104 { "search", 0, command, '/', 0 },
5105 { "searchb", 0, command, '?', 0 },
5106 { "togglesrc", 0, toggle_src, 0, 0 },
5108 /* yanking and pasting */
5109 { "yankuri", 0, yank_uri, 0, 0 },
5110 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5111 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5112 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5114 /* search */
5115 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5116 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5118 /* focus */
5119 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5120 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5122 /* hinting */
5123 { "hinting", 0, hint, 0, 0 },
5125 /* custom stylesheet */
5126 { "userstyle", 0, userstyle, 0, 0 },
5128 /* navigation */
5129 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5130 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5131 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5132 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5134 /* vertical movement */
5135 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5136 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5137 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5138 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5139 { "1", 0, move, XT_MOVE_TOP, 0 },
5140 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5141 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5142 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5143 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5144 /* horizontal movement */
5145 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5146 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5147 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5148 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5151 { "favorites", 0, xtp_page_fl, 0, 0 },
5152 { "fav", 0, xtp_page_fl, 0, 0 },
5153 { "favadd", 0, add_favorite, 0, 0 },
5155 { "qall", 0, quit, 0, 0 },
5156 { "quitall", 0, quit, 0, 0 },
5157 { "w", 0, save_tabs, 0, 0 },
5158 { "wq", 0, save_tabs_and_quit, 0, 0 },
5159 { "help", 0, help, 0, 0 },
5160 { "about", 0, about, 0, 0 },
5161 { "stats", 0, stats, 0, 0 },
5162 { "version", 0, about, 0, 0 },
5164 /* js command */
5165 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5166 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5167 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5168 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5169 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5170 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5171 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5172 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5173 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5174 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5175 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5177 /* cookie command */
5178 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5179 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5180 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5181 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5182 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5183 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5184 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5185 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5186 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5187 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5188 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5190 /* toplevel (domain) command */
5191 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5192 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5194 /* cookie jar */
5195 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5197 /* cert command */
5198 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5199 { "save", 1, cert_cmd, XT_SAVE, 0 },
5200 { "show", 1, cert_cmd, XT_SHOW, 0 },
5202 { "ca", 0, ca_cmd, 0, 0 },
5203 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5204 { "dl", 0, xtp_page_dl, 0, 0 },
5205 { "h", 0, xtp_page_hl, 0, 0 },
5206 { "history", 0, xtp_page_hl, 0, 0 },
5207 { "home", 0, go_home, 0, 0 },
5208 { "restart", 0, restart, 0, 0 },
5209 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5210 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5211 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5212 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5214 { "print", 0, print_page, 0, 0 },
5216 /* tabs */
5217 { "focusin", 0, resizetab, 1, 0 },
5218 { "focusout", 0, resizetab, -1, 0 },
5219 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5220 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5221 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5222 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5223 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5224 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5225 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5226 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5227 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5228 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5229 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5230 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5231 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5232 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5233 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5234 { "buffers", 0, buffers, 0, 0 },
5235 { "ls", 0, buffers, 0, 0 },
5236 { "tabs", 0, buffers, 0, 0 },
5238 /* command aliases (handy when -S flag is used) */
5239 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5240 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5241 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5242 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5244 /* settings */
5245 { "set", 0, set, 0, 0 },
5246 { "fullscreen", 0, fullscreen, 0, 0 },
5247 { "f", 0, fullscreen, 0, 0 },
5249 /* sessions */
5250 { "session", 0, session_cmd, XT_SHOW, 0 },
5251 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5252 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5253 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5254 { "show", 1, session_cmd, XT_SHOW, 0 },
5257 struct {
5258 int index;
5259 int len;
5260 gchar *list[256];
5261 } cmd_status = {-1, 0};
5263 gboolean
5264 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5266 struct karg a;
5268 hide_oops(t);
5269 hide_buffers(t);
5271 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5272 /* go backward */
5273 a.i = XT_NAV_BACK;
5274 navaction(t, &a);
5276 return (TRUE);
5277 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5278 /* go forward */
5279 a.i = XT_NAV_FORWARD;
5280 navaction(t, &a);
5282 return (TRUE);
5285 return (FALSE);
5288 gboolean
5289 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5291 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5293 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5294 delete_tab(t);
5296 return (FALSE);
5300 * cancel, remove, etc. downloads
5302 void
5303 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5305 struct download find, *d = NULL;
5307 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5309 /* some commands require a valid download id */
5310 if (cmd != XT_XTP_DL_LIST) {
5311 /* lookup download in question */
5312 find.id = id;
5313 d = RB_FIND(download_list, &downloads, &find);
5315 if (d == NULL) {
5316 show_oops(t, "%s: no such download", __func__);
5317 return;
5321 /* decide what to do */
5322 switch (cmd) {
5323 case XT_XTP_DL_CANCEL:
5324 webkit_download_cancel(d->download);
5325 break;
5326 case XT_XTP_DL_REMOVE:
5327 webkit_download_cancel(d->download); /* just incase */
5328 g_object_unref(d->download);
5329 RB_REMOVE(download_list, &downloads, d);
5330 break;
5331 case XT_XTP_DL_LIST:
5332 /* Nothing */
5333 break;
5334 default:
5335 show_oops(t, "%s: unknown command", __func__);
5336 break;
5338 xtp_page_dl(t, NULL);
5342 * Actions on history, only does one thing for now, but
5343 * we provide the function for future actions
5345 void
5346 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5348 struct history *h, *next;
5349 int i = 1;
5351 switch (cmd) {
5352 case XT_XTP_HL_REMOVE:
5353 /* walk backwards, as listed in reverse */
5354 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5355 next = RB_PREV(history_list, &hl, h);
5356 if (id == i) {
5357 RB_REMOVE(history_list, &hl, h);
5358 g_free((gpointer) h->title);
5359 g_free((gpointer) h->uri);
5360 g_free(h);
5361 break;
5363 i++;
5365 break;
5366 case XT_XTP_HL_LIST:
5367 /* Nothing - just xtp_page_hl() below */
5368 break;
5369 default:
5370 show_oops(t, "%s: unknown command", __func__);
5371 break;
5374 xtp_page_hl(t, NULL);
5377 /* remove a favorite */
5378 void
5379 remove_favorite(struct tab *t, int index)
5381 char file[PATH_MAX], *title, *uri = NULL;
5382 char *new_favs, *tmp;
5383 FILE *f;
5384 int i;
5385 size_t len, lineno;
5387 /* open favorites */
5388 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5390 if ((f = fopen(file, "r")) == NULL) {
5391 show_oops(t, "%s: can't open favorites: %s",
5392 __func__, strerror(errno));
5393 return;
5396 /* build a string which will become the new favroites file */
5397 new_favs = g_strdup("");
5399 for (i = 1;;) {
5400 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5401 if (feof(f) || ferror(f))
5402 break;
5403 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5404 if (len == 0) {
5405 free(title);
5406 title = NULL;
5407 continue;
5410 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5411 if (feof(f) || ferror(f)) {
5412 show_oops(t, "%s: can't parse favorites %s",
5413 __func__, strerror(errno));
5414 goto clean;
5418 /* as long as this isn't the one we are deleting add to file */
5419 if (i != index) {
5420 tmp = new_favs;
5421 new_favs = g_strdup_printf("%s%s\n%s\n",
5422 new_favs, title, uri);
5423 g_free(tmp);
5426 free(uri);
5427 uri = NULL;
5428 free(title);
5429 title = NULL;
5430 i++;
5432 fclose(f);
5434 /* write back new favorites file */
5435 if ((f = fopen(file, "w")) == NULL) {
5436 show_oops(t, "%s: can't open favorites: %s",
5437 __func__, strerror(errno));
5438 goto clean;
5441 fwrite(new_favs, strlen(new_favs), 1, f);
5442 fclose(f);
5444 clean:
5445 if (uri)
5446 free(uri);
5447 if (title)
5448 free(title);
5450 g_free(new_favs);
5453 void
5454 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5456 switch (cmd) {
5457 case XT_XTP_FL_LIST:
5458 /* nothing, just the below call to xtp_page_fl() */
5459 break;
5460 case XT_XTP_FL_REMOVE:
5461 remove_favorite(t, arg);
5462 break;
5463 default:
5464 show_oops(t, "%s: invalid favorites command", __func__);
5465 break;
5468 xtp_page_fl(t, NULL);
5471 void
5472 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5474 switch (cmd) {
5475 case XT_XTP_CL_LIST:
5476 /* nothing, just xtp_page_cl() */
5477 break;
5478 case XT_XTP_CL_REMOVE:
5479 remove_cookie(arg);
5480 break;
5481 default:
5482 show_oops(t, "%s: unknown cookie xtp command", __func__);
5483 break;
5486 xtp_page_cl(t, NULL);
5489 /* link an XTP class to it's session key and handler function */
5490 struct xtp_despatch {
5491 uint8_t xtp_class;
5492 char **session_key;
5493 void (*handle_func)(struct tab *, uint8_t, int);
5496 struct xtp_despatch xtp_despatches[] = {
5497 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5498 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5499 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5500 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5501 { XT_XTP_INVALID, NULL, NULL }
5505 * is the url xtp protocol? (xxxt://)
5506 * if so, parse and despatch correct bahvior
5509 parse_xtp_url(struct tab *t, const char *url)
5511 char *dup = NULL, *p, *last;
5512 uint8_t n_tokens = 0;
5513 char *tokens[4] = {NULL, NULL, NULL, ""};
5514 struct xtp_despatch *dsp, *dsp_match = NULL;
5515 uint8_t req_class;
5516 int ret = FALSE;
5519 * tokens array meaning:
5520 * tokens[0] = class
5521 * tokens[1] = session key
5522 * tokens[2] = action
5523 * tokens[3] = optional argument
5526 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5528 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5529 goto clean;
5531 dup = g_strdup(url + strlen(XT_XTP_STR));
5533 /* split out the url */
5534 for ((p = strtok_r(dup, "/", &last)); p;
5535 (p = strtok_r(NULL, "/", &last))) {
5536 if (n_tokens < 4)
5537 tokens[n_tokens++] = p;
5540 /* should be atleast three fields 'class/seskey/command/arg' */
5541 if (n_tokens < 3)
5542 goto clean;
5544 dsp = xtp_despatches;
5545 req_class = atoi(tokens[0]);
5546 while (dsp->xtp_class) {
5547 if (dsp->xtp_class == req_class) {
5548 dsp_match = dsp;
5549 break;
5551 dsp++;
5554 /* did we find one atall? */
5555 if (dsp_match == NULL) {
5556 show_oops(t, "%s: no matching xtp despatch found", __func__);
5557 goto clean;
5560 /* check session key and call despatch function */
5561 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5562 ret = TRUE; /* all is well, this was a valid xtp request */
5563 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5566 clean:
5567 if (dup)
5568 g_free(dup);
5570 return (ret);
5575 void
5576 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5578 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5580 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5582 if (t == NULL) {
5583 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5584 return;
5587 if (uri == NULL) {
5588 show_oops(t, "activate_uri_entry_cb no uri");
5589 return;
5592 uri += strspn(uri, "\t ");
5594 /* if xxxt:// treat specially */
5595 if (parse_xtp_url(t, uri))
5596 return;
5598 /* otherwise continue to load page normally */
5599 load_uri(t, (gchar *)uri);
5600 focus_webview(t);
5603 void
5604 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5606 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5607 char *newuri = NULL;
5608 gchar *enc_search;
5610 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5612 if (t == NULL) {
5613 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5614 return;
5617 if (search_string == NULL) {
5618 show_oops(t, "no search_string");
5619 return;
5622 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5623 newuri = g_strdup_printf(search_string, enc_search);
5624 g_free(enc_search);
5626 webkit_web_view_load_uri(t->wv, newuri);
5627 focus_webview(t);
5629 if (newuri)
5630 g_free(newuri);
5633 void
5634 check_and_set_js(const gchar *uri, struct tab *t)
5636 struct domain *d = NULL;
5637 int es = 0;
5639 if (uri == NULL || t == NULL)
5640 return;
5642 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5643 es = 0;
5644 else
5645 es = 1;
5647 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5648 es ? "enable" : "disable", uri);
5650 g_object_set(G_OBJECT(t->settings),
5651 "enable-scripts", es, (char *)NULL);
5652 g_object_set(G_OBJECT(t->settings),
5653 "javascript-can-open-windows-automatically", es, (char *)NULL);
5654 webkit_web_view_set_settings(t->wv, t->settings);
5656 button_set_stockid(t->js_toggle,
5657 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5660 void
5661 show_ca_status(struct tab *t, const char *uri)
5663 WebKitWebFrame *frame;
5664 WebKitWebDataSource *source;
5665 WebKitNetworkRequest *request;
5666 SoupMessage *message;
5667 GdkColor color;
5668 gchar *col_str = XT_COLOR_WHITE;
5669 int r;
5671 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5672 ssl_strict_certs, ssl_ca_file, uri);
5674 if (uri == NULL)
5675 goto done;
5676 if (ssl_ca_file == NULL) {
5677 if (g_str_has_prefix(uri, "http://"))
5678 goto done;
5679 if (g_str_has_prefix(uri, "https://")) {
5680 col_str = XT_COLOR_RED;
5681 goto done;
5683 return;
5685 if (g_str_has_prefix(uri, "http://") ||
5686 !g_str_has_prefix(uri, "https://"))
5687 goto done;
5689 frame = webkit_web_view_get_main_frame(t->wv);
5690 source = webkit_web_frame_get_data_source(frame);
5691 request = webkit_web_data_source_get_request(source);
5692 message = webkit_network_request_get_message(request);
5694 if (message && (soup_message_get_flags(message) &
5695 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5696 col_str = XT_COLOR_GREEN;
5697 goto done;
5698 } else {
5699 r = load_compare_cert(t, NULL);
5700 if (r == 0)
5701 col_str = XT_COLOR_BLUE;
5702 else if (r == 1)
5703 col_str = XT_COLOR_YELLOW;
5704 else
5705 col_str = XT_COLOR_RED;
5706 goto done;
5708 done:
5709 if (col_str) {
5710 gdk_color_parse(col_str, &color);
5711 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5713 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5714 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5715 &color);
5716 gdk_color_parse(XT_COLOR_BLACK, &color);
5717 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5718 &color);
5719 } else {
5720 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5721 &color);
5722 gdk_color_parse(XT_COLOR_BLACK, &color);
5723 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5724 &color);
5729 void
5730 free_favicon(struct tab *t)
5732 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5733 __func__, t->icon_download, t->icon_request);
5735 if (t->icon_request)
5736 g_object_unref(t->icon_request);
5737 if (t->icon_dest_uri)
5738 g_free(t->icon_dest_uri);
5740 t->icon_request = NULL;
5741 t->icon_dest_uri = NULL;
5744 void
5745 xt_icon_from_name(struct tab *t, gchar *name)
5747 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5748 GTK_ENTRY_ICON_PRIMARY, "text-html");
5749 if (show_url == 0)
5750 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5751 GTK_ENTRY_ICON_PRIMARY, "text-html");
5752 else
5753 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5754 GTK_ENTRY_ICON_PRIMARY, NULL);
5757 void
5758 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5760 GdkPixbuf *pb_scaled;
5762 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5763 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16, GDK_INTERP_BILINEAR);
5764 else
5765 pb_scaled = pb;
5767 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5768 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5769 if (show_url == 0)
5770 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5771 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5772 else
5773 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5774 GTK_ENTRY_ICON_PRIMARY, NULL);
5776 if (pb_scaled != pb)
5777 g_object_unref(pb_scaled);
5780 void
5781 xt_icon_from_file(struct tab *t, char *file)
5783 GdkPixbuf *pb;
5785 if (g_str_has_prefix(file, "file://"))
5786 file += strlen("file://");
5788 pb = gdk_pixbuf_new_from_file(file, NULL);
5789 if (pb) {
5790 xt_icon_from_pixbuf(t, pb);
5791 g_object_unref(pb);
5792 } else
5793 xt_icon_from_name(t, "text-html");
5796 gboolean
5797 is_valid_icon(char *file)
5799 gboolean valid = 0;
5800 const char *mime_type;
5801 GFileInfo *fi;
5802 GFile *gf;
5804 gf = g_file_new_for_path(file);
5805 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5806 NULL, NULL);
5807 mime_type = g_file_info_get_content_type(fi);
5808 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5809 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5810 g_strcmp0(mime_type, "image/png") == 0 ||
5811 g_strcmp0(mime_type, "image/gif") == 0 ||
5812 g_strcmp0(mime_type, "application/octet-stream") == 0;
5813 g_object_unref(fi);
5814 g_object_unref(gf);
5816 return (valid);
5819 void
5820 set_favicon_from_file(struct tab *t, char *file)
5822 struct stat sb;
5824 if (t == NULL || file == NULL)
5825 return;
5827 if (g_str_has_prefix(file, "file://"))
5828 file += strlen("file://");
5829 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5831 if (!stat(file, &sb)) {
5832 if (sb.st_size == 0 || !is_valid_icon(file)) {
5833 /* corrupt icon so trash it */
5834 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5835 __func__, file);
5836 unlink(file);
5837 /* no need to set icon to default here */
5838 return;
5841 xt_icon_from_file(t, file);
5844 void
5845 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5846 WebKitWebView *wv)
5848 WebKitDownloadStatus status = webkit_download_get_status(download);
5849 struct tab *tt = NULL, *t = NULL;
5852 * find the webview instead of passing in the tab as it could have been
5853 * deleted from underneath us.
5855 TAILQ_FOREACH(tt, &tabs, entry) {
5856 if (tt->wv == wv) {
5857 t = tt;
5858 break;
5861 if (t == NULL)
5862 return;
5864 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5865 __func__, t->tab_id, status);
5867 switch (status) {
5868 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5869 /* -1 */
5870 t->icon_download = NULL;
5871 free_favicon(t);
5872 break;
5873 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5874 /* 0 */
5875 break;
5876 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5877 /* 1 */
5878 break;
5879 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5880 /* 2 */
5881 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5882 __func__, t->tab_id);
5883 t->icon_download = NULL;
5884 free_favicon(t);
5885 break;
5886 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5887 /* 3 */
5889 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5890 __func__, t->icon_dest_uri);
5891 set_favicon_from_file(t, t->icon_dest_uri);
5892 /* these will be freed post callback */
5893 t->icon_request = NULL;
5894 t->icon_download = NULL;
5895 break;
5896 default:
5897 break;
5901 void
5902 abort_favicon_download(struct tab *t)
5904 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5906 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5907 if (t->icon_download) {
5908 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5909 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5910 webkit_download_cancel(t->icon_download);
5911 t->icon_download = NULL;
5912 } else
5913 free_favicon(t);
5916 xt_icon_from_name(t, "text-html");
5919 void
5920 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5922 GdkPixbuf *pb;
5923 gchar *name_hash, file[PATH_MAX];
5924 struct stat sb;
5926 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5928 if (uri == NULL || t == NULL)
5929 return;
5931 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5932 /* take icon from WebKitIconDatabase */
5933 pb = webkit_web_view_get_icon_pixbuf(wv);
5934 if (pb) {
5935 xt_icon_from_pixbuf(t, pb);
5936 g_object_unref(pb);
5937 } else
5938 xt_icon_from_name(t, "text-html");
5940 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5941 /* download icon to cache dir */
5942 if (t->icon_request) {
5943 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5944 return;
5947 /* check to see if we got the icon in cache */
5948 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5949 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5950 g_free(name_hash);
5952 if (!stat(file, &sb)) {
5953 if (sb.st_size > 0) {
5954 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5955 __func__, file);
5956 set_favicon_from_file(t, file);
5957 return;
5960 /* corrupt icon so trash it */
5961 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5962 __func__, file);
5963 unlink(file);
5966 /* create download for icon */
5967 t->icon_request = webkit_network_request_new(uri);
5968 if (t->icon_request == NULL) {
5969 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5970 __func__, uri);
5971 return;
5974 t->icon_download = webkit_download_new(t->icon_request);
5975 if (t->icon_download == NULL)
5976 return;
5978 /* we have to free icon_dest_uri later */
5979 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5980 webkit_download_set_destination_uri(t->icon_download,
5981 t->icon_dest_uri);
5983 if (webkit_download_get_status(t->icon_download) ==
5984 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5985 g_object_unref(t->icon_request);
5986 g_free(t->icon_dest_uri);
5987 t->icon_request = NULL;
5988 t->icon_dest_uri = NULL;
5989 return;
5992 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5993 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5995 webkit_download_start(t->icon_download);
5999 void
6000 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6002 const gchar *set = NULL, *uri = NULL, *title = NULL;
6003 struct history *h, find;
6004 const gchar *s_loading;
6005 struct karg a;
6007 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6008 webkit_web_view_get_load_status(wview), get_uri(t) ? get_uri(t) : "NOTHING");
6010 if (t == NULL) {
6011 show_oops(NULL, "notify_load_status_cb invalid parameters");
6012 return;
6015 switch (webkit_web_view_get_load_status(wview)) {
6016 case WEBKIT_LOAD_PROVISIONAL:
6017 /* 0 */
6018 abort_favicon_download(t);
6019 #if GTK_CHECK_VERSION(2, 20, 0)
6020 gtk_widget_show(t->spinner);
6021 gtk_spinner_start(GTK_SPINNER(t->spinner));
6022 #endif
6023 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6025 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6027 /* take focus if we are visible */
6028 focus_webview(t);
6029 t->focus_wv = 1;
6031 break;
6033 case WEBKIT_LOAD_COMMITTED:
6034 /* 1 */
6035 if ((uri = get_uri(t)) != NULL) {
6036 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6038 if (t->status) {
6039 g_free(t->status);
6040 t->status = NULL;
6042 set_status(t, (char *)uri, XT_STATUS_LOADING);
6045 /* check if js white listing is enabled */
6046 if (enable_js_whitelist) {
6047 uri = get_uri(t);
6048 check_and_set_js(uri, t);
6051 if (t->styled)
6052 apply_style(t);
6054 show_ca_status(t, uri);
6056 /* we know enough to autosave the session */
6057 if (session_autosave) {
6058 a.s = NULL;
6059 save_tabs(t, &a);
6061 break;
6063 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6064 /* 3 */
6065 break;
6067 case WEBKIT_LOAD_FINISHED:
6068 /* 2 */
6069 uri = get_uri(t);
6070 if (uri == NULL)
6071 return;
6073 if (!strncmp(uri, "http://", strlen("http://")) ||
6074 !strncmp(uri, "https://", strlen("https://")) ||
6075 !strncmp(uri, "file://", strlen("file://"))) {
6076 find.uri = uri;
6077 h = RB_FIND(history_list, &hl, &find);
6078 if (!h) {
6079 title = webkit_web_view_get_title(wview);
6080 set = title ? title: uri;
6081 h = g_malloc(sizeof *h);
6082 h->uri = g_strdup(uri);
6083 h->title = g_strdup(set);
6084 RB_INSERT(history_list, &hl, h);
6085 completion_add_uri(h->uri);
6086 update_history_tabs(NULL);
6090 set_status(t, (char *)uri, XT_STATUS_URI);
6091 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6092 case WEBKIT_LOAD_FAILED:
6093 /* 4 */
6094 #endif
6095 #if GTK_CHECK_VERSION(2, 20, 0)
6096 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6097 gtk_widget_hide(t->spinner);
6098 #endif
6099 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
6100 if (s_loading && !strcmp(s_loading, "Loading"))
6101 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6102 default:
6103 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6104 break;
6107 if (t->item)
6108 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6109 else
6110 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6111 webkit_web_view_can_go_back(wview));
6113 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6114 webkit_web_view_can_go_forward(wview));
6117 void
6118 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6120 const gchar *set = NULL, *title = NULL;
6122 title = webkit_web_view_get_title(wview);
6123 set = title ? title : get_uri(t);
6124 if (set) {
6125 gtk_label_set_text(GTK_LABEL(t->label), set);
6126 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), set);
6127 gtk_window_set_title(GTK_WINDOW(main_window), set);
6128 } else {
6129 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6130 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), "(untitled)");
6131 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6135 void
6136 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6138 run_script(t, JS_HINTING);
6141 void
6142 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6144 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6145 progress == 100 ? 0 : (double)progress / 100);
6146 if (show_url == 0) {
6147 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
6148 progress == 100 ? 0 : (double)progress / 100);
6153 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6154 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6155 WebKitWebPolicyDecision *pd, struct tab *t)
6157 char *uri;
6158 WebKitWebNavigationReason reason;
6159 struct domain *d = NULL;
6161 if (t == NULL) {
6162 show_oops(NULL, "webview_npd_cb invalid parameters");
6163 return (FALSE);
6166 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6167 t->ctrl_click,
6168 webkit_network_request_get_uri(request));
6170 uri = (char *)webkit_network_request_get_uri(request);
6172 /* if this is an xtp url, we don't load anything else */
6173 if (parse_xtp_url(t, uri))
6174 return (TRUE);
6176 if (t->ctrl_click) {
6177 t->ctrl_click = 0;
6178 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6179 webkit_web_policy_decision_ignore(pd);
6180 return (TRUE); /* we made the decission */
6184 * This is a little hairy but it comes down to this:
6185 * when we run in whitelist mode we have to assist the browser in
6186 * opening the URL that it would have opened in a new tab.
6188 reason = webkit_web_navigation_action_get_reason(na);
6189 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6190 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6191 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6192 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6193 load_uri(t, uri);
6194 webkit_web_policy_decision_use(pd);
6195 return (TRUE); /* we made the decision */
6198 return (FALSE);
6201 WebKitWebView *
6202 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6204 struct tab *tt;
6205 struct domain *d = NULL;
6206 const gchar *uri;
6207 WebKitWebView *webview = NULL;
6209 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6210 webkit_web_view_get_uri(wv));
6212 if (tabless) {
6213 /* open in current tab */
6214 webview = t->wv;
6215 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6216 uri = webkit_web_view_get_uri(wv);
6217 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6218 return (NULL);
6220 tt = create_new_tab(NULL, NULL, 1, -1);
6221 webview = tt->wv;
6222 } else if (enable_scripts == 1) {
6223 tt = create_new_tab(NULL, NULL, 1, -1);
6224 webview = tt->wv;
6227 return (webview);
6230 gboolean
6231 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6233 const gchar *uri;
6234 struct domain *d = NULL;
6236 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6238 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6239 uri = webkit_web_view_get_uri(wv);
6240 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6241 return (FALSE);
6243 delete_tab(t);
6244 } else if (enable_scripts == 1)
6245 delete_tab(t);
6247 return (TRUE);
6251 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6253 /* we can not eat the event without throwing gtk off so defer it */
6255 /* catch middle click */
6256 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6257 t->ctrl_click = 1;
6258 goto done;
6261 /* catch ctrl click */
6262 if (e->type == GDK_BUTTON_RELEASE &&
6263 CLEAN(e->state) == GDK_CONTROL_MASK)
6264 t->ctrl_click = 1;
6265 else
6266 t->ctrl_click = 0;
6267 done:
6268 return (XT_CB_PASSTHROUGH);
6272 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6274 struct mime_type *m;
6276 m = find_mime_type(mime_type);
6277 if (m == NULL)
6278 return (1);
6279 if (m->mt_download)
6280 return (1);
6282 switch (fork()) {
6283 case -1:
6284 show_oops(t, "can't fork mime handler");
6285 /* NOTREACHED */
6286 case 0:
6287 break;
6288 default:
6289 return (0);
6292 /* child */
6293 execlp(m->mt_action, m->mt_action,
6294 webkit_network_request_get_uri(request), (void *)NULL);
6296 _exit(0);
6298 /* NOTREACHED */
6299 return (0);
6302 const gchar *
6303 get_mime_type(char *file)
6305 const char *mime_type;
6306 GFileInfo *fi;
6307 GFile *gf;
6309 if (g_str_has_prefix(file, "file://"))
6310 file += strlen("file://");
6312 gf = g_file_new_for_path(file);
6313 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6314 NULL, NULL);
6315 mime_type = g_file_info_get_content_type(fi);
6316 g_object_unref(fi);
6317 g_object_unref(gf);
6319 return (mime_type);
6323 run_download_mimehandler(char *mime_type, char *file)
6325 struct mime_type *m;
6327 m = find_mime_type(mime_type);
6328 if (m == NULL)
6329 return (1);
6331 switch (fork()) {
6332 case -1:
6333 show_oops(NULL, "can't fork download mime handler");
6334 return (1);
6335 /* NOTREACHED */
6336 case 0:
6337 break;
6338 default:
6339 return (0);
6342 /* child */
6343 if (g_str_has_prefix(file, "file://"))
6344 file += strlen("file://");
6345 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6347 _exit(0);
6349 /* NOTREACHED */
6350 return (0);
6353 void
6354 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6355 WebKitWebView *wv)
6357 WebKitDownloadStatus status;
6358 const gchar *file = NULL, *mime = NULL;
6360 if (download == NULL)
6361 return;
6362 status = webkit_download_get_status(download);
6363 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6364 return;
6366 file = webkit_download_get_destination_uri(download);
6367 if (file == NULL)
6368 return;
6369 mime = get_mime_type((char *)file);
6370 if (mime == NULL)
6371 return;
6373 run_download_mimehandler((char *)mime, (char *)file);
6377 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6378 WebKitNetworkRequest *request, char *mime_type,
6379 WebKitWebPolicyDecision *decision, struct tab *t)
6381 if (t == NULL) {
6382 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6383 return (FALSE);
6386 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6387 t->tab_id, mime_type);
6389 if (run_mimehandler(t, mime_type, request) == 0) {
6390 webkit_web_policy_decision_ignore(decision);
6391 focus_webview(t);
6392 return (TRUE);
6395 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6396 webkit_web_policy_decision_download(decision);
6397 return (TRUE);
6400 return (FALSE);
6404 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6405 struct tab *t)
6407 struct stat sb;
6408 const gchar *suggested_name;
6409 gchar *filename = NULL;
6410 char *uri = NULL;
6411 struct download *download_entry;
6412 int i, ret = TRUE;
6414 if (wk_download == NULL || t == NULL) {
6415 show_oops(NULL, "%s invalid parameters", __func__);
6416 return (FALSE);
6419 suggested_name = webkit_download_get_suggested_filename(wk_download);
6420 if (suggested_name == NULL)
6421 return (FALSE); /* abort download */
6423 i = 0;
6424 do {
6425 if (filename) {
6426 g_free(filename);
6427 filename = NULL;
6429 if (i) {
6430 g_free(uri);
6431 uri = NULL;
6432 filename = g_strdup_printf("%d%s", i, suggested_name);
6434 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6435 filename : suggested_name);
6436 i++;
6437 } while (!stat(uri + strlen("file://"), &sb));
6439 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6440 "local %s\n", __func__, t->tab_id, filename, uri);
6442 webkit_download_set_destination_uri(wk_download, uri);
6444 if (webkit_download_get_status(wk_download) ==
6445 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6446 show_oops(t, "%s: download failed to start", __func__);
6447 ret = FALSE;
6448 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6449 } else {
6450 /* connect "download first" mime handler */
6451 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6452 G_CALLBACK(download_status_changed_cb), NULL);
6454 download_entry = g_malloc(sizeof(struct download));
6455 download_entry->download = wk_download;
6456 download_entry->tab = t;
6457 download_entry->id = next_download_id++;
6458 RB_INSERT(download_list, &downloads, download_entry);
6459 /* get from history */
6460 g_object_ref(wk_download);
6461 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6462 show_oops(t, "Download of '%s' started...",
6463 basename((char *)webkit_download_get_destination_uri(wk_download)));
6466 if (uri)
6467 g_free(uri);
6469 if (filename)
6470 g_free(filename);
6472 /* sync other download manager tabs */
6473 update_download_tabs(NULL);
6476 * NOTE: never redirect/render the current tab before this
6477 * function returns. This will cause the download to never start.
6479 return (ret); /* start download */
6482 void
6483 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6485 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6487 if (t == NULL) {
6488 show_oops(NULL, "webview_hover_cb");
6489 return;
6492 if (uri)
6493 set_status(t, uri, XT_STATUS_LINK);
6494 else {
6495 if (t->status)
6496 set_status(t, t->status, XT_STATUS_NOTHING);
6500 gboolean
6501 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6503 struct key_binding *k;
6505 TAILQ_FOREACH(k, &kbl, entry)
6506 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6507 if (k->mask == 0) {
6508 if ((e->state & (CTRL | MOD1)) == 0)
6509 return (cmd_execute(t, k->cmd));
6510 } else if ((e->state & k->mask) == k->mask) {
6511 return (cmd_execute(t, k->cmd));
6515 return (XT_CB_PASSTHROUGH);
6519 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6521 char s[2], buf[128];
6522 const char *errstr = NULL;
6523 long long link;
6525 /* don't use w directly; use t->whatever instead */
6527 if (t == NULL) {
6528 show_oops(NULL, "wv_keypress_after_cb");
6529 return (XT_CB_PASSTHROUGH);
6532 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6533 e->keyval, e->state, t);
6535 if (t->hints_on) {
6536 /* ESC */
6537 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6538 disable_hints(t);
6539 return (XT_CB_HANDLED);
6542 /* RETURN */
6543 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6544 link = strtonum(t->hint_num, 1, 1000, &errstr);
6545 if (errstr) {
6546 /* we have a string */
6547 } else {
6548 /* we have a number */
6549 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6550 t->hint_num);
6551 run_script(t, buf);
6553 disable_hints(t);
6556 /* BACKSPACE */
6557 /* XXX unfuck this */
6558 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6559 if (t->hint_mode == XT_HINT_NUMERICAL) {
6560 /* last input was numerical */
6561 int l;
6562 l = strlen(t->hint_num);
6563 if (l > 0) {
6564 l--;
6565 if (l == 0) {
6566 disable_hints(t);
6567 enable_hints(t);
6568 } else {
6569 t->hint_num[l] = '\0';
6570 goto num;
6573 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6574 /* last input was alphanumerical */
6575 int l;
6576 l = strlen(t->hint_buf);
6577 if (l > 0) {
6578 l--;
6579 if (l == 0) {
6580 disable_hints(t);
6581 enable_hints(t);
6582 } else {
6583 t->hint_buf[l] = '\0';
6584 goto anum;
6587 } else {
6588 /* bogus */
6589 disable_hints(t);
6593 /* numerical input */
6594 if (CLEAN(e->state) == 0 &&
6595 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6596 snprintf(s, sizeof s, "%c", e->keyval);
6597 strlcat(t->hint_num, s, sizeof t->hint_num);
6598 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6599 t->hint_num);
6600 num:
6601 link = strtonum(t->hint_num, 1, 1000, &errstr);
6602 if (errstr) {
6603 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6604 disable_hints(t);
6605 } else {
6606 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6607 t->hint_num);
6608 t->hint_mode = XT_HINT_NUMERICAL;
6609 run_script(t, buf);
6612 /* empty the counter buffer */
6613 bzero(t->hint_buf, sizeof t->hint_buf);
6614 return (XT_CB_HANDLED);
6617 /* alphanumerical input */
6618 if (
6619 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6620 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6621 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6622 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6623 snprintf(s, sizeof s, "%c", e->keyval);
6624 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6625 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6626 t->hint_buf);
6627 anum:
6628 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6629 run_script(t, buf);
6631 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6632 t->hint_buf);
6633 t->hint_mode = XT_HINT_ALPHANUM;
6634 run_script(t, buf);
6636 /* empty the counter buffer */
6637 bzero(t->hint_num, sizeof t->hint_num);
6638 return (XT_CB_HANDLED);
6641 return (XT_CB_HANDLED);
6642 } else {
6643 /* prefix input*/
6644 snprintf(s, sizeof s, "%c", e->keyval);
6645 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6646 cmd_prefix = 10 * cmd_prefix + atoi(s);
6650 return (handle_keypress(t, e, 0));
6654 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6656 hide_oops(t);
6658 /* Hide buffers, if they are visible, with escape. */
6659 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6660 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6661 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6662 hide_buffers(t);
6663 return (XT_CB_HANDLED);
6666 return (XT_CB_PASSTHROUGH);
6670 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6672 const gchar *c = gtk_entry_get_text(w);
6673 GdkColor color;
6674 int forward = TRUE;
6676 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6677 e->keyval, e->state, t);
6679 if (t == NULL) {
6680 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6681 return (XT_CB_PASSTHROUGH);
6684 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6685 e->keyval, e->state, t);
6687 if (c[0] == ':')
6688 goto done;
6689 if (strlen(c) == 1) {
6690 webkit_web_view_unmark_text_matches(t->wv);
6691 goto done;
6694 if (c[0] == '/')
6695 forward = TRUE;
6696 else if (c[0] == '?')
6697 forward = FALSE;
6698 else
6699 goto done;
6701 /* search */
6702 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6703 FALSE) {
6704 /* not found, mark red */
6705 gdk_color_parse(XT_COLOR_RED, &color);
6706 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6707 /* unmark and remove selection */
6708 webkit_web_view_unmark_text_matches(t->wv);
6709 /* my kingdom for a way to unselect text in webview */
6710 } else {
6711 /* found, highlight all */
6712 webkit_web_view_unmark_text_matches(t->wv);
6713 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6714 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6715 gdk_color_parse(XT_COLOR_WHITE, &color);
6716 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6718 done:
6719 return (XT_CB_PASSTHROUGH);
6722 gboolean
6723 match_uri(const gchar *uri, const gchar *key) {
6724 gchar *voffset;
6725 size_t len;
6726 gboolean match = FALSE;
6728 len = strlen(key);
6730 if (!strncmp(key, uri, len))
6731 match = TRUE;
6732 else {
6733 voffset = strstr(uri, "/") + 2;
6734 if (!strncmp(key, voffset, len))
6735 match = TRUE;
6736 else if (g_str_has_prefix(voffset, "www.")) {
6737 voffset = voffset + strlen("www.");
6738 if (!strncmp(key, voffset, len))
6739 match = TRUE;
6743 return (match);
6746 void
6747 cmd_getlist(int id, char *key)
6749 int i, dep, c = 0;
6750 struct history *h;
6752 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6753 RB_FOREACH_REVERSE(h, history_list, &hl)
6754 if (match_uri(h->uri, key)) {
6755 cmd_status.list[c] = (char *)h->uri;
6756 if (++c > 255)
6757 break;
6760 cmd_status.len = c;
6761 return;
6764 dep = (id == -1) ? 0 : cmds[id].level + 1;
6766 for (i = id + 1; i < LENGTH(cmds); i++) {
6767 if(cmds[i].level < dep)
6768 break;
6769 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6770 cmd_status.list[c++] = cmds[i].cmd;
6774 cmd_status.len = c;
6777 char *
6778 cmd_getnext(int dir)
6780 cmd_status.index += dir;
6782 if (cmd_status.index < 0)
6783 cmd_status.index = cmd_status.len - 1;
6784 else if (cmd_status.index >= cmd_status.len)
6785 cmd_status.index = 0;
6787 return cmd_status.list[cmd_status.index];
6791 cmd_tokenize(char *s, char *tokens[])
6793 int i = 0;
6794 char *tok, *last;
6795 size_t len = strlen(s);
6796 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6798 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6799 tokens[i] = tok;
6801 if (blank && i < 3)
6802 tokens[i++] = "";
6804 return (i);
6807 void
6808 cmd_complete(struct tab *t, char *str, int dir)
6810 GtkEntry *w = GTK_ENTRY(t->cmd);
6811 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6812 char *tok, *match, *s = g_strdup(str);
6813 char *tokens[3];
6814 char res[XT_MAX_URL_LENGTH + 32] = ":";
6815 char *sc = s;
6817 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6819 /* copy prefix*/
6820 for (i = 0; isdigit(s[i]); i++)
6821 res[i + 1] = s[i];
6823 for (; isspace(s[i]); i++)
6824 res[i + 1] = s[i];
6826 s += i;
6828 levels = cmd_tokenize(s, tokens);
6830 for (i = 0; i < levels - 1; i++) {
6831 tok = tokens[i];
6832 matchcount = 0;
6833 for (j = c; j < LENGTH(cmds); j++) {
6834 if (cmds[j].level < dep)
6835 break;
6836 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6837 matchcount++;
6838 c = j + 1;
6839 if (strlen(tok) == strlen(cmds[j].cmd)) {
6840 matchcount = 1;
6841 break;
6846 if (matchcount == 1) {
6847 strlcat(res, tok, sizeof res);
6848 strlcat(res, " ", sizeof res);
6849 dep++;
6850 } else {
6851 g_free(sc);
6852 return;
6855 parent = c - 1;
6858 if (cmd_status.index == -1)
6859 cmd_getlist(parent, tokens[i]);
6861 if (cmd_status.len > 0) {
6862 match = cmd_getnext(dir);
6863 strlcat(res, match, sizeof res);
6864 gtk_entry_set_text(w, res);
6865 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6868 g_free(sc);
6871 gboolean
6872 cmd_execute(struct tab *t, char *str)
6874 struct cmd *cmd = NULL;
6875 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6876 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6877 struct karg arg = {0, NULL, -1};
6878 int rv = XT_CB_PASSTHROUGH;
6880 sc = s;
6882 /* copy prefix*/
6883 for (j = 0; j<3 && isdigit(s[j]); j++)
6884 prefixstr[j]=s[j];
6886 prefixstr[j]='\0';
6888 s += j;
6889 while (isspace(s[0]))
6890 s++;
6892 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6893 prefix = atoi(prefixstr);
6894 else
6895 s = sc;
6897 for (tok = strtok_r(s, " ", &last); tok;
6898 tok = strtok_r(NULL, " ", &last)) {
6899 matchcount = 0;
6900 for (j = c; j < LENGTH(cmds); j++) {
6901 if (cmds[j].level < dep)
6902 break;
6903 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6904 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6905 matchcount++;
6906 c = j + 1;
6907 cmd = &cmds[j];
6908 if (len == strlen(cmds[j].cmd)) {
6909 matchcount = 1;
6910 break;
6914 if (matchcount == 1) {
6915 if (cmd->type > 0)
6916 goto execute_cmd;
6917 dep++;
6918 } else {
6919 show_oops(t, "Invalid command: %s", str);
6920 goto done;
6923 execute_cmd:
6924 arg.i = cmd->arg;
6926 if (prefix != -1)
6927 arg.p = prefix;
6928 else if (cmd_prefix > 0)
6929 arg.p = cmd_prefix;
6931 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6932 show_oops(t, "No prefix allowed: %s", str);
6933 goto done;
6935 if (cmd->type > 1)
6936 arg.s = last ? g_strdup(last) : g_strdup("");
6937 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6938 arg.p = atoi(arg.s);
6939 if (arg.p <= 0) {
6940 if (arg.s[0]=='0')
6941 show_oops(t, "Zero count");
6942 else
6943 show_oops(t, "Trailing characters");
6944 goto done;
6948 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6950 cmd->func(t, &arg);
6952 rv = XT_CB_HANDLED;
6953 done:
6954 if (j > 0)
6955 cmd_prefix = 0;
6956 g_free(sc);
6957 if (arg.s)
6958 g_free(arg.s);
6960 return (rv);
6964 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6966 if (t == NULL) {
6967 show_oops(NULL, "entry_key_cb invalid parameters");
6968 return (XT_CB_PASSTHROUGH);
6971 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6972 e->keyval, e->state, t);
6974 hide_oops(t);
6976 if (e->keyval == GDK_Escape) {
6977 /* don't use focus_webview(t) because we want to type :cmds */
6978 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6981 return (handle_keypress(t, e, 1));
6985 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6987 int rv = XT_CB_HANDLED;
6988 const gchar *c = gtk_entry_get_text(w);
6990 if (t == NULL) {
6991 show_oops(NULL, "cmd_keypress_cb parameters");
6992 return (XT_CB_PASSTHROUGH);
6995 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6996 e->keyval, e->state, t);
6998 /* sanity */
6999 if (c == NULL)
7000 e->keyval = GDK_Escape;
7001 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7002 e->keyval = GDK_Escape;
7004 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
7005 cmd_status.index = -1;
7007 switch (e->keyval) {
7008 case GDK_Tab:
7009 if (c[0] == ':')
7010 cmd_complete(t, (char *)&c[1], 1);
7011 goto done;
7012 case GDK_ISO_Left_Tab:
7013 if (c[0] == ':')
7014 cmd_complete(t, (char *)&c[1], -1);
7016 goto done;
7017 case GDK_BackSpace:
7018 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7019 break;
7020 /* FALLTHROUGH */
7021 case GDK_Escape:
7022 hide_cmd(t);
7023 focus_webview(t);
7025 /* cancel search */
7026 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7027 webkit_web_view_unmark_text_matches(t->wv);
7028 goto done;
7031 rv = XT_CB_PASSTHROUGH;
7032 done:
7033 return (rv);
7037 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7039 if (t == NULL) {
7040 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7041 return (XT_CB_PASSTHROUGH);
7043 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7045 hide_cmd(t);
7046 hide_oops(t);
7048 if (show_url == 0 || t->focus_wv)
7049 focus_webview(t);
7050 else
7051 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7053 return (XT_CB_PASSTHROUGH);
7056 void
7057 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7059 char *s;
7060 const gchar *c = gtk_entry_get_text(entry);
7062 if (t == NULL) {
7063 show_oops(NULL, "cmd_activate_cb invalid parameters");
7064 return;
7067 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7069 hide_cmd(t);
7071 /* sanity */
7072 if (c == NULL)
7073 goto done;
7074 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7075 goto done;
7076 if (strlen(c) < 2)
7077 goto done;
7078 s = (char *)&c[1];
7080 if (c[0] == '/' || c[0] == '?') {
7081 if (t->search_text) {
7082 g_free(t->search_text);
7083 t->search_text = NULL;
7086 t->search_text = g_strdup(s);
7087 if (global_search)
7088 g_free(global_search);
7089 global_search = g_strdup(s);
7090 t->search_forward = c[0] == '/';
7092 goto done;
7095 cmd_execute(t, s);
7097 done:
7098 return;
7101 void
7102 backward_cb(GtkWidget *w, struct tab *t)
7104 struct karg a;
7106 if (t == NULL) {
7107 show_oops(NULL, "backward_cb invalid parameters");
7108 return;
7111 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7113 a.i = XT_NAV_BACK;
7114 navaction(t, &a);
7117 void
7118 forward_cb(GtkWidget *w, struct tab *t)
7120 struct karg a;
7122 if (t == NULL) {
7123 show_oops(NULL, "forward_cb invalid parameters");
7124 return;
7127 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7129 a.i = XT_NAV_FORWARD;
7130 navaction(t, &a);
7133 void
7134 home_cb(GtkWidget *w, struct tab *t)
7136 if (t == NULL) {
7137 show_oops(NULL, "home_cb invalid parameters");
7138 return;
7141 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7143 load_uri(t, home);
7146 void
7147 stop_cb(GtkWidget *w, struct tab *t)
7149 WebKitWebFrame *frame;
7151 if (t == NULL) {
7152 show_oops(NULL, "stop_cb invalid parameters");
7153 return;
7156 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7158 frame = webkit_web_view_get_main_frame(t->wv);
7159 if (frame == NULL) {
7160 show_oops(t, "stop_cb: no frame");
7161 return;
7164 webkit_web_frame_stop_loading(frame);
7165 abort_favicon_download(t);
7168 void
7169 setup_webkit(struct tab *t)
7171 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7172 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7173 FALSE, (char *)NULL);
7174 else
7175 warnx("webkit does not have \"enable-dns-prefetching\" property");
7176 g_object_set(G_OBJECT(t->settings),
7177 "user-agent", t->user_agent, (char *)NULL);
7178 g_object_set(G_OBJECT(t->settings),
7179 "enable-scripts", enable_scripts, (char *)NULL);
7180 g_object_set(G_OBJECT(t->settings),
7181 "enable-plugins", enable_plugins, (char *)NULL);
7182 g_object_set(G_OBJECT(t->settings),
7183 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7184 g_object_set(G_OBJECT(t->settings),
7185 "enable-html5-database", FALSE, (char *)NULL);
7186 g_object_set(G_OBJECT(t->settings),
7187 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7188 g_object_set(G_OBJECT(t->settings),
7189 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7190 g_object_set(G_OBJECT(t->settings),
7191 "spell_checking_languages", spell_check_languages, (char *)NULL);
7192 g_object_set(G_OBJECT(t->wv),
7193 "full-content-zoom", TRUE, (char *)NULL);
7194 adjustfont_webkit(t, XT_FONT_SET);
7196 webkit_web_view_set_settings(t->wv, t->settings);
7199 GtkWidget *
7200 create_browser(struct tab *t)
7202 GtkWidget *w;
7203 gchar *strval;
7205 if (t == NULL) {
7206 show_oops(NULL, "create_browser invalid parameters");
7207 return (NULL);
7210 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7211 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7212 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7213 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7215 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7216 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7217 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7219 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7220 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7222 /* set defaults */
7223 t->settings = webkit_web_settings_new();
7225 if (user_agent == NULL) {
7226 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7227 (char *)NULL);
7228 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7229 g_free(strval);
7230 } else
7231 t->user_agent = g_strdup(user_agent);
7233 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7235 setup_webkit(t);
7237 return (w);
7240 GtkWidget *
7241 create_window(void)
7243 GtkWidget *w;
7245 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7246 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7247 gtk_widget_set_name(w, "xxxterm");
7248 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7249 g_signal_connect(G_OBJECT(w), "delete_event",
7250 G_CALLBACK (gtk_main_quit), NULL);
7252 return (w);
7255 GtkWidget *
7256 create_kiosk_toolbar(struct tab *t)
7258 GtkWidget *toolbar = NULL, *b;
7260 b = gtk_hbox_new(FALSE, 0);
7261 toolbar = b;
7262 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7264 /* backward button */
7265 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7266 gtk_widget_set_sensitive(t->backward, FALSE);
7267 g_signal_connect(G_OBJECT(t->backward), "clicked",
7268 G_CALLBACK(backward_cb), t);
7269 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7271 /* forward button */
7272 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7273 gtk_widget_set_sensitive(t->forward, FALSE);
7274 g_signal_connect(G_OBJECT(t->forward), "clicked",
7275 G_CALLBACK(forward_cb), t);
7276 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7278 /* home button */
7279 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7280 gtk_widget_set_sensitive(t->gohome, true);
7281 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7282 G_CALLBACK(home_cb), t);
7283 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7285 /* create widgets but don't use them */
7286 t->uri_entry = gtk_entry_new();
7287 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7288 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7289 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7291 return (toolbar);
7294 GtkWidget *
7295 create_toolbar(struct tab *t)
7297 GtkWidget *toolbar = NULL, *b, *eb1;
7299 b = gtk_hbox_new(FALSE, 0);
7300 toolbar = b;
7301 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7303 if (fancy_bar) {
7304 /* backward button */
7305 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7306 gtk_widget_set_sensitive(t->backward, FALSE);
7307 g_signal_connect(G_OBJECT(t->backward), "clicked",
7308 G_CALLBACK(backward_cb), t);
7309 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7311 /* forward button */
7312 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7313 gtk_widget_set_sensitive(t->forward, FALSE);
7314 g_signal_connect(G_OBJECT(t->forward), "clicked",
7315 G_CALLBACK(forward_cb), t);
7316 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7317 FALSE, 0);
7319 /* stop button */
7320 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7321 gtk_widget_set_sensitive(t->stop, FALSE);
7322 g_signal_connect(G_OBJECT(t->stop), "clicked",
7323 G_CALLBACK(stop_cb), t);
7324 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7325 FALSE, 0);
7327 /* JS button */
7328 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7329 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7330 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7331 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7332 G_CALLBACK(js_toggle_cb), t);
7333 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7336 t->uri_entry = gtk_entry_new();
7337 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7338 G_CALLBACK(activate_uri_entry_cb), t);
7339 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7340 G_CALLBACK(entry_key_cb), t);
7341 completion_add(t);
7342 eb1 = gtk_hbox_new(FALSE, 0);
7343 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7344 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7345 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7347 /* search entry */
7348 if (fancy_bar && search_string) {
7349 GtkWidget *eb2;
7350 t->search_entry = gtk_entry_new();
7351 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7352 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7353 G_CALLBACK(activate_search_entry_cb), t);
7354 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7355 G_CALLBACK(entry_key_cb), t);
7356 gtk_widget_set_size_request(t->search_entry, -1, -1);
7357 eb2 = gtk_hbox_new(FALSE, 0);
7358 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7359 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7361 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7363 return (toolbar);
7366 GtkWidget *
7367 create_buffers(struct tab *t)
7369 GtkCellRenderer *renderer;
7370 GtkWidget *view;
7372 view = gtk_tree_view_new();
7374 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7376 renderer = gtk_cell_renderer_text_new();
7377 gtk_tree_view_insert_column_with_attributes
7378 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7380 renderer = gtk_cell_renderer_text_new();
7381 gtk_tree_view_insert_column_with_attributes
7382 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE, NULL);
7384 gtk_tree_view_set_model
7385 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7387 return view;
7390 void
7391 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7392 GtkTreeViewColumn *col, struct tab *t)
7394 GtkTreeIter iter;
7395 guint id;
7397 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7399 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path)) {
7400 gtk_tree_model_get
7401 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7402 set_current_tab(id - 1);
7405 hide_buffers(t);
7408 /* after tab reordering/creation/removal */
7409 void
7410 recalc_tabs(void)
7412 struct tab *t;
7413 int maxid = 0;
7414 int curid = 0;
7416 TAILQ_FOREACH(t, &tabs, entry) {
7417 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7418 if (t->tab_id > maxid)
7419 maxid = t->tab_id;
7421 gtk_widget_show(t->tab_elems.sep);
7424 curid = gtk_notebook_get_current_page(notebook);
7425 TAILQ_FOREACH(t, &tabs, entry) {
7426 if (t->tab_id == maxid) {
7427 gtk_widget_hide(t->tab_elems.sep);
7428 break;
7433 /* after active tab change */
7434 void
7435 recolor_compact_tabs(void)
7437 struct tab *t;
7438 int curid = 0;
7439 GdkColor color;
7441 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7442 TAILQ_FOREACH(t, &tabs, entry)
7443 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7445 curid = gtk_notebook_get_current_page(notebook);
7446 TAILQ_FOREACH(t, &tabs, entry)
7447 if (t->tab_id == curid) {
7448 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7449 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7450 break;
7454 void
7455 set_current_tab(int page_num)
7457 gtk_notebook_set_current_page(notebook, page_num);
7458 recolor_compact_tabs();
7462 undo_close_tab_save(struct tab *t)
7464 int m, n;
7465 const gchar *uri;
7466 struct undo *u1, *u2;
7467 GList *items;
7468 WebKitWebHistoryItem *item;
7470 if ((uri = get_uri(t)) == NULL)
7471 return (1);
7473 u1 = g_malloc0(sizeof(struct undo));
7474 u1->uri = g_strdup(uri);
7476 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7478 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7479 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7480 u1->back = n;
7482 /* forward history */
7483 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7485 while (items) {
7486 item = items->data;
7487 u1->history = g_list_prepend(u1->history,
7488 webkit_web_history_item_copy(item));
7489 items = g_list_next(items);
7492 /* current item */
7493 if (m) {
7494 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7495 u1->history = g_list_prepend(u1->history,
7496 webkit_web_history_item_copy(item));
7499 /* back history */
7500 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7502 while (items) {
7503 item = items->data;
7504 u1->history = g_list_prepend(u1->history,
7505 webkit_web_history_item_copy(item));
7506 items = g_list_next(items);
7509 TAILQ_INSERT_HEAD(&undos, u1, entry);
7511 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7512 u2 = TAILQ_LAST(&undos, undo_tailq);
7513 TAILQ_REMOVE(&undos, u2, entry);
7514 g_free(u2->uri);
7515 g_list_free(u2->history);
7516 g_free(u2);
7517 } else
7518 undo_count++;
7520 return (0);
7523 void
7524 delete_tab(struct tab *t)
7526 struct karg a;
7528 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7530 if (t == NULL)
7531 return;
7533 TAILQ_REMOVE(&tabs, t, entry);
7535 /* Halt all webkit activity. */
7536 abort_favicon_download(t);
7537 webkit_web_view_stop_loading(t->wv);
7539 /* Save the tab, so we can undo the close. */
7540 undo_close_tab_save(t);
7542 if (browser_mode == XT_BM_KIOSK) {
7543 gtk_widget_destroy(t->uri_entry);
7544 gtk_widget_destroy(t->stop);
7545 gtk_widget_destroy(t->js_toggle);
7548 gtk_widget_destroy(t->tab_elems.eventbox);
7549 gtk_widget_destroy(t->vbox);
7551 g_free(t->user_agent);
7552 g_free(t->stylesheet);
7553 g_free(t);
7555 if (TAILQ_EMPTY(&tabs)) {
7556 if (browser_mode == XT_BM_KIOSK)
7557 create_new_tab(home, NULL, 1, -1);
7558 else
7559 create_new_tab(NULL, NULL, 1, -1);
7562 /* recreate session */
7563 if (session_autosave) {
7564 a.s = NULL;
7565 save_tabs(t, &a);
7568 recalc_tabs();
7569 recolor_compact_tabs();
7572 void
7573 adjustfont_webkit(struct tab *t, int adjust)
7575 gfloat zoom;
7577 if (t == NULL) {
7578 show_oops(NULL, "adjustfont_webkit invalid parameters");
7579 return;
7582 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7583 if (adjust == XT_FONT_SET) {
7584 t->font_size = default_font_size;
7585 zoom = default_zoom_level;
7586 t->font_size += adjust;
7587 g_object_set(G_OBJECT(t->settings), "default-font-size",
7588 t->font_size, (char *)NULL);
7589 g_object_get(G_OBJECT(t->settings), "default-font-size",
7590 &t->font_size, (char *)NULL);
7591 } else {
7592 t->font_size += adjust;
7593 zoom += adjust/25.0;
7594 if (zoom < 0.0) {
7595 zoom = 0.04;
7598 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7599 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7602 gboolean
7603 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
7605 struct tab *t = (struct tab *) data;
7607 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
7609 switch (event->button) {
7610 case 1:
7611 set_current_tab(t->tab_id);
7612 break;
7613 case 2:
7614 delete_tab(t);
7615 break;
7618 return TRUE;
7621 gboolean
7622 page_reordered_cb(GtkWidget *nb, GtkWidget *eventbox, guint pn, gpointer data)
7624 struct tab *t = (struct tab *) data;
7626 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
7628 recalc_tabs();
7629 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, t->tab_id);
7631 return TRUE;
7634 void
7635 append_tab(struct tab *t)
7637 if (t == NULL)
7638 return;
7640 TAILQ_INSERT_TAIL(&tabs, t, entry);
7641 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7644 struct tab *
7645 create_new_tab(char *title, struct undo *u, int focus, int position)
7647 struct tab *t;
7648 int load = 1, id;
7649 GtkWidget *b, *bb;
7650 WebKitWebHistoryItem *item;
7651 GList *items;
7652 GdkColor color;
7654 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7656 if (tabless && !TAILQ_EMPTY(&tabs)) {
7657 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7658 return (NULL);
7661 t = g_malloc0(sizeof *t);
7663 if (title == NULL) {
7664 title = "(untitled)";
7665 load = 0;
7668 t->vbox = gtk_vbox_new(FALSE, 0);
7670 /* label + button for tab */
7671 b = gtk_hbox_new(FALSE, 0);
7672 t->tab_content = b;
7674 #if GTK_CHECK_VERSION(2, 20, 0)
7675 t->spinner = gtk_spinner_new();
7676 #endif
7677 t->label = gtk_label_new(title);
7678 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7679 gtk_widget_set_size_request(t->label, 100, 0);
7680 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7681 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7682 gtk_widget_set_size_request(b, 130, 0);
7684 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7685 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7686 #if GTK_CHECK_VERSION(2, 20, 0)
7687 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7688 #endif
7690 /* toolbar */
7691 if (browser_mode == XT_BM_KIOSK)
7692 t->toolbar = create_kiosk_toolbar(t);
7693 else
7694 t->toolbar = create_toolbar(t);
7696 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7698 /* browser */
7699 t->browser_win = create_browser(t);
7700 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7702 /* oops message for user feedback */
7703 t->oops = gtk_entry_new();
7704 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7705 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7706 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7707 gdk_color_parse(XT_COLOR_RED, &color);
7708 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7709 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7711 /* command entry */
7712 t->cmd = gtk_entry_new();
7713 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7714 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7715 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7716 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7718 /* status bar */
7719 t->statusbar = gtk_entry_new();
7720 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7721 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7722 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7723 gdk_color_parse(XT_COLOR_BLACK, &color);
7724 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7725 gdk_color_parse(XT_COLOR_WHITE, &color);
7726 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7727 gtk_widget_modify_font(GTK_WIDGET(t->statusbar), statusbar_font);
7728 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7730 /* buffer list */
7731 t->buffers = create_buffers(t);
7732 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
7734 /* xtp meaning is normal by default */
7735 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7737 /* set empty favicon */
7738 xt_icon_from_name(t, "text-html");
7740 /* and show it all */
7741 gtk_widget_show_all(b);
7742 gtk_widget_show_all(t->vbox);
7744 /* compact tab bar */
7745 t->tab_elems.label = gtk_label_new(title);
7746 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
7747 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
7748 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
7750 t->tab_elems.eventbox = gtk_event_box_new();
7751 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
7752 t->tab_elems.sep = gtk_vseparator_new();
7754 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
7755 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
7756 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7757 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7758 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
7759 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
7761 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE, TRUE, 0);
7762 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE, FALSE, 0);
7763 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox), t->tab_elems.box);
7765 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE, TRUE, 0);
7766 gtk_widget_show_all(t->tab_elems.eventbox);
7768 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7769 append_tab(t);
7770 else {
7771 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7772 if (id > gtk_notebook_get_n_pages(notebook))
7773 append_tab(t);
7774 else {
7775 TAILQ_INSERT_TAIL(&tabs, t, entry);
7776 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7777 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, id);
7778 recalc_tabs();
7782 #if GTK_CHECK_VERSION(2, 20, 0)
7783 /* turn spinner off if we are a new tab without uri */
7784 if (!load) {
7785 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7786 gtk_widget_hide(t->spinner);
7788 #endif
7789 /* make notebook tabs reorderable */
7790 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7792 /* compact tabs clickable */
7793 g_signal_connect(GTK_OBJECT(t->tab_elems.eventbox),
7794 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
7796 g_signal_connect(GTK_OBJECT(notebook),
7797 "page_reordered", G_CALLBACK(page_reordered_cb), t);
7799 g_object_connect(G_OBJECT(t->cmd),
7800 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7801 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7802 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7803 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7804 (char *)NULL);
7806 /* reuse wv_button_cb to hide oops */
7807 g_object_connect(G_OBJECT(t->oops),
7808 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7809 (char *)NULL);
7811 g_signal_connect(t->buffers,
7812 "row-activated", G_CALLBACK(row_activated_cb), t);
7813 g_object_connect(G_OBJECT(t->buffers),
7814 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
7816 g_object_connect(G_OBJECT(t->wv),
7817 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7818 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7819 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7820 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7821 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7822 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7823 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7824 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7825 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7826 "signal::event", G_CALLBACK(webview_event_cb), t,
7827 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7828 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7829 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7830 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7831 (char *)NULL);
7832 g_signal_connect(t->wv,
7833 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7834 g_signal_connect(t->wv,
7835 "notify::title", G_CALLBACK(notify_title_cb), t);
7837 /* hijack the unused keys as if we were the browser */
7838 g_object_connect(G_OBJECT(t->toolbar),
7839 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7840 (char *)NULL);
7842 g_signal_connect(G_OBJECT(bb), "button_press_event",
7843 G_CALLBACK(tab_close_cb), t);
7845 /* hide stuff */
7846 hide_cmd(t);
7847 hide_oops(t);
7848 hide_buffers(t);
7849 url_set_visibility();
7850 statusbar_set_visibility();
7852 if (focus) {
7853 set_current_tab(t->tab_id);
7854 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7855 t->tab_id);
7858 if (load) {
7859 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7860 load_uri(t, title);
7861 } else {
7862 if (show_url == 1)
7863 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7864 else
7865 focus_webview(t);
7868 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7869 /* restore the tab's history */
7870 if (u && u->history) {
7871 items = u->history;
7872 while (items) {
7873 item = items->data;
7874 webkit_web_back_forward_list_add_item(t->bfl, item);
7875 items = g_list_next(items);
7878 item = g_list_nth_data(u->history, u->back);
7879 if (item)
7880 webkit_web_view_go_to_back_forward_item(t->wv, item);
7882 g_list_free(items);
7883 g_list_free(u->history);
7884 } else
7885 webkit_web_back_forward_list_clear(t->bfl);
7887 recalc_tabs();
7888 recolor_compact_tabs();
7889 return (t);
7892 void
7893 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7894 gpointer *udata)
7896 struct tab *t;
7897 const gchar *uri;
7899 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7901 if (gtk_notebook_get_current_page(notebook) == -1)
7902 recalc_tabs();
7904 TAILQ_FOREACH(t, &tabs, entry) {
7905 if (t->tab_id == pn) {
7906 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7907 "%d\n", pn);
7909 uri = webkit_web_view_get_title(t->wv);
7910 if (uri == NULL)
7911 uri = XT_NAME;
7912 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7914 hide_cmd(t);
7915 hide_oops(t);
7917 if (t->focus_wv) {
7918 /* can't use focus_webview here */
7919 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7925 void
7926 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7927 gpointer *udata)
7929 recalc_tabs();
7932 void
7933 menuitem_response(struct tab *t)
7935 gtk_notebook_set_current_page(notebook, t->tab_id);
7938 gboolean
7939 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7941 GtkWidget *menu, *menu_items;
7942 GdkEventButton *bevent;
7943 const gchar *uri;
7944 struct tab *ti;
7946 if (event->type == GDK_BUTTON_PRESS) {
7947 bevent = (GdkEventButton *) event;
7948 menu = gtk_menu_new();
7950 TAILQ_FOREACH(ti, &tabs, entry) {
7951 if ((uri = get_uri(ti)) == NULL)
7952 /* XXX make sure there is something to print */
7953 /* XXX add gui pages in here to look purdy */
7954 uri = "(untitled)";
7955 menu_items = gtk_menu_item_new_with_label(uri);
7956 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7957 gtk_widget_show(menu_items);
7959 g_signal_connect_swapped((menu_items),
7960 "activate", G_CALLBACK(menuitem_response),
7961 (gpointer)ti);
7964 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7965 bevent->button, bevent->time);
7967 /* unref object so it'll free itself when popped down */
7968 #if !GTK_CHECK_VERSION(3, 0, 0)
7969 /* XXX does not need unref with gtk+3? */
7970 g_object_ref_sink(menu);
7971 g_object_unref(menu);
7972 #endif
7974 return (TRUE /* eat event */);
7977 return (FALSE /* propagate */);
7981 icon_size_map(int icon_size)
7983 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7984 icon_size > GTK_ICON_SIZE_DIALOG)
7985 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7987 return (icon_size);
7990 GtkWidget *
7991 create_button(char *name, char *stockid, int size)
7993 GtkWidget *button, *image;
7994 gchar *rcstring;
7995 int gtk_icon_size;
7997 rcstring = g_strdup_printf(
7998 "style \"%s-style\"\n"
7999 "{\n"
8000 " GtkWidget::focus-padding = 0\n"
8001 " GtkWidget::focus-line-width = 0\n"
8002 " xthickness = 0\n"
8003 " ythickness = 0\n"
8004 "}\n"
8005 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8006 gtk_rc_parse_string(rcstring);
8007 g_free(rcstring);
8008 button = gtk_button_new();
8009 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8010 gtk_icon_size = icon_size_map(size ? size : icon_size);
8012 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8013 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8014 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8015 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8016 gtk_widget_set_name(button, name);
8017 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8019 return (button);
8022 void
8023 button_set_stockid(GtkWidget *button, char *stockid)
8025 GtkWidget *image;
8027 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8028 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8029 gtk_button_set_image(GTK_BUTTON(button), image);
8032 void
8033 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8035 GtkClipboard *clipboard;
8036 gchar *p = NULL, *s = NULL;
8039 * This code is very aggressive!
8040 * It basically ensures that the primary and regular clipboard are
8041 * always set the same. This obviously messes with standard X protocol
8042 * but those clowns should have come up with something better.
8045 /* XXX make this setting? */
8046 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8047 p = gtk_clipboard_wait_for_text(primary);
8048 if (p == NULL) {
8049 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
8050 p = gtk_clipboard_wait_for_text(clipboard);
8051 if (p)
8052 gtk_clipboard_set_text(primary, p, -1);
8053 } else {
8054 DNPRINTF(XT_D_CLIP, "primary got selection\n");
8055 s = gtk_clipboard_wait_for_text(clipboard);
8056 if (s) {
8058 * if s and p are the same the string was set by
8059 * clipb_clipboard_cb so do nothing in that case
8060 * to prevent endless loop
8062 if (!strcmp(s, p))
8063 goto done;
8065 gtk_clipboard_set_text(clipboard, p, -1);
8067 done:
8068 if (p)
8069 g_free(p);
8070 if (s)
8071 g_free(s);
8074 void
8075 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
8077 GtkClipboard *primary;
8078 gchar *p = NULL, *s = NULL;
8080 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
8082 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8083 p = gtk_clipboard_wait_for_text(clipboard);
8084 if (p) {
8085 s = gtk_clipboard_wait_for_text(primary);
8086 if (s) {
8088 * if s and p are the same the string was set by
8089 * clipb_primary_cb so do nothing in that case
8090 * to prevent endless loop and deselection of text
8092 if (!strcmp(s, p))
8093 goto done;
8095 gtk_clipboard_set_text(primary, p, -1);
8097 done:
8098 if (p)
8099 g_free(p);
8100 if (s)
8101 g_free(s);
8104 void
8105 create_canvas(void)
8107 GtkWidget *vbox;
8108 GList *l = NULL;
8109 GdkPixbuf *pb;
8110 char file[PATH_MAX];
8111 int i;
8113 vbox = gtk_vbox_new(FALSE, 0);
8114 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8115 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8116 #if !GTK_CHECK_VERSION(3, 0, 0)
8117 /* XXX seems to be needed with gtk+2 */
8118 gtk_notebook_set_tab_hborder(notebook, 0);
8119 gtk_notebook_set_tab_vborder(notebook, 0);
8120 #endif
8121 gtk_notebook_set_scrollable(notebook, TRUE);
8122 gtk_notebook_set_show_border(notebook, FALSE);
8123 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8125 abtn = gtk_button_new();
8126 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8127 gtk_widget_set_size_request(arrow, -1, -1);
8128 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8129 gtk_widget_set_size_request(abtn, -1, 20);
8131 #if GTK_CHECK_VERSION(2, 20, 0)
8132 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8133 #endif
8134 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8136 /* compact tab bar */
8137 tab_bar = gtk_hbox_new(TRUE, 0);
8139 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8140 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8141 gtk_widget_set_size_request(vbox, -1, -1);
8143 g_object_connect(G_OBJECT(notebook),
8144 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8145 (char *)NULL);
8146 g_object_connect(G_OBJECT(notebook),
8147 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
8148 (char *)NULL);
8149 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8150 G_CALLBACK(arrow_cb), NULL);
8152 main_window = create_window();
8153 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8154 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
8156 /* icons */
8157 for (i = 0; i < LENGTH(icons); i++) {
8158 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8159 pb = gdk_pixbuf_new_from_file(file, NULL);
8160 l = g_list_append(l, pb);
8162 gtk_window_set_default_icon_list(l);
8164 /* clipboard */
8165 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8166 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8167 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
8168 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
8170 gtk_widget_show_all(abtn);
8171 gtk_widget_show_all(main_window);
8172 notebook_tab_set_visibility();
8175 void
8176 set_hook(void **hook, char *name)
8178 if (hook == NULL)
8179 errx(1, "set_hook");
8181 if (*hook == NULL) {
8182 *hook = dlsym(RTLD_NEXT, name);
8183 if (*hook == NULL)
8184 errx(1, "can't hook %s", name);
8188 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8189 gboolean
8190 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8192 g_return_val_if_fail(cookie1, FALSE);
8193 g_return_val_if_fail(cookie2, FALSE);
8195 return (!strcmp (cookie1->name, cookie2->name) &&
8196 !strcmp (cookie1->value, cookie2->value) &&
8197 !strcmp (cookie1->path, cookie2->path) &&
8198 !strcmp (cookie1->domain, cookie2->domain));
8201 void
8202 transfer_cookies(void)
8204 GSList *cf;
8205 SoupCookie *sc, *pc;
8207 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8209 for (;cf; cf = cf->next) {
8210 pc = cf->data;
8211 sc = soup_cookie_copy(pc);
8212 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8215 soup_cookies_free(cf);
8218 void
8219 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8221 GSList *cf;
8222 SoupCookie *ci;
8224 print_cookie("soup_cookie_jar_delete_cookie", c);
8226 if (cookies_enabled == 0)
8227 return;
8229 if (jar == NULL || c == NULL)
8230 return;
8232 /* find and remove from persistent jar */
8233 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8235 for (;cf; cf = cf->next) {
8236 ci = cf->data;
8237 if (soup_cookie_equal(ci, c)) {
8238 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8239 break;
8243 soup_cookies_free(cf);
8245 /* delete from session jar */
8246 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8249 void
8250 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8252 struct domain *d = NULL;
8253 SoupCookie *c;
8254 FILE *r_cookie_f;
8256 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8257 jar, p_cookiejar, s_cookiejar);
8259 if (cookies_enabled == 0)
8260 return;
8262 /* see if we are up and running */
8263 if (p_cookiejar == NULL) {
8264 _soup_cookie_jar_add_cookie(jar, cookie);
8265 return;
8267 /* disallow p_cookiejar adds, shouldn't happen */
8268 if (jar == p_cookiejar)
8269 return;
8271 /* sanity */
8272 if (jar == NULL || cookie == NULL)
8273 return;
8275 if (enable_cookie_whitelist &&
8276 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8277 blocked_cookies++;
8278 DNPRINTF(XT_D_COOKIE,
8279 "soup_cookie_jar_add_cookie: reject %s\n",
8280 cookie->domain);
8281 if (save_rejected_cookies) {
8282 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8283 show_oops(NULL, "can't open reject cookie file");
8284 return;
8286 fseek(r_cookie_f, 0, SEEK_END);
8287 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8288 cookie->http_only ? "#HttpOnly_" : "",
8289 cookie->domain,
8290 *cookie->domain == '.' ? "TRUE" : "FALSE",
8291 cookie->path,
8292 cookie->secure ? "TRUE" : "FALSE",
8293 cookie->expires ?
8294 (gulong)soup_date_to_time_t(cookie->expires) :
8296 cookie->name,
8297 cookie->value);
8298 fflush(r_cookie_f);
8299 fclose(r_cookie_f);
8301 if (!allow_volatile_cookies)
8302 return;
8305 if (cookie->expires == NULL && session_timeout) {
8306 soup_cookie_set_expires(cookie,
8307 soup_date_new_from_now(session_timeout));
8308 print_cookie("modified add cookie", cookie);
8311 /* see if we are white listed for persistence */
8312 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8313 /* add to persistent jar */
8314 c = soup_cookie_copy(cookie);
8315 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8316 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8319 /* add to session jar */
8320 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8321 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8324 void
8325 setup_cookies(void)
8327 char file[PATH_MAX];
8329 set_hook((void *)&_soup_cookie_jar_add_cookie,
8330 "soup_cookie_jar_add_cookie");
8331 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8332 "soup_cookie_jar_delete_cookie");
8334 if (cookies_enabled == 0)
8335 return;
8338 * the following code is intricate due to overriding several libsoup
8339 * functions.
8340 * do not alter order of these operations.
8343 /* rejected cookies */
8344 if (save_rejected_cookies)
8345 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8347 /* persistent cookies */
8348 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8349 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8351 /* session cookies */
8352 s_cookiejar = soup_cookie_jar_new();
8353 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8354 cookie_policy, (void *)NULL);
8355 transfer_cookies();
8357 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8360 void
8361 setup_proxy(char *uri)
8363 if (proxy_uri) {
8364 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8365 soup_uri_free(proxy_uri);
8366 proxy_uri = NULL;
8368 if (http_proxy) {
8369 if (http_proxy != uri) {
8370 g_free(http_proxy);
8371 http_proxy = NULL;
8375 if (uri) {
8376 http_proxy = g_strdup(uri);
8377 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8378 proxy_uri = soup_uri_new(http_proxy);
8379 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8384 send_cmd_to_socket(char *cmd)
8386 int s, len, rv = 1;
8387 struct sockaddr_un sa;
8389 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8390 warnx("%s: socket", __func__);
8391 return (rv);
8394 sa.sun_family = AF_UNIX;
8395 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8396 work_dir, XT_SOCKET_FILE);
8397 len = SUN_LEN(&sa);
8399 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8400 warnx("%s: connect", __func__);
8401 goto done;
8404 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8405 warnx("%s: send", __func__);
8406 goto done;
8409 rv = 0;
8410 done:
8411 close(s);
8412 return (rv);
8415 gboolean
8416 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8418 int s, n;
8419 char str[XT_MAX_URL_LENGTH];
8420 socklen_t t = sizeof(struct sockaddr_un);
8421 struct sockaddr_un sa;
8422 struct passwd *p;
8423 uid_t uid;
8424 gid_t gid;
8425 struct tab *tt;
8426 gint fd = g_io_channel_unix_get_fd(source);
8428 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8429 warn("accept");
8430 return (FALSE);
8433 if (getpeereid(s, &uid, &gid) == -1) {
8434 warn("getpeereid");
8435 return (FALSE);
8437 if (uid != getuid() || gid != getgid()) {
8438 warnx("unauthorized user");
8439 return (FALSE);
8442 p = getpwuid(uid);
8443 if (p == NULL) {
8444 warnx("not a valid user");
8445 return (FALSE);
8448 n = recv(s, str, sizeof(str), 0);
8449 if (n <= 0)
8450 return (FALSE);
8452 tt = TAILQ_LAST(&tabs, tab_list);
8453 cmd_execute(tt, str);
8454 return (TRUE);
8458 is_running(void)
8460 int s, len, rv = 1;
8461 struct sockaddr_un sa;
8463 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8464 warn("is_running: socket");
8465 return (-1);
8468 sa.sun_family = AF_UNIX;
8469 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8470 work_dir, XT_SOCKET_FILE);
8471 len = SUN_LEN(&sa);
8473 /* connect to see if there is a listener */
8474 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8475 rv = 0; /* not running */
8476 else
8477 rv = 1; /* already running */
8479 close(s);
8481 return (rv);
8485 build_socket(void)
8487 int s, len;
8488 struct sockaddr_un sa;
8490 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8491 warn("build_socket: socket");
8492 return (-1);
8495 sa.sun_family = AF_UNIX;
8496 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8497 work_dir, XT_SOCKET_FILE);
8498 len = SUN_LEN(&sa);
8500 /* connect to see if there is a listener */
8501 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8502 /* no listener so we will */
8503 unlink(sa.sun_path);
8505 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8506 warn("build_socket: bind");
8507 goto done;
8510 if (listen(s, 1) == -1) {
8511 warn("build_socket: listen");
8512 goto done;
8515 return (s);
8518 done:
8519 close(s);
8520 return (-1);
8523 gboolean
8524 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8525 GtkTreeIter *iter, struct tab *t)
8527 gchar *value;
8529 gtk_tree_model_get(model, iter, 0, &value, -1);
8530 load_uri(t, value);
8531 g_free(value);
8533 return (FALSE);
8536 gboolean
8537 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8538 GtkTreeIter *iter, struct tab *t)
8540 gchar *value;
8542 gtk_tree_model_get(model, iter, 0, &value, -1);
8543 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8544 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8545 g_free(value);
8547 return (TRUE);
8550 void
8551 completion_add_uri(const gchar *uri)
8553 GtkTreeIter iter;
8555 /* add uri to list_store */
8556 gtk_list_store_append(completion_model, &iter);
8557 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8560 gboolean
8561 completion_match(GtkEntryCompletion *completion, const gchar *key,
8562 GtkTreeIter *iter, gpointer user_data)
8564 gchar *value;
8565 gboolean match = FALSE;
8567 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8568 -1);
8570 if (value == NULL)
8571 return FALSE;
8573 match = match_uri(value, key);
8575 g_free(value);
8576 return (match);
8579 void
8580 completion_add(struct tab *t)
8582 /* enable completion for tab */
8583 t->completion = gtk_entry_completion_new();
8584 gtk_entry_completion_set_text_column(t->completion, 0);
8585 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8586 gtk_entry_completion_set_model(t->completion,
8587 GTK_TREE_MODEL(completion_model));
8588 gtk_entry_completion_set_match_func(t->completion, completion_match,
8589 NULL, NULL);
8590 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8591 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8592 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8593 G_CALLBACK(completion_select_cb), t);
8594 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8595 G_CALLBACK(completion_hover_cb), t);
8598 void
8599 xxx_dir(char *dir)
8601 struct stat sb;
8603 if (stat(dir, &sb)) {
8604 if (mkdir(dir, S_IRWXU) == -1)
8605 err(1, "mkdir %s", dir);
8606 if (stat(dir, &sb))
8607 err(1, "stat %s", dir);
8609 if (S_ISDIR(sb.st_mode) == 0)
8610 errx(1, "%s not a dir", dir);
8611 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8612 warnx("fixing invalid permissions on %s", dir);
8613 if (chmod(dir, S_IRWXU) == -1)
8614 err(1, "chmod %s", dir);
8618 void
8619 usage(void)
8621 fprintf(stderr,
8622 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8623 exit(0);
8628 main(int argc, char *argv[])
8630 struct stat sb;
8631 int c, s, optn = 0, opte = 0, focus = 1;
8632 char conf[PATH_MAX] = { '\0' };
8633 char file[PATH_MAX];
8634 char *env_proxy = NULL;
8635 FILE *f = NULL;
8636 struct karg a;
8637 struct sigaction sact;
8638 GIOChannel *channel;
8639 struct rlimit rlp;
8641 start_argv = argv;
8643 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8645 /* fiddle with ulimits */
8646 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8647 warn("getrlimit");
8648 else {
8649 /* just use them all */
8650 rlp.rlim_cur = rlp.rlim_max;
8651 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8652 warn("setrlimit");
8653 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8654 warn("getrlimit");
8655 else if (rlp.rlim_cur <= 256)
8656 warnx("%s requires at least 256 file descriptors",
8657 __progname);
8660 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8661 switch (c) {
8662 case 'S':
8663 show_url = 0;
8664 break;
8665 case 'T':
8666 show_tabs = 0;
8667 break;
8668 case 'V':
8669 errx(0 , "Version: %s", version);
8670 break;
8671 case 'f':
8672 strlcpy(conf, optarg, sizeof(conf));
8673 break;
8674 case 's':
8675 strlcpy(named_session, optarg, sizeof(named_session));
8676 break;
8677 case 't':
8678 tabless = 1;
8679 break;
8680 case 'n':
8681 optn = 1;
8682 break;
8683 case 'e':
8684 opte = 1;
8685 break;
8686 default:
8687 usage();
8688 /* NOTREACHED */
8691 argc -= optind;
8692 argv += optind;
8694 RB_INIT(&hl);
8695 RB_INIT(&js_wl);
8696 RB_INIT(&downloads);
8698 TAILQ_INIT(&tabs);
8699 TAILQ_INIT(&mtl);
8700 TAILQ_INIT(&aliases);
8701 TAILQ_INIT(&undos);
8702 TAILQ_INIT(&kbl);
8704 init_keybindings();
8706 gnutls_global_init();
8708 /* generate session keys for xtp pages */
8709 generate_xtp_session_key(&dl_session_key);
8710 generate_xtp_session_key(&hl_session_key);
8711 generate_xtp_session_key(&cl_session_key);
8712 generate_xtp_session_key(&fl_session_key);
8714 /* prepare gtk */
8715 gtk_init(&argc, &argv);
8716 if (!g_thread_supported())
8717 g_thread_init(NULL);
8719 /* signals */
8720 bzero(&sact, sizeof(sact));
8721 sigemptyset(&sact.sa_mask);
8722 sact.sa_handler = sigchild;
8723 sact.sa_flags = SA_NOCLDSTOP;
8724 sigaction(SIGCHLD, &sact, NULL);
8726 /* set download dir */
8727 pwd = getpwuid(getuid());
8728 if (pwd == NULL)
8729 errx(1, "invalid user %d", getuid());
8730 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8732 /* set default string settings */
8733 home = g_strdup("https://www.cyphertite.com");
8734 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8735 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8736 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
8737 cmd_font_name = g_strdup("monospace normal 9");
8738 statusbar_font_name = g_strdup("monospace normal 9");
8741 /* read config file */
8742 if (strlen(conf) == 0)
8743 snprintf(conf, sizeof conf, "%s/.%s",
8744 pwd->pw_dir, XT_CONF_FILE);
8745 config_parse(conf, 0);
8747 /* init fonts */
8748 cmd_font = pango_font_description_from_string(cmd_font_name);
8749 statusbar_font = pango_font_description_from_string(statusbar_font_name);
8751 /* working directory */
8752 if (strlen(work_dir) == 0)
8753 snprintf(work_dir, sizeof work_dir, "%s/%s",
8754 pwd->pw_dir, XT_DIR);
8755 xxx_dir(work_dir);
8757 /* icon cache dir */
8758 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8759 xxx_dir(cache_dir);
8761 /* certs dir */
8762 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8763 xxx_dir(certs_dir);
8765 /* sessions dir */
8766 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8767 work_dir, XT_SESSIONS_DIR);
8768 xxx_dir(sessions_dir);
8770 /* runtime settings that can override config file */
8771 if (runtime_settings[0] != '\0')
8772 config_parse(runtime_settings, 1);
8774 /* download dir */
8775 if (!strcmp(download_dir, pwd->pw_dir))
8776 strlcat(download_dir, "/downloads", sizeof download_dir);
8777 xxx_dir(download_dir);
8779 /* favorites file */
8780 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8781 if (stat(file, &sb)) {
8782 warnx("favorites file doesn't exist, creating it");
8783 if ((f = fopen(file, "w")) == NULL)
8784 err(1, "favorites");
8785 fclose(f);
8788 /* cookies */
8789 session = webkit_get_default_session();
8790 setup_cookies();
8792 /* certs */
8793 if (ssl_ca_file) {
8794 if (stat(ssl_ca_file, &sb)) {
8795 warnx("no CA file: %s", ssl_ca_file);
8796 g_free(ssl_ca_file);
8797 ssl_ca_file = NULL;
8798 } else
8799 g_object_set(session,
8800 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8801 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8802 (void *)NULL);
8805 /* proxy */
8806 env_proxy = getenv("http_proxy");
8807 if (env_proxy)
8808 setup_proxy(env_proxy);
8809 else
8810 setup_proxy(http_proxy);
8812 if (opte) {
8813 send_cmd_to_socket(argv[0]);
8814 exit(0);
8817 /* set some connection parameters */
8818 /* XXX webkit 1.4.X overwrites these values! */
8819 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
8820 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8821 g_object_set(session, "max-conns-per-host", max_host_connections,
8822 (char *)NULL);
8824 /* see if there is already an xxxterm running */
8825 if (single_instance && is_running()) {
8826 optn = 1;
8827 warnx("already running");
8830 char *cmd = NULL;
8831 if (optn) {
8832 while (argc) {
8833 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8834 send_cmd_to_socket(cmd);
8835 if (cmd)
8836 g_free(cmd);
8838 argc--;
8839 argv++;
8841 exit(0);
8844 /* uri completion */
8845 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8847 /* buffers */
8848 buffers_store = gtk_list_store_new
8849 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
8851 /* go graphical */
8852 create_canvas();
8853 notebook_tab_set_visibility();
8855 if (save_global_history)
8856 restore_global_history();
8858 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8859 restore_saved_tabs();
8860 else {
8861 a.s = named_session;
8862 a.i = XT_SES_DONOTHING;
8863 open_tabs(NULL, &a);
8866 while (argc) {
8867 create_new_tab(argv[0], NULL, focus, -1);
8868 focus = 0;
8870 argc--;
8871 argv++;
8874 if (TAILQ_EMPTY(&tabs))
8875 create_new_tab(home, NULL, 1, -1);
8877 if (enable_socket)
8878 if ((s = build_socket()) != -1) {
8879 channel = g_io_channel_unix_new(s);
8880 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8883 gtk_main();
8885 gnutls_global_deinit();
8887 return (0);