add raphael and michal to authors
[xxxterm.git] / xxxterm.c
blob145bb3035a3b979f6cf17a57823c40eb758502e8
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <err.h>
34 #include <pwd.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pthread.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <libgen.h>
42 #include <ctype.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/stat.h>
59 #include <sys/socket.h>
60 #include <sys/un.h>
61 #include <sys/time.h>
62 #include <sys/resource.h>
64 #include <gtk/gtk.h>
65 #include <gdk/gdkkeysyms.h>
67 #if GTK_CHECK_VERSION(3,0,0)
68 /* we still use GDK_* instead of GDK_KEY_* */
69 #include <gdk/gdkkeysyms-compat.h>
70 #endif
72 #include <webkit/webkit.h>
73 #include <libsoup/soup.h>
74 #include <gnutls/gnutls.h>
75 #include <JavaScriptCore/JavaScript.h>
76 #include <gnutls/x509.h>
78 #include "javascript.h"
81 javascript.h borrowed from vimprobable2 under the following license:
83 Copyright (c) 2009 Leon Winter
84 Copyright (c) 2009 Hannes Schueller
85 Copyright (c) 2009 Matto Fransen
87 Permission is hereby granted, free of charge, to any person obtaining a copy
88 of this software and associated documentation files (the "Software"), to deal
89 in the Software without restriction, including without limitation the rights
90 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
91 copies of the Software, and to permit persons to whom the Software is
92 furnished to do so, subject to the following conditions:
94 The above copyright notice and this permission notice shall be included in
95 all copies or substantial portions of the Software.
97 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
98 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
99 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
100 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
101 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
103 THE SOFTWARE.
106 static char *version = "$xxxterm$";
108 /* hooked functions */
109 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
110 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
111 SoupCookie *);
113 /*#define XT_DEBUG*/
114 #ifdef XT_DEBUG
115 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
116 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
117 #define XT_D_MOVE 0x0001
118 #define XT_D_KEY 0x0002
119 #define XT_D_TAB 0x0004
120 #define XT_D_URL 0x0008
121 #define XT_D_CMD 0x0010
122 #define XT_D_NAV 0x0020
123 #define XT_D_DOWNLOAD 0x0040
124 #define XT_D_CONFIG 0x0080
125 #define XT_D_JS 0x0100
126 #define XT_D_FAVORITE 0x0200
127 #define XT_D_PRINTING 0x0400
128 #define XT_D_COOKIE 0x0800
129 #define XT_D_KEYBINDING 0x1000
130 #define XT_D_CLIP 0x2000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
147 #else
148 #define DPRINTF(x...)
149 #define DNPRINTF(n,x...)
150 #endif
152 #define LENGTH(x) (sizeof x / sizeof x[0])
153 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
154 ~(GDK_BUTTON1_MASK) & \
155 ~(GDK_BUTTON2_MASK) & \
156 ~(GDK_BUTTON3_MASK) & \
157 ~(GDK_BUTTON4_MASK) & \
158 ~(GDK_BUTTON5_MASK))
160 char *icons[] = {
161 "xxxtermicon16.png",
162 "xxxtermicon32.png",
163 "xxxtermicon48.png",
164 "xxxtermicon64.png",
165 "xxxtermicon128.png"
168 struct tab {
169 TAILQ_ENTRY(tab) entry;
170 GtkWidget *vbox;
171 GtkWidget *tab_content;
172 struct {
173 GtkWidget *label;
174 GtkWidget *eventbox;
175 GtkWidget *box;
176 GtkWidget *sep;
177 } tab_elems;
178 GtkWidget *label;
179 GtkWidget *spinner;
180 GtkWidget *uri_entry;
181 GtkWidget *search_entry;
182 GtkWidget *toolbar;
183 GtkWidget *browser_win;
184 GtkWidget *statusbar;
185 GtkWidget *cmd;
186 GtkWidget *buffers;
187 GtkWidget *oops;
188 GtkWidget *backward;
189 GtkWidget *forward;
190 GtkWidget *stop;
191 GtkWidget *gohome;
192 GtkWidget *js_toggle;
193 GtkEntryCompletion *completion;
194 guint tab_id;
195 WebKitWebView *wv;
197 WebKitWebHistoryItem *item;
198 WebKitWebBackForwardList *bfl;
200 /* favicon */
201 WebKitNetworkRequest *icon_request;
202 WebKitDownload *icon_download;
203 gchar *icon_dest_uri;
205 /* adjustments for browser */
206 GtkScrollbar *sb_h;
207 GtkScrollbar *sb_v;
208 GtkAdjustment *adjust_h;
209 GtkAdjustment *adjust_v;
211 /* flags */
212 int focus_wv;
213 int ctrl_click;
214 gchar *status;
215 int xtp_meaning; /* identifies dls/favorites */
217 /* hints */
218 int hints_on;
219 int hint_mode;
220 #define XT_HINT_NONE (0)
221 #define XT_HINT_NUMERICAL (1)
222 #define XT_HINT_ALPHANUM (2)
223 char hint_buf[128];
224 char hint_num[128];
226 /* custom stylesheet */
227 int styled;
228 char *stylesheet;
230 /* search */
231 char *search_text;
232 int search_forward;
234 /* settings */
235 WebKitWebSettings *settings;
236 int font_size;
237 gchar *user_agent;
239 TAILQ_HEAD(tab_list, tab);
241 struct history {
242 RB_ENTRY(history) entry;
243 const gchar *uri;
244 const gchar *title;
246 RB_HEAD(history_list, history);
248 struct download {
249 RB_ENTRY(download) entry;
250 int id;
251 WebKitDownload *download;
252 struct tab *tab;
254 RB_HEAD(download_list, download);
256 struct domain {
257 RB_ENTRY(domain) entry;
258 gchar *d;
259 int handy; /* app use */
261 RB_HEAD(domain_list, domain);
263 struct undo {
264 TAILQ_ENTRY(undo) entry;
265 gchar *uri;
266 GList *history;
267 int back; /* Keeps track of how many back
268 * history items there are. */
270 TAILQ_HEAD(undo_tailq, undo);
272 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
273 int next_download_id = 1;
275 struct karg {
276 int i;
277 char *s;
278 int p;
281 /* defines */
282 #define XT_NAME ("XXXTerm")
283 #define XT_DIR (".xxxterm")
284 #define XT_CACHE_DIR ("cache")
285 #define XT_CERT_DIR ("certs/")
286 #define XT_SESSIONS_DIR ("sessions/")
287 #define XT_CONF_FILE ("xxxterm.conf")
288 #define XT_FAVS_FILE ("favorites")
289 #define XT_SAVED_TABS_FILE ("main_session")
290 #define XT_RESTART_TABS_FILE ("restart_tabs")
291 #define XT_SOCKET_FILE ("socket")
292 #define XT_HISTORY_FILE ("history")
293 #define XT_REJECT_FILE ("rejected.txt")
294 #define XT_COOKIE_FILE ("cookies.txt")
295 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
296 #define XT_CB_HANDLED (TRUE)
297 #define XT_CB_PASSTHROUGH (FALSE)
298 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
299 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
300 #define XT_DLMAN_REFRESH "10"
301 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
302 "td{overflow: hidden;" \
303 " padding: 2px 2px 2px 2px;" \
304 " border: 1px solid black;" \
305 " vertical-align:top;" \
306 " word-wrap: break-word}\n" \
307 "tr:hover{background: #ffff99}\n" \
308 "th{background-color: #cccccc;" \
309 " border: 1px solid black}\n" \
310 "table{width: 100%%;" \
311 " border: 1px black solid;" \
312 " border-collapse:collapse}\n" \
313 ".progress-outer{" \
314 "border: 1px solid black;" \
315 " height: 8px;" \
316 " width: 90%%}\n" \
317 ".progress-inner{float: left;" \
318 " height: 8px;" \
319 " background: green}\n" \
320 ".dlstatus{font-size: small;" \
321 " text-align: center}\n" \
322 "</style>\n"
323 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
324 #define XT_MAX_UNDO_CLOSE_TAB (32)
325 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
326 #define XT_PRINT_EXTRA_MARGIN 10
328 /* colors */
329 #define XT_COLOR_RED "#cc0000"
330 #define XT_COLOR_YELLOW "#ffff66"
331 #define XT_COLOR_BLUE "lightblue"
332 #define XT_COLOR_GREEN "#99ff66"
333 #define XT_COLOR_WHITE "white"
334 #define XT_COLOR_BLACK "black"
336 #define XT_COLOR_CT_BACKGROUND "#000000"
337 #define XT_COLOR_CT_INACTIVE "#dddddd"
338 #define XT_COLOR_CT_ACTIVE "#bbbb00"
339 #define XT_COLOR_CT_SEPARATOR "#555555"
342 * xxxterm "protocol" (xtp)
343 * We use this for managing stuff like downloads and favorites. They
344 * make magical HTML pages in memory which have xxxt:// links in order
345 * to communicate with xxxterm's internals. These links take the format:
346 * xxxt://class/session_key/action/arg
348 * Don't begin xtp class/actions as 0. atoi returns that on error.
350 * Typically we have not put addition of items in this framework, as
351 * adding items is either done via an ex-command or via a keybinding instead.
354 #define XT_XTP_STR "xxxt://"
356 /* XTP classes (xxxt://<class>) */
357 #define XT_XTP_INVALID 0 /* invalid */
358 #define XT_XTP_DL 1 /* downloads */
359 #define XT_XTP_HL 2 /* history */
360 #define XT_XTP_CL 3 /* cookies */
361 #define XT_XTP_FL 4 /* favorites */
363 /* XTP download actions */
364 #define XT_XTP_DL_LIST 1
365 #define XT_XTP_DL_CANCEL 2
366 #define XT_XTP_DL_REMOVE 3
368 /* XTP history actions */
369 #define XT_XTP_HL_LIST 1
370 #define XT_XTP_HL_REMOVE 2
372 /* XTP cookie actions */
373 #define XT_XTP_CL_LIST 1
374 #define XT_XTP_CL_REMOVE 2
376 /* XTP cookie actions */
377 #define XT_XTP_FL_LIST 1
378 #define XT_XTP_FL_REMOVE 2
380 /* actions */
381 #define XT_MOVE_INVALID (0)
382 #define XT_MOVE_DOWN (1)
383 #define XT_MOVE_UP (2)
384 #define XT_MOVE_BOTTOM (3)
385 #define XT_MOVE_TOP (4)
386 #define XT_MOVE_PAGEDOWN (5)
387 #define XT_MOVE_PAGEUP (6)
388 #define XT_MOVE_HALFDOWN (7)
389 #define XT_MOVE_HALFUP (8)
390 #define XT_MOVE_LEFT (9)
391 #define XT_MOVE_FARLEFT (10)
392 #define XT_MOVE_RIGHT (11)
393 #define XT_MOVE_FARRIGHT (12)
395 #define XT_TAB_LAST (-4)
396 #define XT_TAB_FIRST (-3)
397 #define XT_TAB_PREV (-2)
398 #define XT_TAB_NEXT (-1)
399 #define XT_TAB_INVALID (0)
400 #define XT_TAB_NEW (1)
401 #define XT_TAB_DELETE (2)
402 #define XT_TAB_DELQUIT (3)
403 #define XT_TAB_OPEN (4)
404 #define XT_TAB_UNDO_CLOSE (5)
405 #define XT_TAB_SHOW (6)
406 #define XT_TAB_HIDE (7)
407 #define XT_TAB_NEXTSTYLE (8)
409 #define XT_NAV_INVALID (0)
410 #define XT_NAV_BACK (1)
411 #define XT_NAV_FORWARD (2)
412 #define XT_NAV_RELOAD (3)
413 #define XT_NAV_RELOAD_CACHE (4)
415 #define XT_FOCUS_INVALID (0)
416 #define XT_FOCUS_URI (1)
417 #define XT_FOCUS_SEARCH (2)
419 #define XT_SEARCH_INVALID (0)
420 #define XT_SEARCH_NEXT (1)
421 #define XT_SEARCH_PREV (2)
423 #define XT_PASTE_CURRENT_TAB (0)
424 #define XT_PASTE_NEW_TAB (1)
426 #define XT_FONT_SET (0)
428 #define XT_URL_SHOW (1)
429 #define XT_URL_HIDE (2)
431 #define XT_STATUSBAR_SHOW (1)
432 #define XT_STATUSBAR_HIDE (2)
434 #define XT_WL_TOGGLE (1<<0)
435 #define XT_WL_ENABLE (1<<1)
436 #define XT_WL_DISABLE (1<<2)
437 #define XT_WL_FQDN (1<<3) /* default */
438 #define XT_WL_TOPLEVEL (1<<4)
439 #define XT_WL_PERSISTENT (1<<5)
440 #define XT_WL_SESSION (1<<6)
441 #define XT_WL_RELOAD (1<<7)
443 #define XT_SHOW (1<<7)
444 #define XT_DELETE (1<<8)
445 #define XT_SAVE (1<<9)
446 #define XT_OPEN (1<<10)
448 #define XT_CMD_OPEN (0)
449 #define XT_CMD_OPEN_CURRENT (1)
450 #define XT_CMD_TABNEW (2)
451 #define XT_CMD_TABNEW_CURRENT (3)
453 #define XT_STATUS_NOTHING (0)
454 #define XT_STATUS_LINK (1)
455 #define XT_STATUS_URI (2)
456 #define XT_STATUS_LOADING (3)
458 #define XT_SES_DONOTHING (0)
459 #define XT_SES_CLOSETABS (1)
461 #define XT_BM_NORMAL (0)
462 #define XT_BM_WHITELIST (1)
463 #define XT_BM_KIOSK (2)
465 #define XT_PREFIX (1<<0)
466 #define XT_USERARG (1<<1)
467 #define XT_URLARG (1<<2)
468 #define XT_INTARG (1<<3)
470 #define XT_TABS_NORMAL 0
471 #define XT_TABS_COMPACT 1
473 /* mime types */
474 struct mime_type {
475 char *mt_type;
476 char *mt_action;
477 int mt_default;
478 int mt_download;
479 TAILQ_ENTRY(mime_type) entry;
481 TAILQ_HEAD(mime_type_list, mime_type);
483 /* uri aliases */
484 struct alias {
485 char *a_name;
486 char *a_uri;
487 TAILQ_ENTRY(alias) entry;
489 TAILQ_HEAD(alias_list, alias);
491 /* settings that require restart */
492 int tabless = 0; /* allow only 1 tab */
493 int enable_socket = 0;
494 int single_instance = 0; /* only allow one xxxterm to run */
495 int fancy_bar = 1; /* fancy toolbar */
496 int browser_mode = XT_BM_NORMAL;
497 int enable_localstorage = 0;
499 /* runtime settings */
500 int show_tabs = 1; /* show tabs on notebook */
501 int tab_style = XT_TABS_NORMAL; /* tab bar style */
502 int show_url = 1; /* show url toolbar on notebook */
503 int show_statusbar = 0; /* vimperator style status bar */
504 int ctrl_click_focus = 0; /* ctrl click gets focus */
505 int cookies_enabled = 1; /* enable cookies */
506 int read_only_cookies = 0; /* enable to not write cookies */
507 int enable_scripts = 1;
508 int enable_plugins = 0;
509 int default_font_size = 12;
510 gfloat default_zoom_level = 1.0;
511 int window_height = 768;
512 int window_width = 1024;
513 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
514 int refresh_interval = 10; /* download refresh interval */
515 int enable_cookie_whitelist = 0;
516 int enable_js_whitelist = 0;
517 int session_timeout = 3600; /* cookie session timeout */
518 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
519 char *ssl_ca_file = NULL;
520 char *resource_dir = NULL;
521 gboolean ssl_strict_certs = FALSE;
522 int append_next = 1; /* append tab after current tab */
523 char *home = NULL;
524 char *search_string = NULL;
525 char *http_proxy = NULL;
526 char download_dir[PATH_MAX];
527 char runtime_settings[PATH_MAX]; /* override of settings */
528 int allow_volatile_cookies = 0;
529 int save_global_history = 0; /* save global history to disk */
530 char *user_agent = NULL;
531 int save_rejected_cookies = 0;
532 int session_autosave = 0;
533 int guess_search = 0;
534 int dns_prefetch = FALSE;
535 gint max_connections = 25;
536 gint max_host_connections = 5;
537 gint enable_spell_checking = 0;
538 char *spell_check_languages = NULL;
540 char *cmd_font_name = NULL;
541 char *statusbar_font_name = NULL;
542 PangoFontDescription *cmd_font;
543 PangoFontDescription *statusbar_font;
545 struct settings;
546 struct key_binding;
547 int set_browser_mode(struct settings *, char *);
548 int set_cookie_policy(struct settings *, char *);
549 int set_download_dir(struct settings *, char *);
550 int set_runtime_dir(struct settings *, char *);
551 int set_tab_style(struct settings *, char *);
552 int set_work_dir(struct settings *, char *);
553 int add_alias(struct settings *, char *);
554 int add_mime_type(struct settings *, char *);
555 int add_cookie_wl(struct settings *, char *);
556 int add_js_wl(struct settings *, char *);
557 int add_kb(struct settings *, char *);
558 void button_set_stockid(GtkWidget *, char *);
559 GtkWidget * create_button(char *, char *, int);
561 char *get_browser_mode(struct settings *);
562 char *get_cookie_policy(struct settings *);
563 char *get_download_dir(struct settings *);
564 char *get_runtime_dir(struct settings *);
565 char *get_tab_style(struct settings *);
566 char *get_work_dir(struct settings *);
568 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
569 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
570 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
571 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
572 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
574 void recalc_tabs(void);
575 void recolor_compact_tabs(void);
576 void set_current_tab(int page_num);
578 struct special {
579 int (*set)(struct settings *, char *);
580 char *(*get)(struct settings *);
581 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
584 struct special s_browser_mode = {
585 set_browser_mode,
586 get_browser_mode,
587 NULL
590 struct special s_cookie = {
591 set_cookie_policy,
592 get_cookie_policy,
593 NULL
596 struct special s_alias = {
597 add_alias,
598 NULL,
599 walk_alias
602 struct special s_mime = {
603 add_mime_type,
604 NULL,
605 walk_mime_type
608 struct special s_js = {
609 add_js_wl,
610 NULL,
611 walk_js_wl
614 struct special s_kb = {
615 add_kb,
616 NULL,
617 walk_kb
620 struct special s_cookie_wl = {
621 add_cookie_wl,
622 NULL,
623 walk_cookie_wl
626 struct special s_download_dir = {
627 set_download_dir,
628 get_download_dir,
629 NULL
632 struct special s_work_dir = {
633 set_work_dir,
634 get_work_dir,
635 NULL
638 struct special s_tab_style = {
639 set_tab_style,
640 get_tab_style,
641 NULL
644 struct settings {
645 char *name;
646 int type;
647 #define XT_S_INVALID (0)
648 #define XT_S_INT (1)
649 #define XT_S_STR (2)
650 #define XT_S_FLOAT (3)
651 uint32_t flags;
652 #define XT_SF_RESTART (1<<0)
653 #define XT_SF_RUNTIME (1<<1)
654 int *ival;
655 char **sval;
656 struct special *s;
657 gfloat *fval;
658 } rs[] = {
659 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
660 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
661 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
662 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
663 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
664 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
665 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
666 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
667 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
668 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
669 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
670 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
671 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
672 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
673 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
674 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
675 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
676 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
677 { "home", XT_S_STR, 0, NULL, &home, NULL },
678 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
679 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
680 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
681 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
682 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
683 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
684 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
685 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
686 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
687 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
688 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
689 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
690 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
691 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
692 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
693 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
694 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
695 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
696 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
697 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
698 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
699 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
700 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
701 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
703 /* font settings */
704 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
705 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
707 /* runtime settings */
708 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
709 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
710 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
711 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
712 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
715 int about(struct tab *, struct karg *);
716 int blank(struct tab *, struct karg *);
717 int ca_cmd(struct tab *, struct karg *);
718 int cookie_show_wl(struct tab *, struct karg *);
719 int js_show_wl(struct tab *, struct karg *);
720 int help(struct tab *, struct karg *);
721 int set(struct tab *, struct karg *);
722 int stats(struct tab *, struct karg *);
723 int marco(struct tab *, struct karg *);
724 const char * marco_message(int *);
725 int xtp_page_cl(struct tab *, struct karg *);
726 int xtp_page_dl(struct tab *, struct karg *);
727 int xtp_page_fl(struct tab *, struct karg *);
728 int xtp_page_hl(struct tab *, struct karg *);
729 void xt_icon_from_file(struct tab *, char *);
730 const gchar *get_uri(struct tab *);
731 const gchar *get_title(struct tab *);
733 #define XT_URI_ABOUT ("about:")
734 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
735 #define XT_URI_ABOUT_ABOUT ("about")
736 #define XT_URI_ABOUT_BLANK ("blank")
737 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
738 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
739 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
740 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
741 #define XT_URI_ABOUT_FAVORITES ("favorites")
742 #define XT_URI_ABOUT_HELP ("help")
743 #define XT_URI_ABOUT_HISTORY ("history")
744 #define XT_URI_ABOUT_JSWL ("jswl")
745 #define XT_URI_ABOUT_SET ("set")
746 #define XT_URI_ABOUT_STATS ("stats")
747 #define XT_URI_ABOUT_MARCO ("marco")
749 struct about_type {
750 char *name;
751 int (*func)(struct tab *, struct karg *);
752 } about_list[] = {
753 { XT_URI_ABOUT_ABOUT, about },
754 { XT_URI_ABOUT_BLANK, blank },
755 { XT_URI_ABOUT_CERTS, ca_cmd },
756 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
757 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
758 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
759 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
760 { XT_URI_ABOUT_HELP, help },
761 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
762 { XT_URI_ABOUT_JSWL, js_show_wl },
763 { XT_URI_ABOUT_SET, set },
764 { XT_URI_ABOUT_STATS, stats },
765 { XT_URI_ABOUT_MARCO, marco },
768 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
769 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
770 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
771 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
772 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
773 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
774 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
776 /* globals */
777 extern char *__progname;
778 char **start_argv;
779 struct passwd *pwd;
780 GtkWidget *main_window;
781 GtkNotebook *notebook;
782 GtkWidget *tab_bar;
783 GtkWidget *arrow, *abtn;
784 struct tab_list tabs;
785 struct history_list hl;
786 struct download_list downloads;
787 struct domain_list c_wl;
788 struct domain_list js_wl;
789 struct undo_tailq undos;
790 struct keybinding_list kbl;
791 int undo_count;
792 int updating_dl_tabs = 0;
793 int updating_hl_tabs = 0;
794 int updating_cl_tabs = 0;
795 int updating_fl_tabs = 0;
796 char *global_search;
797 uint64_t blocked_cookies = 0;
798 char named_session[PATH_MAX];
799 int icon_size_map(int);
801 GtkListStore *completion_model;
802 void completion_add(struct tab *);
803 void completion_add_uri(const gchar *);
804 GtkListStore *buffers_store;
805 void xxx_dir(char *);
807 void
808 sigchild(int sig)
810 int saved_errno, status;
811 pid_t pid;
813 saved_errno = errno;
815 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
816 if (pid == -1) {
817 if (errno == EINTR)
818 continue;
819 if (errno != ECHILD) {
821 clog_warn("sigchild: waitpid:");
824 break;
827 if (WIFEXITED(status)) {
828 if (WEXITSTATUS(status) != 0) {
830 clog_warnx("sigchild: child exit status: %d",
831 WEXITSTATUS(status));
834 } else {
836 clog_warnx("sigchild: child is terminated abnormally");
841 errno = saved_errno;
845 is_g_object_setting(GObject *o, char *str)
847 guint n_props = 0, i;
848 GParamSpec **proplist;
850 if (! G_IS_OBJECT(o))
851 return (0);
853 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
854 &n_props);
856 for (i=0; i < n_props; i++) {
857 if (! strcmp(proplist[i]->name, str))
858 return (1);
860 return (0);
863 gchar *
864 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
866 gchar *r;
868 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
869 "<head>\n"
870 "<title>%s</title>\n"
871 "%s"
872 "%s"
873 "</head>\n"
874 "<body>\n"
875 "<h1>%s</h1>\n"
876 "%s\n</body>\n"
877 "</html>",
878 title,
879 addstyles ? XT_PAGE_STYLE : "",
880 head,
881 title,
882 body);
884 return r;
888 * Display a web page from a HTML string in memory, rather than from a URL
890 void
891 load_webkit_string(struct tab *t, const char *str, gchar *title)
893 char file[PATH_MAX];
894 int i;
896 /* we set this to indicate we want to manually do navaction */
897 if (t->bfl)
898 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
900 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
901 if (title) {
902 /* set t->xtp_meaning */
903 for (i = 0; i < LENGTH(about_list); i++)
904 if (!strcmp(title, about_list[i].name)) {
905 t->xtp_meaning = i;
906 break;
909 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
910 #if GTK_CHECK_VERSION(2, 20, 0)
911 gtk_spinner_stop(GTK_SPINNER(t->spinner));
912 gtk_widget_hide(t->spinner);
913 #endif
914 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
915 xt_icon_from_file(t, file);
919 struct tab *
920 get_current_tab(void)
922 struct tab *t;
924 TAILQ_FOREACH(t, &tabs, entry) {
925 if (t->tab_id == gtk_notebook_get_current_page(notebook))
926 return (t);
929 warnx("%s: no current tab", __func__);
931 return (NULL);
934 void
935 set_status(struct tab *t, gchar *s, int status)
937 gchar *type = NULL;
939 if (s == NULL)
940 return;
942 switch (status) {
943 case XT_STATUS_LOADING:
944 type = g_strdup_printf("Loading: %s", s);
945 s = type;
946 break;
947 case XT_STATUS_LINK:
948 type = g_strdup_printf("Link: %s", s);
949 if (!t->status)
950 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
951 s = type;
952 break;
953 case XT_STATUS_URI:
954 type = g_strdup_printf("%s", s);
955 if (!t->status) {
956 t->status = g_strdup(type);
958 s = type;
959 if (!t->status)
960 t->status = g_strdup(s);
961 break;
962 case XT_STATUS_NOTHING:
963 /* FALL THROUGH */
964 default:
965 break;
967 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
968 if (type)
969 g_free(type);
972 void
973 hide_cmd(struct tab *t)
975 gtk_widget_hide(t->cmd);
978 void
979 show_cmd(struct tab *t)
981 gtk_widget_hide(t->oops);
982 gtk_widget_show(t->cmd);
985 void
986 hide_buffers(struct tab *t)
988 gtk_widget_hide(t->buffers);
989 gtk_list_store_clear(buffers_store);
992 enum {
993 COL_ID = 0,
994 COL_TITLE,
995 NUM_COLS
999 sort_tabs_by_page_num(struct tab ***stabs)
1001 int num_tabs = 0;
1002 struct tab *t;
1004 num_tabs = gtk_notebook_get_n_pages(notebook);
1006 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1008 TAILQ_FOREACH(t, &tabs, entry)
1009 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1011 return (num_tabs);
1014 void
1015 buffers_make_list(void)
1017 int i, num_tabs;
1018 const gchar *title = NULL;
1019 GtkTreeIter iter;
1020 struct tab **stabs = NULL;
1022 num_tabs = sort_tabs_by_page_num(&stabs);
1024 for (i = 0; i < num_tabs; i++)
1025 if (stabs[i]) {
1026 gtk_list_store_append(buffers_store, &iter);
1027 title = get_title(stabs[i]);
1028 gtk_list_store_set(buffers_store, &iter,
1029 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1030 * rather than 0. */
1031 COL_TITLE, title,
1032 -1);
1035 g_free(stabs);
1038 void
1039 show_buffers(struct tab *t)
1041 buffers_make_list();
1042 gtk_widget_show(t->buffers);
1043 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1046 void
1047 toggle_buffers(struct tab *t)
1049 if (gtk_widget_get_visible(t->buffers))
1050 hide_buffers(t);
1051 else
1052 show_buffers(t);
1056 buffers(struct tab *t, struct karg *args)
1058 show_buffers(t);
1060 return (0);
1063 void
1064 hide_oops(struct tab *t)
1066 gtk_widget_hide(t->oops);
1069 void
1070 show_oops(struct tab *at, const char *fmt, ...)
1072 va_list ap;
1073 char *msg;
1074 struct tab *t = NULL;
1076 if (fmt == NULL)
1077 return;
1079 if (at == NULL) {
1080 if ((t = get_current_tab()) == NULL)
1081 return;
1082 } else
1083 t = at;
1085 va_start(ap, fmt);
1086 if (vasprintf(&msg, fmt, ap) == -1)
1087 errx(1, "show_oops failed");
1088 va_end(ap);
1090 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1091 gtk_widget_hide(t->cmd);
1092 gtk_widget_show(t->oops);
1095 char *
1096 get_as_string(struct settings *s)
1098 char *r = NULL;
1100 if (s == NULL)
1101 return (NULL);
1103 if (s->s) {
1104 if (s->s->get)
1105 r = s->s->get(s);
1106 else
1107 warnx("get_as_string skip %s\n", s->name);
1108 } else if (s->type == XT_S_INT)
1109 r = g_strdup_printf("%d", *s->ival);
1110 else if (s->type == XT_S_STR)
1111 r = g_strdup(*s->sval);
1112 else if (s->type == XT_S_FLOAT)
1113 r = g_strdup_printf("%f", *s->fval);
1114 else
1115 r = g_strdup_printf("INVALID TYPE");
1117 return (r);
1120 void
1121 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1123 int i;
1124 char *s;
1126 for (i = 0; i < LENGTH(rs); i++) {
1127 if (rs[i].s && rs[i].s->walk)
1128 rs[i].s->walk(&rs[i], cb, cb_args);
1129 else {
1130 s = get_as_string(&rs[i]);
1131 cb(&rs[i], s, cb_args);
1132 g_free(s);
1138 set_browser_mode(struct settings *s, char *val)
1140 if (!strcmp(val, "whitelist")) {
1141 browser_mode = XT_BM_WHITELIST;
1142 allow_volatile_cookies = 0;
1143 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1144 cookies_enabled = 1;
1145 enable_cookie_whitelist = 1;
1146 read_only_cookies = 0;
1147 save_rejected_cookies = 0;
1148 session_timeout = 3600;
1149 enable_scripts = 0;
1150 enable_js_whitelist = 1;
1151 enable_localstorage = 0;
1152 } else if (!strcmp(val, "normal")) {
1153 browser_mode = XT_BM_NORMAL;
1154 allow_volatile_cookies = 0;
1155 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1156 cookies_enabled = 1;
1157 enable_cookie_whitelist = 0;
1158 read_only_cookies = 0;
1159 save_rejected_cookies = 0;
1160 session_timeout = 3600;
1161 enable_scripts = 1;
1162 enable_js_whitelist = 0;
1163 enable_localstorage = 1;
1164 } else if (!strcmp(val, "kiosk")) {
1165 browser_mode = XT_BM_KIOSK;
1166 allow_volatile_cookies = 0;
1167 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1168 cookies_enabled = 1;
1169 enable_cookie_whitelist = 0;
1170 read_only_cookies = 0;
1171 save_rejected_cookies = 0;
1172 session_timeout = 3600;
1173 enable_scripts = 1;
1174 enable_js_whitelist = 0;
1175 enable_localstorage = 1;
1176 show_tabs = 0;
1177 tabless = 1;
1178 } else
1179 return (1);
1181 return (0);
1184 char *
1185 get_browser_mode(struct settings *s)
1187 char *r = NULL;
1189 if (browser_mode == XT_BM_WHITELIST)
1190 r = g_strdup("whitelist");
1191 else if (browser_mode == XT_BM_NORMAL)
1192 r = g_strdup("normal");
1193 else if (browser_mode == XT_BM_KIOSK)
1194 r = g_strdup("kiosk");
1195 else
1196 return (NULL);
1198 return (r);
1202 set_cookie_policy(struct settings *s, char *val)
1204 if (!strcmp(val, "no3rdparty"))
1205 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1206 else if (!strcmp(val, "accept"))
1207 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1208 else if (!strcmp(val, "reject"))
1209 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1210 else
1211 return (1);
1213 return (0);
1216 char *
1217 get_cookie_policy(struct settings *s)
1219 char *r = NULL;
1221 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1222 r = g_strdup("no3rdparty");
1223 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1224 r = g_strdup("accept");
1225 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1226 r = g_strdup("reject");
1227 else
1228 return (NULL);
1230 return (r);
1233 char *
1234 get_download_dir(struct settings *s)
1236 if (download_dir[0] == '\0')
1237 return (0);
1238 return (g_strdup(download_dir));
1242 set_download_dir(struct settings *s, char *val)
1244 if (val[0] == '~')
1245 snprintf(download_dir, sizeof download_dir, "%s/%s",
1246 pwd->pw_dir, &val[1]);
1247 else
1248 strlcpy(download_dir, val, sizeof download_dir);
1250 return (0);
1254 * Session IDs.
1255 * We use these to prevent people putting xxxt:// URLs on
1256 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1258 #define XT_XTP_SES_KEY_SZ 8
1259 #define XT_XTP_SES_KEY_HEX_FMT \
1260 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1261 char *dl_session_key; /* downloads */
1262 char *hl_session_key; /* history list */
1263 char *cl_session_key; /* cookie list */
1264 char *fl_session_key; /* favorites list */
1266 char work_dir[PATH_MAX];
1267 char certs_dir[PATH_MAX];
1268 char cache_dir[PATH_MAX];
1269 char sessions_dir[PATH_MAX];
1270 char cookie_file[PATH_MAX];
1271 SoupURI *proxy_uri = NULL;
1272 SoupSession *session;
1273 SoupCookieJar *s_cookiejar;
1274 SoupCookieJar *p_cookiejar;
1275 char rc_fname[PATH_MAX];
1277 struct mime_type_list mtl;
1278 struct alias_list aliases;
1280 /* protos */
1281 struct tab *create_new_tab(char *, struct undo *, int, int);
1282 void delete_tab(struct tab *);
1283 void adjustfont_webkit(struct tab *, int);
1284 int run_script(struct tab *, char *);
1285 int download_rb_cmp(struct download *, struct download *);
1286 gboolean cmd_execute(struct tab *t, char *str);
1289 history_rb_cmp(struct history *h1, struct history *h2)
1291 return (strcmp(h1->uri, h2->uri));
1293 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1296 domain_rb_cmp(struct domain *d1, struct domain *d2)
1298 return (strcmp(d1->d, d2->d));
1300 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1302 char *
1303 get_work_dir(struct settings *s)
1305 if (work_dir[0] == '\0')
1306 return (0);
1307 return (g_strdup(work_dir));
1311 set_work_dir(struct settings *s, char *val)
1313 if (val[0] == '~')
1314 snprintf(work_dir, sizeof work_dir, "%s/%s",
1315 pwd->pw_dir, &val[1]);
1316 else
1317 strlcpy(work_dir, val, sizeof work_dir);
1319 return (0);
1322 char *
1323 get_tab_style(struct settings *s)
1325 if (tab_style == XT_TABS_NORMAL)
1326 return (g_strdup("normal"));
1327 else
1328 return (g_strdup("compact"));
1332 set_tab_style(struct settings *s, char *val)
1334 if (!strcmp(val, "normal"))
1335 tab_style = XT_TABS_NORMAL;
1336 else if (!strcmp(val, "compact"))
1337 tab_style = XT_TABS_COMPACT;
1338 else
1339 return (1);
1341 return (0);
1345 * generate a session key to secure xtp commands.
1346 * pass in a ptr to the key in question and it will
1347 * be modified in place.
1349 void
1350 generate_xtp_session_key(char **key)
1352 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1354 /* free old key */
1355 if (*key)
1356 g_free(*key);
1358 /* make a new one */
1359 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1360 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1361 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1362 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1364 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1368 * validate a xtp session key.
1369 * return 1 if OK
1372 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1374 if (strcmp(trusted, untrusted) != 0) {
1375 show_oops(t, "%s: xtp session key mismatch possible spoof",
1376 __func__);
1377 return (0);
1380 return (1);
1384 download_rb_cmp(struct download *e1, struct download *e2)
1386 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1388 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1390 struct valid_url_types {
1391 char *type;
1392 } vut[] = {
1393 { "http://" },
1394 { "https://" },
1395 { "ftp://" },
1396 { "file://" },
1397 { XT_XTP_STR },
1401 valid_url_type(char *url)
1403 int i;
1405 for (i = 0; i < LENGTH(vut); i++)
1406 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1407 return (0);
1409 return (1);
1412 void
1413 print_cookie(char *msg, SoupCookie *c)
1415 if (c == NULL)
1416 return;
1418 if (msg)
1419 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1420 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1421 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1422 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1423 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1424 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1425 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1426 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1427 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1428 DNPRINTF(XT_D_COOKIE, "====================================\n");
1431 void
1432 walk_alias(struct settings *s,
1433 void (*cb)(struct settings *, char *, void *), void *cb_args)
1435 struct alias *a;
1436 char *str;
1438 if (s == NULL || cb == NULL) {
1439 show_oops(NULL, "walk_alias invalid parameters");
1440 return;
1443 TAILQ_FOREACH(a, &aliases, entry) {
1444 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1445 cb(s, str, cb_args);
1446 g_free(str);
1450 char *
1451 match_alias(char *url_in)
1453 struct alias *a;
1454 char *arg;
1455 char *url_out = NULL, *search, *enc_arg;
1457 search = g_strdup(url_in);
1458 arg = search;
1459 if (strsep(&arg, " \t") == NULL) {
1460 show_oops(NULL, "match_alias: NULL URL");
1461 goto done;
1464 TAILQ_FOREACH(a, &aliases, entry) {
1465 if (!strcmp(search, a->a_name))
1466 break;
1469 if (a != NULL) {
1470 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1471 a->a_name);
1472 if (arg != NULL) {
1473 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1474 url_out = g_strdup_printf(a->a_uri, enc_arg);
1475 g_free(enc_arg);
1476 } else
1477 url_out = g_strdup_printf(a->a_uri, "");
1479 done:
1480 g_free(search);
1481 return (url_out);
1484 char *
1485 guess_url_type(char *url_in)
1487 struct stat sb;
1488 char *url_out = NULL, *enc_search = NULL;
1490 url_out = match_alias(url_in);
1491 if (url_out != NULL)
1492 return (url_out);
1494 if (guess_search) {
1496 * If there is no dot nor slash in the string and it isn't a
1497 * path to a local file and doesn't resolves to an IP, assume
1498 * that the user wants to search for the string.
1501 if (strchr(url_in, '.') == NULL &&
1502 strchr(url_in, '/') == NULL &&
1503 stat(url_in, &sb) != 0 &&
1504 gethostbyname(url_in) == NULL) {
1506 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1507 url_out = g_strdup_printf(search_string, enc_search);
1508 g_free(enc_search);
1509 return (url_out);
1513 /* XXX not sure about this heuristic */
1514 if (stat(url_in, &sb) == 0)
1515 url_out = g_strdup_printf("file://%s", url_in);
1516 else
1517 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1519 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1521 return (url_out);
1524 void
1525 load_uri(struct tab *t, gchar *uri)
1527 struct karg args;
1528 gchar *newuri = NULL;
1529 int i;
1531 if (uri == NULL)
1532 return;
1534 /* Strip leading spaces. */
1535 while (*uri && isspace(*uri))
1536 uri++;
1538 if (strlen(uri) == 0) {
1539 blank(t, NULL);
1540 return;
1543 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1545 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1546 for (i = 0; i < LENGTH(about_list); i++)
1547 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1548 bzero(&args, sizeof args);
1549 about_list[i].func(t, &args);
1550 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1551 FALSE);
1552 return;
1554 show_oops(t, "invalid about page");
1555 return;
1558 if (valid_url_type(uri)) {
1559 newuri = guess_url_type(uri);
1560 uri = newuri;
1563 set_status(t, (char *)uri, XT_STATUS_LOADING);
1564 webkit_web_view_load_uri(t->wv, uri);
1566 if (newuri)
1567 g_free(newuri);
1570 const gchar *
1571 get_uri(struct tab *t)
1573 const gchar *uri = NULL;
1575 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
1576 uri = webkit_web_view_get_uri(t->wv);
1577 else
1578 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, about_list[t->xtp_meaning].name);
1580 return uri;
1583 const gchar *
1584 get_title(struct tab *t)
1586 const gchar *set = NULL, *title = NULL;
1588 title = webkit_web_view_get_title(t->wv);
1589 set = title ? title : get_uri(t);
1590 if (!set || t->xtp_meaning == XT_XTP_TAB_MEANING_BL) {
1591 set = "(untitled)";
1593 return set;
1597 add_alias(struct settings *s, char *line)
1599 char *l, *alias;
1600 struct alias *a = NULL;
1602 if (s == NULL || line == NULL) {
1603 show_oops(NULL, "add_alias invalid parameters");
1604 return (1);
1607 l = line;
1608 a = g_malloc(sizeof(*a));
1610 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1611 show_oops(NULL, "add_alias: incomplete alias definition");
1612 goto bad;
1614 if (strlen(alias) == 0 || strlen(l) == 0) {
1615 show_oops(NULL, "add_alias: invalid alias definition");
1616 goto bad;
1619 a->a_name = g_strdup(alias);
1620 a->a_uri = g_strdup(l);
1622 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1624 TAILQ_INSERT_TAIL(&aliases, a, entry);
1626 return (0);
1627 bad:
1628 if (a)
1629 g_free(a);
1630 return (1);
1634 add_mime_type(struct settings *s, char *line)
1636 char *mime_type;
1637 char *l;
1638 struct mime_type *m = NULL;
1639 int downloadfirst = 0;
1641 /* XXX this could be smarter */
1643 if (line == NULL || strlen(line) == 0) {
1644 show_oops(NULL, "add_mime_type invalid parameters");
1645 return (1);
1648 l = line;
1649 if (*l == '@') {
1650 downloadfirst = 1;
1651 l++;
1653 m = g_malloc(sizeof(*m));
1655 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1656 show_oops(NULL, "add_mime_type: invalid mime_type");
1657 goto bad;
1659 if (mime_type[strlen(mime_type) - 1] == '*') {
1660 mime_type[strlen(mime_type) - 1] = '\0';
1661 m->mt_default = 1;
1662 } else
1663 m->mt_default = 0;
1665 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1666 show_oops(NULL, "add_mime_type: invalid mime_type");
1667 goto bad;
1670 m->mt_type = g_strdup(mime_type);
1671 m->mt_action = g_strdup(l);
1672 m->mt_download = downloadfirst;
1674 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1675 m->mt_type, m->mt_action, m->mt_default);
1677 TAILQ_INSERT_TAIL(&mtl, m, entry);
1679 return (0);
1680 bad:
1681 if (m)
1682 g_free(m);
1683 return (1);
1686 struct mime_type *
1687 find_mime_type(char *mime_type)
1689 struct mime_type *m, *def = NULL, *rv = NULL;
1691 TAILQ_FOREACH(m, &mtl, entry) {
1692 if (m->mt_default &&
1693 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1694 def = m;
1696 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1697 rv = m;
1698 break;
1702 if (rv == NULL)
1703 rv = def;
1705 return (rv);
1708 void
1709 walk_mime_type(struct settings *s,
1710 void (*cb)(struct settings *, char *, void *), void *cb_args)
1712 struct mime_type *m;
1713 char *str;
1715 if (s == NULL || cb == NULL) {
1716 show_oops(NULL, "walk_mime_type invalid parameters");
1717 return;
1720 TAILQ_FOREACH(m, &mtl, entry) {
1721 str = g_strdup_printf("%s%s --> %s",
1722 m->mt_type,
1723 m->mt_default ? "*" : "",
1724 m->mt_action);
1725 cb(s, str, cb_args);
1726 g_free(str);
1730 void
1731 wl_add(char *str, struct domain_list *wl, int handy)
1733 struct domain *d;
1734 int add_dot = 0;
1736 if (str == NULL || wl == NULL || strlen(str) < 2)
1737 return;
1739 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1741 /* treat *.moo.com the same as .moo.com */
1742 if (str[0] == '*' && str[1] == '.')
1743 str = &str[1];
1744 else if (str[0] == '.')
1745 str = &str[0];
1746 else
1747 add_dot = 1;
1749 d = g_malloc(sizeof *d);
1750 if (add_dot)
1751 d->d = g_strdup_printf(".%s", str);
1752 else
1753 d->d = g_strdup(str);
1754 d->handy = handy;
1756 if (RB_INSERT(domain_list, wl, d))
1757 goto unwind;
1759 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1760 return;
1761 unwind:
1762 if (d) {
1763 if (d->d)
1764 g_free(d->d);
1765 g_free(d);
1770 add_cookie_wl(struct settings *s, char *entry)
1772 wl_add(entry, &c_wl, 1);
1773 return (0);
1776 void
1777 walk_cookie_wl(struct settings *s,
1778 void (*cb)(struct settings *, char *, void *), void *cb_args)
1780 struct domain *d;
1782 if (s == NULL || cb == NULL) {
1783 show_oops(NULL, "walk_cookie_wl invalid parameters");
1784 return;
1787 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1788 cb(s, d->d, cb_args);
1791 void
1792 walk_js_wl(struct settings *s,
1793 void (*cb)(struct settings *, char *, void *), void *cb_args)
1795 struct domain *d;
1797 if (s == NULL || cb == NULL) {
1798 show_oops(NULL, "walk_js_wl invalid parameters");
1799 return;
1802 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1803 cb(s, d->d, cb_args);
1807 add_js_wl(struct settings *s, char *entry)
1809 wl_add(entry, &js_wl, 1 /* persistent */);
1810 return (0);
1813 struct domain *
1814 wl_find(const gchar *search, struct domain_list *wl)
1816 int i;
1817 struct domain *d = NULL, dfind;
1818 gchar *s = NULL;
1820 if (search == NULL || wl == NULL)
1821 return (NULL);
1822 if (strlen(search) < 2)
1823 return (NULL);
1825 if (search[0] != '.')
1826 s = g_strdup_printf(".%s", search);
1827 else
1828 s = g_strdup(search);
1830 for (i = strlen(s) - 1; i >= 0; i--) {
1831 if (s[i] == '.') {
1832 dfind.d = &s[i];
1833 d = RB_FIND(domain_list, wl, &dfind);
1834 if (d)
1835 goto done;
1839 done:
1840 if (s)
1841 g_free(s);
1843 return (d);
1846 struct domain *
1847 wl_find_uri(const gchar *s, struct domain_list *wl)
1849 int i;
1850 char *ss;
1851 struct domain *r;
1853 if (s == NULL || wl == NULL)
1854 return (NULL);
1856 if (!strncmp(s, "http://", strlen("http://")))
1857 s = &s[strlen("http://")];
1858 else if (!strncmp(s, "https://", strlen("https://")))
1859 s = &s[strlen("https://")];
1861 if (strlen(s) < 2)
1862 return (NULL);
1864 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1865 /* chop string at first slash */
1866 if (s[i] == '/' || s[i] == '\0') {
1867 ss = g_strdup(s);
1868 ss[i] = '\0';
1869 r = wl_find(ss, wl);
1870 g_free(ss);
1871 return (r);
1874 return (NULL);
1877 char *
1878 get_toplevel_domain(char *domain)
1880 char *s;
1881 int found = 0;
1883 if (domain == NULL)
1884 return (NULL);
1885 if (strlen(domain) < 2)
1886 return (NULL);
1888 s = &domain[strlen(domain) - 1];
1889 while (s != domain) {
1890 if (*s == '.') {
1891 found++;
1892 if (found == 2)
1893 return (s);
1895 s--;
1898 if (found)
1899 return (domain);
1901 return (NULL);
1905 settings_add(char *var, char *val)
1907 int i, rv, *p;
1908 gfloat *f;
1909 char **s;
1911 /* get settings */
1912 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1913 if (strcmp(var, rs[i].name))
1914 continue;
1916 if (rs[i].s) {
1917 if (rs[i].s->set(&rs[i], val))
1918 errx(1, "invalid value for %s: %s", var, val);
1919 rv = 1;
1920 break;
1921 } else
1922 switch (rs[i].type) {
1923 case XT_S_INT:
1924 p = rs[i].ival;
1925 *p = atoi(val);
1926 rv = 1;
1927 break;
1928 case XT_S_STR:
1929 s = rs[i].sval;
1930 if (s == NULL)
1931 errx(1, "invalid sval for %s",
1932 rs[i].name);
1933 if (*s)
1934 g_free(*s);
1935 *s = g_strdup(val);
1936 rv = 1;
1937 break;
1938 case XT_S_FLOAT:
1939 f = rs[i].fval;
1940 *f = atof(val);
1941 rv = 1;
1942 break;
1943 case XT_S_INVALID:
1944 default:
1945 errx(1, "invalid type for %s", var);
1947 break;
1949 return (rv);
1952 #define WS "\n= \t"
1953 void
1954 config_parse(char *filename, int runtime)
1956 FILE *config, *f;
1957 char *line, *cp, *var, *val;
1958 size_t len, lineno = 0;
1959 int handled;
1960 char file[PATH_MAX];
1961 struct stat sb;
1963 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1965 if (filename == NULL)
1966 return;
1968 if (runtime && runtime_settings[0] != '\0') {
1969 snprintf(file, sizeof file, "%s/%s",
1970 work_dir, runtime_settings);
1971 if (stat(file, &sb)) {
1972 warnx("runtime file doesn't exist, creating it");
1973 if ((f = fopen(file, "w")) == NULL)
1974 err(1, "runtime");
1975 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1976 fclose(f);
1978 } else
1979 strlcpy(file, filename, sizeof file);
1981 if ((config = fopen(file, "r")) == NULL) {
1982 warn("config_parse: cannot open %s", filename);
1983 return;
1986 for (;;) {
1987 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1988 if (feof(config) || ferror(config))
1989 break;
1991 cp = line;
1992 cp += (long)strspn(cp, WS);
1993 if (cp[0] == '\0') {
1994 /* empty line */
1995 free(line);
1996 continue;
1999 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2000 errx(1, "invalid config file entry: %s", line);
2002 cp += (long)strspn(cp, WS);
2004 if ((val = strsep(&cp, "\0")) == NULL)
2005 break;
2007 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2008 handled = settings_add(var, val);
2009 if (handled == 0)
2010 errx(1, "invalid conf file entry: %s=%s", var, val);
2012 free(line);
2015 fclose(config);
2018 char *
2019 js_ref_to_string(JSContextRef context, JSValueRef ref)
2021 char *s = NULL;
2022 size_t l;
2023 JSStringRef jsref;
2025 jsref = JSValueToStringCopy(context, ref, NULL);
2026 if (jsref == NULL)
2027 return (NULL);
2029 l = JSStringGetMaximumUTF8CStringSize(jsref);
2030 s = g_malloc(l);
2031 if (s)
2032 JSStringGetUTF8CString(jsref, s, l);
2033 JSStringRelease(jsref);
2035 return (s);
2038 void
2039 disable_hints(struct tab *t)
2041 bzero(t->hint_buf, sizeof t->hint_buf);
2042 bzero(t->hint_num, sizeof t->hint_num);
2043 run_script(t, "vimprobable_clear()");
2044 t->hints_on = 0;
2045 t->hint_mode = XT_HINT_NONE;
2048 void
2049 enable_hints(struct tab *t)
2051 bzero(t->hint_buf, sizeof t->hint_buf);
2052 run_script(t, "vimprobable_show_hints()");
2053 t->hints_on = 1;
2054 t->hint_mode = XT_HINT_NONE;
2057 #define XT_JS_OPEN ("open;")
2058 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2059 #define XT_JS_FIRE ("fire;")
2060 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2061 #define XT_JS_FOUND ("found;")
2062 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2065 run_script(struct tab *t, char *s)
2067 JSGlobalContextRef ctx;
2068 WebKitWebFrame *frame;
2069 JSStringRef str;
2070 JSValueRef val, exception;
2071 char *es, buf[128];
2073 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2074 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2076 frame = webkit_web_view_get_main_frame(t->wv);
2077 ctx = webkit_web_frame_get_global_context(frame);
2079 str = JSStringCreateWithUTF8CString(s);
2080 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2081 NULL, 0, &exception);
2082 JSStringRelease(str);
2084 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2085 if (val == NULL) {
2086 es = js_ref_to_string(ctx, exception);
2087 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2088 g_free(es);
2089 return (1);
2090 } else {
2091 es = js_ref_to_string(ctx, val);
2092 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2094 /* handle return value right here */
2095 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2096 disable_hints(t);
2097 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
2100 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2101 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2102 &es[XT_JS_FIRE_LEN]);
2103 run_script(t, buf);
2104 disable_hints(t);
2107 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2108 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2109 disable_hints(t);
2112 g_free(es);
2115 return (0);
2119 hint(struct tab *t, struct karg *args)
2122 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2124 if (t->hints_on == 0)
2125 enable_hints(t);
2126 else
2127 disable_hints(t);
2129 return (0);
2132 void
2133 apply_style(struct tab *t)
2135 g_object_set(G_OBJECT(t->settings),
2136 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2140 userstyle(struct tab *t, struct karg *args)
2142 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2144 if (t->styled) {
2145 t->styled = 0;
2146 g_object_set(G_OBJECT(t->settings),
2147 "user-stylesheet-uri", NULL, (char *)NULL);
2148 } else {
2149 t->styled = 1;
2150 apply_style(t);
2152 return (0);
2156 * Doesn't work fully, due to the following bug:
2157 * https://bugs.webkit.org/show_bug.cgi?id=51747
2160 restore_global_history(void)
2162 char file[PATH_MAX];
2163 FILE *f;
2164 struct history *h;
2165 gchar *uri;
2166 gchar *title;
2167 const char delim[3] = {'\\', '\\', '\0'};
2169 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2171 if ((f = fopen(file, "r")) == NULL) {
2172 warnx("%s: fopen", __func__);
2173 return (1);
2176 for (;;) {
2177 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2178 if (feof(f) || ferror(f))
2179 break;
2181 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2182 if (feof(f) || ferror(f)) {
2183 free(uri);
2184 warnx("%s: broken history file\n", __func__);
2185 return (1);
2188 if (uri && strlen(uri) && title && strlen(title)) {
2189 webkit_web_history_item_new_with_data(uri, title);
2190 h = g_malloc(sizeof(struct history));
2191 h->uri = g_strdup(uri);
2192 h->title = g_strdup(title);
2193 RB_INSERT(history_list, &hl, h);
2194 completion_add_uri(h->uri);
2195 } else {
2196 warnx("%s: failed to restore history\n", __func__);
2197 free(uri);
2198 free(title);
2199 return (1);
2202 free(uri);
2203 free(title);
2204 uri = NULL;
2205 title = NULL;
2208 return (0);
2212 save_global_history_to_disk(struct tab *t)
2214 char file[PATH_MAX];
2215 FILE *f;
2216 struct history *h;
2218 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2220 if ((f = fopen(file, "w")) == NULL) {
2221 show_oops(t, "%s: global history file: %s",
2222 __func__, strerror(errno));
2223 return (1);
2226 RB_FOREACH_REVERSE(h, history_list, &hl) {
2227 if (h->uri && h->title)
2228 fprintf(f, "%s\n%s\n", h->uri, h->title);
2231 fclose(f);
2233 return (0);
2237 quit(struct tab *t, struct karg *args)
2239 if (save_global_history)
2240 save_global_history_to_disk(t);
2242 gtk_main_quit();
2244 return (1);
2248 open_tabs(struct tab *t, struct karg *a)
2250 char file[PATH_MAX];
2251 FILE *f = NULL;
2252 char *uri = NULL;
2253 int rv = 1;
2254 struct tab *ti, *tt;
2256 if (a == NULL)
2257 goto done;
2259 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2260 if ((f = fopen(file, "r")) == NULL)
2261 goto done;
2263 ti = TAILQ_LAST(&tabs, tab_list);
2265 for (;;) {
2266 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2267 if (feof(f) || ferror(f))
2268 break;
2270 /* retrieve session name */
2271 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2272 strlcpy(named_session,
2273 &uri[strlen(XT_SAVE_SESSION_ID)],
2274 sizeof named_session);
2275 continue;
2278 if (uri && strlen(uri))
2279 create_new_tab(uri, NULL, 1, -1);
2281 free(uri);
2282 uri = NULL;
2285 /* close open tabs */
2286 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2287 for (;;) {
2288 tt = TAILQ_FIRST(&tabs);
2289 if (tt != ti) {
2290 delete_tab(tt);
2291 continue;
2293 delete_tab(tt);
2294 break;
2296 recalc_tabs();
2299 rv = 0;
2300 done:
2301 if (f)
2302 fclose(f);
2304 return (rv);
2308 restore_saved_tabs(void)
2310 char file[PATH_MAX];
2311 int unlink_file = 0;
2312 struct stat sb;
2313 struct karg a;
2314 int rv = 0;
2316 snprintf(file, sizeof file, "%s/%s",
2317 sessions_dir, XT_RESTART_TABS_FILE);
2318 if (stat(file, &sb) == -1)
2319 a.s = XT_SAVED_TABS_FILE;
2320 else {
2321 unlink_file = 1;
2322 a.s = XT_RESTART_TABS_FILE;
2325 a.i = XT_SES_DONOTHING;
2326 rv = open_tabs(NULL, &a);
2328 if (unlink_file)
2329 unlink(file);
2331 return (rv);
2335 save_tabs(struct tab *t, struct karg *a)
2337 char file[PATH_MAX];
2338 FILE *f;
2339 int num_tabs = 0, i;
2340 struct tab **stabs = NULL;
2342 if (a == NULL)
2343 return (1);
2344 if (a->s == NULL)
2345 snprintf(file, sizeof file, "%s/%s",
2346 sessions_dir, named_session);
2347 else
2348 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2350 if ((f = fopen(file, "w")) == NULL) {
2351 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2352 return (1);
2355 /* save session name */
2356 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2358 /* Save tabs, in the order they are arranged in the notebook. */
2359 num_tabs = sort_tabs_by_page_num(&stabs);
2361 for (i = 0; i < num_tabs; i++)
2362 if (stabs[i] && get_uri(stabs[i]) != NULL)
2363 fprintf(f, "%s\n", get_uri(stabs[i]));
2365 g_free(stabs);
2367 /* try and make sure this gets to disk NOW. XXX Backup first? */
2368 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2369 show_oops(t, "May not have managed to save session: %s",
2370 strerror(errno));
2373 fclose(f);
2375 return (0);
2379 save_tabs_and_quit(struct tab *t, struct karg *args)
2381 struct karg a;
2383 a.s = NULL;
2384 save_tabs(t, &a);
2385 quit(t, NULL);
2387 return (1);
2391 yank_uri(struct tab *t, struct karg *args)
2393 const gchar *uri;
2394 GtkClipboard *clipboard;
2396 if ((uri = get_uri(t)) == NULL)
2397 return (1);
2399 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2400 gtk_clipboard_set_text(clipboard, uri, -1);
2402 return (0);
2406 paste_uri(struct tab *t, struct karg *args)
2408 GtkClipboard *clipboard;
2409 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2410 gint len;
2411 gchar *p = NULL, *uri;
2413 /* try primary clipboard first */
2414 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2415 p = gtk_clipboard_wait_for_text(clipboard);
2417 /* if it failed get whatever text is in cut_buffer0 */
2418 if (p == NULL)
2419 if (gdk_property_get(gdk_get_default_root_window(),
2420 atom,
2421 gdk_atom_intern("STRING", FALSE),
2423 65536 /* picked out of my butt */,
2424 FALSE,
2425 NULL,
2426 NULL,
2427 &len,
2428 (guchar **)&p)) {
2429 /* yes sir, we need to NUL the string */
2430 p[len] = '\0';
2433 if (p) {
2434 uri = p;
2435 while (*uri && isspace(*uri))
2436 uri++;
2437 if (strlen(uri) == 0) {
2438 show_oops(t, "empty paste buffer");
2439 goto done;
2441 if (guess_search == 0 && valid_url_type(uri)) {
2442 /* we can be clever and paste this in search box */
2443 show_oops(t, "not a valid URL");
2444 goto done;
2447 if (args->i == XT_PASTE_CURRENT_TAB)
2448 load_uri(t, uri);
2449 else if (args->i == XT_PASTE_NEW_TAB)
2450 create_new_tab(uri, NULL, 1, -1);
2453 done:
2454 if (p)
2455 g_free(p);
2457 return (0);
2460 char *
2461 find_domain(const gchar *s, int add_dot)
2463 int i;
2464 char *r = NULL, *ss = NULL;
2466 if (s == NULL)
2467 return (NULL);
2469 if (!strncmp(s, "http://", strlen("http://")))
2470 s = &s[strlen("http://")];
2471 else if (!strncmp(s, "https://", strlen("https://")))
2472 s = &s[strlen("https://")];
2474 if (strlen(s) < 2)
2475 return (NULL);
2477 ss = g_strdup(s);
2478 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2479 /* chop string at first slash */
2480 if (ss[i] == '/' || ss[i] == '\0') {
2481 ss[i] = '\0';
2482 if (add_dot)
2483 r = g_strdup_printf(".%s", ss);
2484 else
2485 r = g_strdup(ss);
2486 break;
2488 g_free(ss);
2490 return (r);
2494 toggle_cwl(struct tab *t, struct karg *args)
2496 struct domain *d;
2497 const gchar *uri;
2498 char *dom = NULL, *dom_toggle = NULL;
2499 int es;
2501 if (args == NULL)
2502 return (1);
2504 uri = get_uri(t);
2505 dom = find_domain(uri, 1);
2506 d = wl_find(dom, &c_wl);
2508 if (d == NULL)
2509 es = 0;
2510 else
2511 es = 1;
2513 if (args->i & XT_WL_TOGGLE)
2514 es = !es;
2515 else if ((args->i & XT_WL_ENABLE) && es != 1)
2516 es = 1;
2517 else if ((args->i & XT_WL_DISABLE) && es != 0)
2518 es = 0;
2520 if (args->i & XT_WL_TOPLEVEL)
2521 dom_toggle = get_toplevel_domain(dom);
2522 else
2523 dom_toggle = dom;
2525 if (es)
2526 /* enable cookies for domain */
2527 wl_add(dom_toggle, &c_wl, 0);
2528 else
2529 /* disable cookies for domain */
2530 RB_REMOVE(domain_list, &c_wl, d);
2532 if (args->i & XT_WL_RELOAD)
2533 webkit_web_view_reload(t->wv);
2535 g_free(dom);
2536 return (0);
2540 toggle_js(struct tab *t, struct karg *args)
2542 int es;
2543 const gchar *uri;
2544 struct domain *d;
2545 char *dom = NULL, *dom_toggle = NULL;
2547 if (args == NULL)
2548 return (1);
2550 g_object_get(G_OBJECT(t->settings),
2551 "enable-scripts", &es, (char *)NULL);
2552 if (args->i & XT_WL_TOGGLE)
2553 es = !es;
2554 else if ((args->i & XT_WL_ENABLE) && es != 1)
2555 es = 1;
2556 else if ((args->i & XT_WL_DISABLE) && es != 0)
2557 es = 0;
2558 else
2559 return (1);
2561 uri = get_uri(t);
2562 dom = find_domain(uri, 1);
2564 if (uri == NULL || dom == NULL) {
2565 show_oops(t, "Can't toggle domain in JavaScript white list");
2566 goto done;
2569 if (args->i & XT_WL_TOPLEVEL)
2570 dom_toggle = get_toplevel_domain(dom);
2571 else
2572 dom_toggle = dom;
2574 if (es) {
2575 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2576 wl_add(dom_toggle, &js_wl, 0 /* session */);
2577 } else {
2578 d = wl_find(dom_toggle, &js_wl);
2579 if (d)
2580 RB_REMOVE(domain_list, &js_wl, d);
2581 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2583 g_object_set(G_OBJECT(t->settings),
2584 "enable-scripts", es, (char *)NULL);
2585 g_object_set(G_OBJECT(t->settings),
2586 "javascript-can-open-windows-automatically", es, (char *)NULL);
2587 webkit_web_view_set_settings(t->wv, t->settings);
2589 if (args->i & XT_WL_RELOAD)
2590 webkit_web_view_reload(t->wv);
2591 done:
2592 if (dom)
2593 g_free(dom);
2594 return (0);
2597 void
2598 js_toggle_cb(GtkWidget *w, struct tab *t)
2600 struct karg a;
2602 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2603 toggle_cwl(t, &a);
2605 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2606 toggle_js(t, &a);
2610 toggle_src(struct tab *t, struct karg *args)
2612 gboolean mode;
2614 if (t == NULL)
2615 return (0);
2617 mode = webkit_web_view_get_view_source_mode(t->wv);
2618 webkit_web_view_set_view_source_mode(t->wv, !mode);
2619 webkit_web_view_reload(t->wv);
2621 return (0);
2624 void
2625 focus_webview(struct tab *t)
2627 if (t == NULL)
2628 return;
2630 /* only grab focus if we are visible */
2631 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2632 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2636 focus(struct tab *t, struct karg *args)
2638 if (t == NULL || args == NULL)
2639 return (1);
2641 if (show_url == 0)
2642 return (0);
2644 if (args->i == XT_FOCUS_URI)
2645 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2646 else if (args->i == XT_FOCUS_SEARCH)
2647 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2649 return (0);
2653 stats(struct tab *t, struct karg *args)
2655 char *page, *body, *s, line[64 * 1024];
2656 uint64_t line_count = 0;
2657 FILE *r_cookie_f;
2659 if (t == NULL)
2660 show_oops(NULL, "stats invalid parameters");
2662 line[0] = '\0';
2663 if (save_rejected_cookies) {
2664 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2665 for (;;) {
2666 s = fgets(line, sizeof line, r_cookie_f);
2667 if (s == NULL || feof(r_cookie_f) ||
2668 ferror(r_cookie_f))
2669 break;
2670 line_count++;
2672 fclose(r_cookie_f);
2673 snprintf(line, sizeof line,
2674 "<br/>Cookies blocked(*) total: %llu", line_count);
2675 } else
2676 show_oops(t, "Can't open blocked cookies file: %s",
2677 strerror(errno));
2680 body = g_strdup_printf(
2681 "Cookies blocked(*) this session: %llu"
2682 "%s"
2683 "<p><small><b>*</b> results vary based on settings</small></p>",
2684 blocked_cookies,
2685 line);
2687 page = get_html_page("Statistics", body, "", 0);
2688 g_free(body);
2690 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2691 g_free(page);
2693 return (0);
2697 marco(struct tab *t, struct karg *args)
2699 char *page, line[64 * 1024];
2700 int len;
2702 if (t == NULL)
2703 show_oops(NULL, "marco invalid parameters");
2705 line[0] = '\0';
2706 snprintf(line, sizeof line, "%s", marco_message(&len));
2708 page = get_html_page("Marco Sez...", line, "", 0);
2710 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2711 g_free(page);
2713 return (0);
2717 blank(struct tab *t, struct karg *args)
2719 if (t == NULL)
2720 show_oops(NULL, "blank invalid parameters");
2722 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2724 return (0);
2727 about(struct tab *t, struct karg *args)
2729 char *page, *body;
2731 if (t == NULL)
2732 show_oops(NULL, "about invalid parameters");
2734 body = g_strdup_printf("<b>Version: %s</b><p>"
2735 "Authors:"
2736 "<ul>"
2737 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2738 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2739 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2740 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2741 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2742 "</ul>"
2743 "Copyrights and licenses can be found on the XXXterm "
2744 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2745 version
2748 page = get_html_page("About", body, "", 0);
2749 g_free(body);
2751 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2752 g_free(page);
2754 return (0);
2758 help(struct tab *t, struct karg *args)
2760 char *page, *head, *body;
2762 if (t == NULL)
2763 show_oops(NULL, "help invalid parameters");
2765 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2766 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2767 "</head>\n";
2768 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2769 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2770 "cgi-bin/man-cgi?xxxterm</a>";
2772 page = get_html_page("XXXterm", body, head, FALSE);
2774 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2775 g_free(page);
2777 return (0);
2781 * update all favorite tabs apart from one. Pass NULL if
2782 * you want to update all.
2784 void
2785 update_favorite_tabs(struct tab *apart_from)
2787 struct tab *t;
2788 if (!updating_fl_tabs) {
2789 updating_fl_tabs = 1; /* stop infinite recursion */
2790 TAILQ_FOREACH(t, &tabs, entry)
2791 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2792 && (t != apart_from))
2793 xtp_page_fl(t, NULL);
2794 updating_fl_tabs = 0;
2798 /* show a list of favorites (bookmarks) */
2800 xtp_page_fl(struct tab *t, struct karg *args)
2802 char file[PATH_MAX];
2803 FILE *f;
2804 char *uri = NULL, *title = NULL;
2805 size_t len, lineno = 0;
2806 int i, failed = 0;
2807 char *body, *tmp, *page = NULL;
2808 const char delim[3] = {'\\', '\\', '\0'};
2810 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2812 if (t == NULL)
2813 warn("%s: bad param", __func__);
2815 /* new session key */
2816 if (!updating_fl_tabs)
2817 generate_xtp_session_key(&fl_session_key);
2819 /* open favorites */
2820 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2821 if ((f = fopen(file, "r")) == NULL) {
2822 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2823 return (1);
2826 /* body */
2827 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2828 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2829 "<th style='width: 40px'>Rm</th></tr>\n");
2831 for (i = 1;;) {
2832 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2833 if (feof(f) || ferror(f))
2834 break;
2835 if (len == 0) {
2836 free(title);
2837 title = NULL;
2838 continue;
2841 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2842 if (feof(f) || ferror(f)) {
2843 show_oops(t, "favorites file corrupt");
2844 failed = 1;
2845 break;
2848 tmp = body;
2849 body = g_strdup_printf("%s<tr>"
2850 "<td>%d</td>"
2851 "<td><a href='%s'>%s</a></td>"
2852 "<td style='text-align: center'>"
2853 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2854 "</tr>\n",
2855 body, i, uri, title,
2856 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2858 g_free(tmp);
2860 free(uri);
2861 uri = NULL;
2862 free(title);
2863 title = NULL;
2864 i++;
2866 fclose(f);
2868 /* if none, say so */
2869 if (i == 1) {
2870 tmp = body;
2871 body = g_strdup_printf("%s<tr>"
2872 "<td colspan='3' style='text-align: center'>"
2873 "No favorites - To add one use the 'favadd' command."
2874 "</td></tr>", body);
2875 g_free(tmp);
2878 tmp = body;
2879 body = g_strdup_printf("%s</table>", body);
2880 g_free(tmp);
2882 if (uri)
2883 free(uri);
2884 if (title)
2885 free(title);
2887 /* render */
2888 if (!failed) {
2889 page = get_html_page("Favorites", body, "", 1);
2890 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2891 g_free(page);
2894 update_favorite_tabs(t);
2896 if (body)
2897 g_free(body);
2899 return (failed);
2902 void
2903 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2904 size_t cert_count, char *title)
2906 gnutls_datum_t cinfo;
2907 char *tmp, *body;
2908 int i;
2910 body = g_strdup("");
2912 for (i = 0; i < cert_count; i++) {
2913 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2914 &cinfo))
2915 return;
2917 tmp = body;
2918 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2919 body, i, cinfo.data);
2920 gnutls_free(cinfo.data);
2921 g_free(tmp);
2924 tmp = get_html_page(title, body, "", 0);
2925 g_free(body);
2927 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2928 g_free(tmp);
2932 ca_cmd(struct tab *t, struct karg *args)
2934 FILE *f = NULL;
2935 int rv = 1, certs = 0, certs_read;
2936 struct stat sb;
2937 gnutls_datum_t dt;
2938 gnutls_x509_crt_t *c = NULL;
2939 char *certs_buf = NULL, *s;
2941 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2942 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2943 return (1);
2946 if (fstat(fileno(f), &sb) == -1) {
2947 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2948 goto done;
2951 certs_buf = g_malloc(sb.st_size + 1);
2952 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2953 show_oops(t, "Can't read CA file: %s", strerror(errno));
2954 goto done;
2956 certs_buf[sb.st_size] = '\0';
2958 s = certs_buf;
2959 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2960 certs++;
2961 s += strlen("BEGIN CERTIFICATE");
2964 bzero(&dt, sizeof dt);
2965 dt.data = (unsigned char *)certs_buf;
2966 dt.size = sb.st_size;
2967 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2968 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
2969 GNUTLS_X509_FMT_PEM, 0);
2970 if (certs_read <= 0) {
2971 show_oops(t, "No cert(s) available");
2972 goto done;
2974 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2975 done:
2976 if (c)
2977 g_free(c);
2978 if (certs_buf)
2979 g_free(certs_buf);
2980 if (f)
2981 fclose(f);
2983 return (rv);
2987 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2989 SoupURI *su = NULL;
2990 struct addrinfo hints, *res = NULL, *ai;
2991 int s = -1, on;
2992 char port[8];
2994 if (uri && !g_str_has_prefix(uri, "https://"))
2995 goto done;
2997 su = soup_uri_new(uri);
2998 if (su == NULL)
2999 goto done;
3000 if (!SOUP_URI_VALID_FOR_HTTP(su))
3001 goto done;
3003 snprintf(port, sizeof port, "%d", su->port);
3004 bzero(&hints, sizeof(struct addrinfo));
3005 hints.ai_flags = AI_CANONNAME;
3006 hints.ai_family = AF_UNSPEC;
3007 hints.ai_socktype = SOCK_STREAM;
3009 if (getaddrinfo(su->host, port, &hints, &res))
3010 goto done;
3012 for (ai = res; ai; ai = ai->ai_next) {
3013 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3014 continue;
3016 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3017 if (s < 0)
3018 goto done;
3019 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3020 sizeof(on)) == -1)
3021 goto done;
3023 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3024 goto done;
3027 if (domain)
3028 strlcpy(domain, su->host, domain_sz);
3029 done:
3030 if (su)
3031 soup_uri_free(su);
3032 if (res)
3033 freeaddrinfo(res);
3035 return (s);
3039 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3041 if (gsession)
3042 gnutls_deinit(gsession);
3043 if (xcred)
3044 gnutls_certificate_free_credentials(xcred);
3046 return (0);
3050 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3051 gnutls_certificate_credentials_t *xc)
3053 gnutls_certificate_credentials_t xcred;
3054 gnutls_session_t gsession;
3055 int rv = 1;
3057 if (gs == NULL || xc == NULL)
3058 goto done;
3060 *gs = NULL;
3061 *xc = NULL;
3063 gnutls_certificate_allocate_credentials(&xcred);
3064 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3065 GNUTLS_X509_FMT_PEM);
3066 gnutls_init(&gsession, GNUTLS_CLIENT);
3067 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3068 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3069 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3070 if ((rv = gnutls_handshake(gsession)) < 0) {
3071 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3073 gnutls_error_is_fatal(rv),
3074 gnutls_strerror_name(rv));
3075 stop_tls(gsession, xcred);
3076 goto done;
3079 gnutls_credentials_type_t cred;
3080 cred = gnutls_auth_get_type(gsession);
3081 if (cred != GNUTLS_CRD_CERTIFICATE) {
3082 stop_tls(gsession, xcred);
3083 goto done;
3086 *gs = gsession;
3087 *xc = xcred;
3088 rv = 0;
3089 done:
3090 return (rv);
3094 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3095 size_t *cert_count)
3097 unsigned int len;
3098 const gnutls_datum_t *cl;
3099 gnutls_x509_crt_t *all_certs;
3100 int i, rv = 1;
3102 if (certs == NULL || cert_count == NULL)
3103 goto done;
3104 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3105 goto done;
3106 cl = gnutls_certificate_get_peers(gsession, &len);
3107 if (len == 0)
3108 goto done;
3110 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3111 for (i = 0; i < len; i++) {
3112 gnutls_x509_crt_init(&all_certs[i]);
3113 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3114 GNUTLS_X509_FMT_PEM < 0)) {
3115 g_free(all_certs);
3116 goto done;
3120 *certs = all_certs;
3121 *cert_count = len;
3122 rv = 0;
3123 done:
3124 return (rv);
3127 void
3128 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3130 int i;
3132 for (i = 0; i < cert_count; i++)
3133 gnutls_x509_crt_deinit(certs[i]);
3134 g_free(certs);
3137 void
3138 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3139 size_t cert_count, char *domain)
3141 size_t cert_buf_sz;
3142 char cert_buf[64 * 1024], file[PATH_MAX];
3143 int i;
3144 FILE *f;
3145 GdkColor color;
3147 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3148 return;
3150 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3151 if ((f = fopen(file, "w")) == NULL) {
3152 show_oops(t, "Can't create cert file %s %s",
3153 file, strerror(errno));
3154 return;
3157 for (i = 0; i < cert_count; i++) {
3158 cert_buf_sz = sizeof cert_buf;
3159 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3160 cert_buf, &cert_buf_sz)) {
3161 show_oops(t, "gnutls_x509_crt_export failed");
3162 goto done;
3164 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3165 show_oops(t, "Can't write certs: %s", strerror(errno));
3166 goto done;
3170 /* not the best spot but oh well */
3171 gdk_color_parse("lightblue", &color);
3172 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3173 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3174 gdk_color_parse(XT_COLOR_BLACK, &color);
3175 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3176 done:
3177 fclose(f);
3181 load_compare_cert(struct tab *t, struct karg *args)
3183 const gchar *uri;
3184 char domain[8182], file[PATH_MAX];
3185 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3186 int s = -1, rv = 1, i;
3187 size_t cert_count;
3188 FILE *f = NULL;
3189 size_t cert_buf_sz;
3190 gnutls_session_t gsession;
3191 gnutls_x509_crt_t *certs;
3192 gnutls_certificate_credentials_t xcred;
3194 if (t == NULL)
3195 return (1);
3197 if ((uri = get_uri(t)) == NULL)
3198 return (1);
3200 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3201 return (1);
3203 /* go ssl/tls */
3204 if (start_tls(t, s, &gsession, &xcred)) {
3205 show_oops(t, "Start TLS failed");
3206 goto done;
3209 /* get certs */
3210 if (get_connection_certs(gsession, &certs, &cert_count)) {
3211 show_oops(t, "Can't get connection certificates");
3212 goto done;
3215 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3216 if ((f = fopen(file, "r")) == NULL)
3217 goto freeit;
3219 for (i = 0; i < cert_count; i++) {
3220 cert_buf_sz = sizeof cert_buf;
3221 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3222 cert_buf, &cert_buf_sz)) {
3223 goto freeit;
3225 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3226 rv = -1; /* critical */
3227 goto freeit;
3229 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3230 rv = -1; /* critical */
3231 goto freeit;
3235 rv = 0;
3236 freeit:
3237 if (f)
3238 fclose(f);
3239 free_connection_certs(certs, cert_count);
3240 done:
3241 /* we close the socket first for speed */
3242 if (s != -1)
3243 close(s);
3244 stop_tls(gsession, xcred);
3246 return (rv);
3250 cert_cmd(struct tab *t, struct karg *args)
3252 const gchar *uri;
3253 char domain[8182];
3254 int s = -1;
3255 size_t cert_count;
3256 gnutls_session_t gsession;
3257 gnutls_x509_crt_t *certs;
3258 gnutls_certificate_credentials_t xcred;
3260 if (t == NULL)
3261 return (1);
3263 if (ssl_ca_file == NULL) {
3264 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3265 return (1);
3268 if ((uri = get_uri(t)) == NULL) {
3269 show_oops(t, "Invalid URI");
3270 return (1);
3273 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3274 show_oops(t, "Invalid certificate URI: %s", uri);
3275 return (1);
3278 /* go ssl/tls */
3279 if (start_tls(t, s, &gsession, &xcred)) {
3280 show_oops(t, "Start TLS failed");
3281 goto done;
3284 /* get certs */
3285 if (get_connection_certs(gsession, &certs, &cert_count)) {
3286 show_oops(t, "get_connection_certs failed");
3287 goto done;
3290 if (args->i & XT_SHOW)
3291 show_certs(t, certs, cert_count, "Certificate Chain");
3292 else if (args->i & XT_SAVE)
3293 save_certs(t, certs, cert_count, domain);
3295 free_connection_certs(certs, cert_count);
3296 done:
3297 /* we close the socket first for speed */
3298 if (s != -1)
3299 close(s);
3300 stop_tls(gsession, xcred);
3302 return (0);
3306 remove_cookie(int index)
3308 int i, rv = 1;
3309 GSList *cf;
3310 SoupCookie *c;
3312 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3314 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3316 for (i = 1; cf; cf = cf->next, i++) {
3317 if (i != index)
3318 continue;
3319 c = cf->data;
3320 print_cookie("remove cookie", c);
3321 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3322 rv = 0;
3323 break;
3326 soup_cookies_free(cf);
3328 return (rv);
3332 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3334 struct domain *d;
3335 char *tmp, *body;
3337 body = g_strdup("");
3339 /* p list */
3340 if (args->i & XT_WL_PERSISTENT) {
3341 tmp = body;
3342 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3343 g_free(tmp);
3344 RB_FOREACH(d, domain_list, wl) {
3345 if (d->handy == 0)
3346 continue;
3347 tmp = body;
3348 body = g_strdup_printf("%s%s<br/>", body, d->d);
3349 g_free(tmp);
3353 /* s list */
3354 if (args->i & XT_WL_SESSION) {
3355 tmp = body;
3356 body = g_strdup_printf("%s<h2>Session</h2>", body);
3357 g_free(tmp);
3358 RB_FOREACH(d, domain_list, wl) {
3359 if (d->handy == 1)
3360 continue;
3361 tmp = body;
3362 body = g_strdup_printf("%s%s<br/>", body, d->d);
3363 g_free(tmp);
3367 tmp = get_html_page(title, body, "", 0);
3368 g_free(body);
3369 if (wl == &js_wl)
3370 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3371 else
3372 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3373 g_free(tmp);
3374 return (0);
3378 wl_save(struct tab *t, struct karg *args, int js)
3380 char file[PATH_MAX];
3381 FILE *f;
3382 char *line = NULL, *lt = NULL;
3383 size_t linelen;
3384 const gchar *uri;
3385 char *dom = NULL, *dom_save = NULL;
3386 struct karg a;
3387 struct domain *d;
3388 GSList *cf;
3389 SoupCookie *ci, *c;
3391 if (t == NULL || args == NULL)
3392 return (1);
3394 if (runtime_settings[0] == '\0')
3395 return (1);
3397 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3398 if ((f = fopen(file, "r+")) == NULL)
3399 return (1);
3401 uri = get_uri(t);
3402 dom = find_domain(uri, 1);
3403 if (uri == NULL || dom == NULL) {
3404 show_oops(t, "Can't add domain to %s white list",
3405 js ? "JavaScript" : "cookie");
3406 goto done;
3409 if (args->i & XT_WL_TOPLEVEL) {
3410 /* save domain */
3411 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3412 show_oops(t, "invalid domain: %s", dom);
3413 goto done;
3415 } else if (args->i & XT_WL_FQDN) {
3416 /* save fqdn */
3417 dom_save = dom;
3418 } else
3419 goto done;
3421 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3423 while (!feof(f)) {
3424 line = fparseln(f, &linelen, NULL, NULL, 0);
3425 if (line == NULL)
3426 continue;
3427 if (!strcmp(line, lt))
3428 goto done;
3429 free(line);
3430 line = NULL;
3433 fprintf(f, "%s\n", lt);
3435 a.i = XT_WL_ENABLE;
3436 a.i |= args->i;
3437 if (js) {
3438 d = wl_find(dom_save, &js_wl);
3439 if (!d) {
3440 settings_add("js_wl", dom_save);
3441 d = wl_find(dom_save, &js_wl);
3443 toggle_js(t, &a);
3444 } else {
3445 d = wl_find(dom_save, &c_wl);
3446 if (!d) {
3447 settings_add("cookie_wl", dom_save);
3448 d = wl_find(dom_save, &c_wl);
3450 toggle_cwl(t, &a);
3452 /* find and add to persistent jar */
3453 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3454 for (;cf; cf = cf->next) {
3455 ci = cf->data;
3456 if (!strcmp(dom_save, ci->domain) ||
3457 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3458 c = soup_cookie_copy(ci);
3459 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3462 soup_cookies_free(cf);
3464 if (d)
3465 d->handy = 1;
3467 done:
3468 if (line)
3469 free(line);
3470 if (dom)
3471 g_free(dom);
3472 if (lt)
3473 g_free(lt);
3474 fclose(f);
3476 return (0);
3480 js_show_wl(struct tab *t, struct karg *args)
3482 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3483 wl_show(t, args, "JavaScript White List", &js_wl);
3485 return (0);
3489 cookie_show_wl(struct tab *t, struct karg *args)
3491 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3492 wl_show(t, args, "Cookie White List", &c_wl);
3494 return (0);
3498 cookie_cmd(struct tab *t, struct karg *args)
3500 if (args->i & XT_SHOW)
3501 wl_show(t, args, "Cookie White List", &c_wl);
3502 else if (args->i & XT_WL_TOGGLE) {
3503 args->i |= XT_WL_RELOAD;
3504 toggle_cwl(t, args);
3505 } else if (args->i & XT_SAVE) {
3506 args->i |= XT_WL_RELOAD;
3507 wl_save(t, args, 0);
3508 } else if (args->i & XT_DELETE)
3509 show_oops(t, "'cookie delete' currently unimplemented");
3511 return (0);
3515 js_cmd(struct tab *t, struct karg *args)
3517 if (args->i & XT_SHOW)
3518 wl_show(t, args, "JavaScript White List", &js_wl);
3519 else if (args->i & XT_SAVE) {
3520 args->i |= XT_WL_RELOAD;
3521 wl_save(t, args, 1);
3522 } else if (args->i & XT_WL_TOGGLE) {
3523 args->i |= XT_WL_RELOAD;
3524 toggle_js(t, args);
3525 } else if (args->i & XT_DELETE)
3526 show_oops(t, "'js delete' currently unimplemented");
3528 return (0);
3532 toplevel_cmd(struct tab *t, struct karg *args)
3534 js_toggle_cb(t->js_toggle, t);
3536 return (0);
3540 add_favorite(struct tab *t, struct karg *args)
3542 char file[PATH_MAX];
3543 FILE *f;
3544 char *line = NULL;
3545 size_t urilen, linelen;
3546 const gchar *uri, *title;
3548 if (t == NULL)
3549 return (1);
3551 /* don't allow adding of xtp pages to favorites */
3552 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3553 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3554 return (1);
3557 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3558 if ((f = fopen(file, "r+")) == NULL) {
3559 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3560 return (1);
3563 title = webkit_web_view_get_title(t->wv);
3564 uri = get_uri(t);
3566 if (title == NULL)
3567 title = uri;
3569 if (title == NULL || uri == NULL) {
3570 show_oops(t, "can't add page to favorites");
3571 goto done;
3574 urilen = strlen(uri);
3576 for (;;) {
3577 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3578 if (feof(f) || ferror(f))
3579 break;
3581 if (linelen == urilen && !strcmp(line, uri))
3582 goto done;
3584 free(line);
3585 line = NULL;
3588 fprintf(f, "\n%s\n%s", title, uri);
3589 done:
3590 if (line)
3591 free(line);
3592 fclose(f);
3594 update_favorite_tabs(NULL);
3596 return (0);
3600 navaction(struct tab *t, struct karg *args)
3602 WebKitWebHistoryItem *item;
3604 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3605 t->tab_id, args->i);
3607 if (t->item) {
3608 if (args->i == XT_NAV_BACK)
3609 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3610 else
3611 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3612 if (item == NULL)
3613 return (XT_CB_PASSTHROUGH);
3614 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3615 t->item = NULL;
3616 return (XT_CB_PASSTHROUGH);
3619 switch (args->i) {
3620 case XT_NAV_BACK:
3621 webkit_web_view_go_back(t->wv);
3622 break;
3623 case XT_NAV_FORWARD:
3624 webkit_web_view_go_forward(t->wv);
3625 break;
3626 case XT_NAV_RELOAD:
3627 webkit_web_view_reload(t->wv);
3628 break;
3629 case XT_NAV_RELOAD_CACHE:
3630 webkit_web_view_reload_bypass_cache(t->wv);
3631 break;
3633 return (XT_CB_PASSTHROUGH);
3637 move(struct tab *t, struct karg *args)
3639 GtkAdjustment *adjust;
3640 double pi, si, pos, ps, upper, lower, max;
3642 switch (args->i) {
3643 case XT_MOVE_DOWN:
3644 case XT_MOVE_UP:
3645 case XT_MOVE_BOTTOM:
3646 case XT_MOVE_TOP:
3647 case XT_MOVE_PAGEDOWN:
3648 case XT_MOVE_PAGEUP:
3649 case XT_MOVE_HALFDOWN:
3650 case XT_MOVE_HALFUP:
3651 adjust = t->adjust_v;
3652 break;
3653 default:
3654 adjust = t->adjust_h;
3655 break;
3658 pos = gtk_adjustment_get_value(adjust);
3659 ps = gtk_adjustment_get_page_size(adjust);
3660 upper = gtk_adjustment_get_upper(adjust);
3661 lower = gtk_adjustment_get_lower(adjust);
3662 si = gtk_adjustment_get_step_increment(adjust);
3663 pi = gtk_adjustment_get_page_increment(adjust);
3664 max = upper - ps;
3666 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3667 "max %f si %f pi %f\n",
3668 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3669 pos, ps, upper, lower, max, si, pi);
3671 switch (args->i) {
3672 case XT_MOVE_DOWN:
3673 case XT_MOVE_RIGHT:
3674 pos += si;
3675 gtk_adjustment_set_value(adjust, MIN(pos, max));
3676 break;
3677 case XT_MOVE_UP:
3678 case XT_MOVE_LEFT:
3679 pos -= si;
3680 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3681 break;
3682 case XT_MOVE_BOTTOM:
3683 case XT_MOVE_FARRIGHT:
3684 gtk_adjustment_set_value(adjust, max);
3685 break;
3686 case XT_MOVE_TOP:
3687 case XT_MOVE_FARLEFT:
3688 gtk_adjustment_set_value(adjust, lower);
3689 break;
3690 case XT_MOVE_PAGEDOWN:
3691 pos += pi;
3692 gtk_adjustment_set_value(adjust, MIN(pos, max));
3693 break;
3694 case XT_MOVE_PAGEUP:
3695 pos -= pi;
3696 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3697 break;
3698 case XT_MOVE_HALFDOWN:
3699 pos += pi / 2;
3700 gtk_adjustment_set_value(adjust, MIN(pos, max));
3701 break;
3702 case XT_MOVE_HALFUP:
3703 pos -= pi / 2;
3704 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3705 break;
3706 default:
3707 return (XT_CB_PASSTHROUGH);
3710 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3712 return (XT_CB_HANDLED);
3715 void
3716 url_set_visibility(void)
3718 struct tab *t;
3720 TAILQ_FOREACH(t, &tabs, entry) {
3721 if (show_url == 0) {
3722 gtk_widget_hide(t->toolbar);
3723 focus_webview(t);
3724 } else
3725 gtk_widget_show(t->toolbar);
3729 void
3730 notebook_tab_set_visibility()
3732 if (show_tabs == 0) {
3733 gtk_widget_hide(tab_bar);
3734 gtk_notebook_set_show_tabs(notebook, FALSE);
3735 } else {
3736 if (tab_style == XT_TABS_NORMAL) {
3737 gtk_widget_hide(tab_bar);
3738 gtk_notebook_set_show_tabs(notebook, TRUE);
3739 } else if (tab_style == XT_TABS_COMPACT) {
3740 gtk_widget_show(tab_bar);
3741 gtk_notebook_set_show_tabs(notebook, FALSE);
3746 void
3747 statusbar_set_visibility(void)
3749 struct tab *t;
3751 TAILQ_FOREACH(t, &tabs, entry) {
3752 if (show_statusbar == 0) {
3753 gtk_widget_hide(t->statusbar);
3754 focus_webview(t);
3755 } else
3756 gtk_widget_show(t->statusbar);
3760 void
3761 url_set(struct tab *t, int enable_url_entry)
3763 GdkPixbuf *pixbuf;
3764 int progress;
3766 show_url = enable_url_entry;
3768 if (enable_url_entry) {
3769 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3770 GTK_ENTRY_ICON_PRIMARY, NULL);
3771 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3772 } else {
3773 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3774 GTK_ENTRY_ICON_PRIMARY);
3775 progress =
3776 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3777 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3778 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3779 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3780 progress);
3785 fullscreen(struct tab *t, struct karg *args)
3787 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3789 if (t == NULL)
3790 return (XT_CB_PASSTHROUGH);
3792 if (show_url == 0) {
3793 url_set(t, 1);
3794 show_tabs = 1;
3795 } else {
3796 url_set(t, 0);
3797 show_tabs = 0;
3800 url_set_visibility();
3801 notebook_tab_set_visibility();
3803 return (XT_CB_HANDLED);
3807 statusaction(struct tab *t, struct karg *args)
3809 int rv = XT_CB_HANDLED;
3811 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3813 if (t == NULL)
3814 return (XT_CB_PASSTHROUGH);
3816 switch (args->i) {
3817 case XT_STATUSBAR_SHOW:
3818 if (show_statusbar == 0) {
3819 show_statusbar = 1;
3820 statusbar_set_visibility();
3822 break;
3823 case XT_STATUSBAR_HIDE:
3824 if (show_statusbar == 1) {
3825 show_statusbar = 0;
3826 statusbar_set_visibility();
3828 break;
3830 return (rv);
3834 urlaction(struct tab *t, struct karg *args)
3836 int rv = XT_CB_HANDLED;
3838 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3840 if (t == NULL)
3841 return (XT_CB_PASSTHROUGH);
3843 switch (args->i) {
3844 case XT_URL_SHOW:
3845 if (show_url == 0) {
3846 url_set(t, 1);
3847 url_set_visibility();
3849 break;
3850 case XT_URL_HIDE:
3851 if (show_url == 1) {
3852 url_set(t, 0);
3853 url_set_visibility();
3855 break;
3857 return (rv);
3861 tabaction(struct tab *t, struct karg *args)
3863 int rv = XT_CB_HANDLED;
3864 char *url = args->s;
3865 struct undo *u;
3866 struct tab *tt;
3868 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3870 if (t == NULL)
3871 return (XT_CB_PASSTHROUGH);
3873 switch (args->i) {
3874 case XT_TAB_NEW:
3875 if (strlen(url) > 0)
3876 create_new_tab(url, NULL, 1, args->p);
3877 else
3878 create_new_tab(NULL, NULL, 1, args->p);
3879 break;
3880 case XT_TAB_DELETE:
3881 if (args->p < 0)
3882 delete_tab(t);
3883 else
3884 TAILQ_FOREACH(tt, &tabs, entry)
3885 if (tt->tab_id == args->p - 1) {
3886 delete_tab(tt);
3887 recalc_tabs();
3888 break;
3890 break;
3891 case XT_TAB_DELQUIT:
3892 if (gtk_notebook_get_n_pages(notebook) > 1)
3893 delete_tab(t);
3894 else
3895 quit(t, args);
3896 break;
3897 case XT_TAB_OPEN:
3898 if (strlen(url) > 0)
3900 else {
3901 rv = XT_CB_PASSTHROUGH;
3902 goto done;
3904 load_uri(t, url);
3905 break;
3906 case XT_TAB_SHOW:
3907 if (show_tabs == 0) {
3908 show_tabs = 1;
3909 notebook_tab_set_visibility();
3911 break;
3912 case XT_TAB_HIDE:
3913 if (show_tabs == 1) {
3914 show_tabs = 0;
3915 notebook_tab_set_visibility();
3917 break;
3918 case XT_TAB_NEXTSTYLE:
3919 if (tab_style == XT_TABS_NORMAL)
3920 tab_style = XT_TABS_COMPACT;
3921 else
3922 tab_style = XT_TABS_NORMAL;
3923 notebook_tab_set_visibility();
3924 break;
3925 case XT_TAB_UNDO_CLOSE:
3926 if (undo_count == 0) {
3927 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3928 goto done;
3929 } else {
3930 undo_count--;
3931 u = TAILQ_FIRST(&undos);
3932 create_new_tab(u->uri, u, 1, -1);
3934 TAILQ_REMOVE(&undos, u, entry);
3935 g_free(u->uri);
3936 /* u->history is freed in create_new_tab() */
3937 g_free(u);
3939 break;
3940 default:
3941 rv = XT_CB_PASSTHROUGH;
3942 goto done;
3945 done:
3946 if (args->s) {
3947 g_free(args->s);
3948 args->s = NULL;
3951 return (rv);
3955 resizetab(struct tab *t, struct karg *args)
3957 if (t == NULL || args == NULL) {
3958 show_oops(NULL, "resizetab invalid parameters");
3959 return (XT_CB_PASSTHROUGH);
3962 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3963 t->tab_id, args->i);
3965 adjustfont_webkit(t, args->i);
3967 return (XT_CB_HANDLED);
3971 movetab(struct tab *t, struct karg *args)
3973 int n, dest;
3975 if (t == NULL || args == NULL) {
3976 show_oops(NULL, "movetab invalid parameters");
3977 return (XT_CB_PASSTHROUGH);
3980 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3981 t->tab_id, args->i);
3983 if (args->i >= XT_TAB_INVALID)
3984 return (XT_CB_PASSTHROUGH);
3986 if (TAILQ_EMPTY(&tabs))
3987 return (XT_CB_PASSTHROUGH);
3989 n = gtk_notebook_get_n_pages(notebook);
3990 dest = gtk_notebook_get_current_page(notebook);
3992 switch (args->i) {
3993 case XT_TAB_NEXT:
3994 if (args->p < 0)
3995 dest = dest == n - 1 ? 0 : dest + 1;
3996 else
3997 dest = args->p - 1;
3999 break;
4000 case XT_TAB_PREV:
4001 if (args->p < 0)
4002 dest -= 1;
4003 else
4004 dest -= args->p % n;
4006 if (dest < 0)
4007 dest += n;
4009 break;
4010 case XT_TAB_FIRST:
4011 dest = 0;
4012 break;
4013 case XT_TAB_LAST:
4014 dest = n - 1;
4015 break;
4016 default:
4017 return (XT_CB_PASSTHROUGH);
4020 if (dest < 0 || dest >= n)
4021 return (XT_CB_PASSTHROUGH);
4022 if (t->tab_id == dest) {
4023 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4024 return (XT_CB_HANDLED);
4027 set_current_tab(dest);
4029 return (XT_CB_HANDLED);
4032 int cmd_prefix = 0;
4035 command(struct tab *t, struct karg *args)
4037 char *s = NULL, *ss = NULL;
4038 GdkColor color;
4039 const gchar *uri;
4041 if (t == NULL || args == NULL) {
4042 show_oops(NULL, "command invalid parameters");
4043 return (XT_CB_PASSTHROUGH);
4046 switch (args->i) {
4047 case '/':
4048 s = "/";
4049 break;
4050 case '?':
4051 s = "?";
4052 break;
4053 case ':':
4054 if (cmd_prefix == 0)
4055 s = ":";
4056 else {
4057 ss = g_strdup_printf(":%d", cmd_prefix);
4058 s = ss;
4059 cmd_prefix = 0;
4061 break;
4062 case XT_CMD_OPEN:
4063 s = ":open ";
4064 break;
4065 case XT_CMD_TABNEW:
4066 s = ":tabnew ";
4067 break;
4068 case XT_CMD_OPEN_CURRENT:
4069 s = ":open ";
4070 /* FALL THROUGH */
4071 case XT_CMD_TABNEW_CURRENT:
4072 if (!s) /* FALL THROUGH? */
4073 s = ":tabnew ";
4074 if ((uri = get_uri(t)) != NULL) {
4075 ss = g_strdup_printf("%s%s", s, uri);
4076 s = ss;
4078 break;
4079 default:
4080 show_oops(t, "command: invalid opcode %d", args->i);
4081 return (XT_CB_PASSTHROUGH);
4084 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4086 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4087 gdk_color_parse(XT_COLOR_WHITE, &color);
4088 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4089 show_cmd(t);
4090 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4091 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4093 if (ss)
4094 g_free(ss);
4096 return (XT_CB_HANDLED);
4100 * Return a new string with a download row (in html)
4101 * appended. Old string is freed.
4103 char *
4104 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4107 WebKitDownloadStatus stat;
4108 char *status_html = NULL, *cmd_html = NULL, *new_html;
4109 gdouble progress;
4110 char cur_sz[FMT_SCALED_STRSIZE];
4111 char tot_sz[FMT_SCALED_STRSIZE];
4112 char *xtp_prefix;
4114 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4116 /* All actions wil take this form:
4117 * xxxt://class/seskey
4119 xtp_prefix = g_strdup_printf("%s%d/%s/",
4120 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4122 stat = webkit_download_get_status(dl->download);
4124 switch (stat) {
4125 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4126 status_html = g_strdup_printf("Finished");
4127 cmd_html = g_strdup_printf(
4128 "<a href='%s%d/%d'>Remove</a>",
4129 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4130 break;
4131 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4132 /* gather size info */
4133 progress = 100 * webkit_download_get_progress(dl->download);
4135 fmt_scaled(
4136 webkit_download_get_current_size(dl->download), cur_sz);
4137 fmt_scaled(
4138 webkit_download_get_total_size(dl->download), tot_sz);
4140 status_html = g_strdup_printf(
4141 "<div style='width: 100%%' align='center'>"
4142 "<div class='progress-outer'>"
4143 "<div class='progress-inner' style='width: %.2f%%'>"
4144 "</div></div></div>"
4145 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4146 progress, cur_sz, tot_sz, progress);
4148 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4149 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4151 break;
4152 /* LLL */
4153 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4154 status_html = g_strdup_printf("Cancelled");
4155 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4156 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4157 break;
4158 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4159 status_html = g_strdup_printf("Error!");
4160 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4161 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4162 break;
4163 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4164 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4165 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4166 status_html = g_strdup_printf("Starting");
4167 break;
4168 default:
4169 show_oops(t, "%s: unknown download status", __func__);
4172 new_html = g_strdup_printf(
4173 "%s\n<tr><td>%s</td><td>%s</td>"
4174 "<td style='text-align:center'>%s</td></tr>\n",
4175 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4176 status_html, cmd_html);
4177 g_free(html);
4179 if (status_html)
4180 g_free(status_html);
4182 if (cmd_html)
4183 g_free(cmd_html);
4185 g_free(xtp_prefix);
4187 return new_html;
4191 * update all download tabs apart from one. Pass NULL if
4192 * you want to update all.
4194 void
4195 update_download_tabs(struct tab *apart_from)
4197 struct tab *t;
4198 if (!updating_dl_tabs) {
4199 updating_dl_tabs = 1; /* stop infinite recursion */
4200 TAILQ_FOREACH(t, &tabs, entry)
4201 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4202 && (t != apart_from))
4203 xtp_page_dl(t, NULL);
4204 updating_dl_tabs = 0;
4209 * update all cookie tabs apart from one. Pass NULL if
4210 * you want to update all.
4212 void
4213 update_cookie_tabs(struct tab *apart_from)
4215 struct tab *t;
4216 if (!updating_cl_tabs) {
4217 updating_cl_tabs = 1; /* stop infinite recursion */
4218 TAILQ_FOREACH(t, &tabs, entry)
4219 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4220 && (t != apart_from))
4221 xtp_page_cl(t, NULL);
4222 updating_cl_tabs = 0;
4227 * update all history tabs apart from one. Pass NULL if
4228 * you want to update all.
4230 void
4231 update_history_tabs(struct tab *apart_from)
4233 struct tab *t;
4235 if (!updating_hl_tabs) {
4236 updating_hl_tabs = 1; /* stop infinite recursion */
4237 TAILQ_FOREACH(t, &tabs, entry)
4238 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4239 && (t != apart_from))
4240 xtp_page_hl(t, NULL);
4241 updating_hl_tabs = 0;
4245 /* cookie management XTP page */
4247 xtp_page_cl(struct tab *t, struct karg *args)
4249 char *body, *page, *tmp;
4250 int i = 1; /* all ids start 1 */
4251 GSList *sc, *pc, *pc_start;
4252 SoupCookie *c;
4253 char *type, *table_headers, *last_domain;
4255 DNPRINTF(XT_D_CMD, "%s", __func__);
4257 if (t == NULL) {
4258 show_oops(NULL, "%s invalid parameters", __func__);
4259 return (1);
4262 /* Generate a new session key */
4263 if (!updating_cl_tabs)
4264 generate_xtp_session_key(&cl_session_key);
4266 /* table headers */
4267 table_headers = g_strdup_printf("<table><tr>"
4268 "<th>Type</th>"
4269 "<th>Name</th>"
4270 "<th style='width:200px'>Value</th>"
4271 "<th>Path</th>"
4272 "<th>Expires</th>"
4273 "<th>Secure</th>"
4274 "<th>HTTP<br />only</th>"
4275 "<th style='width:40px'>Rm</th></tr>\n");
4277 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4278 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4279 pc_start = pc;
4281 body = NULL;
4282 last_domain = strdup("");
4283 for (; sc; sc = sc->next) {
4284 c = sc->data;
4286 if (strcmp(last_domain, c->domain) != 0) {
4287 /* new domain */
4288 free(last_domain);
4289 last_domain = strdup(c->domain);
4291 if (body != NULL) {
4292 tmp = body;
4293 body = g_strdup_printf("%s</table>"
4294 "<h2>%s</h2>%s\n",
4295 body, c->domain, table_headers);
4296 g_free(tmp);
4297 } else {
4298 /* first domain */
4299 body = g_strdup_printf("<h2>%s</h2>%s\n",
4300 c->domain, table_headers);
4304 type = "Session";
4305 for (pc = pc_start; pc; pc = pc->next)
4306 if (soup_cookie_equal(pc->data, c)) {
4307 type = "Session + Persistent";
4308 break;
4311 tmp = body;
4312 body = g_strdup_printf(
4313 "%s\n<tr>"
4314 "<td>%s</td>"
4315 "<td style='word-wrap:normal'>%s</td>"
4316 "<td>"
4317 " <textarea rows='4'>%s</textarea>"
4318 "</td>"
4319 "<td>%s</td>"
4320 "<td>%s</td>"
4321 "<td>%d</td>"
4322 "<td>%d</td>"
4323 "<td style='text-align:center'>"
4324 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4325 body,
4326 type,
4327 c->name,
4328 c->value,
4329 c->path,
4330 c->expires ?
4331 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4332 c->secure,
4333 c->http_only,
4335 XT_XTP_STR,
4336 XT_XTP_CL,
4337 cl_session_key,
4338 XT_XTP_CL_REMOVE,
4342 g_free(tmp);
4343 i++;
4346 soup_cookies_free(sc);
4347 soup_cookies_free(pc);
4349 /* small message if there are none */
4350 if (i == 1) {
4351 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4352 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4354 tmp = body;
4355 body = g_strdup_printf("%s</table>", body);
4356 g_free(tmp);
4358 page = get_html_page("Cookie Jar", body, "", TRUE);
4359 g_free(body);
4360 g_free(table_headers);
4361 g_free(last_domain);
4363 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4364 update_cookie_tabs(t);
4366 g_free(page);
4368 return (0);
4372 xtp_page_hl(struct tab *t, struct karg *args)
4374 char *body, *page, *tmp;
4375 struct history *h;
4376 int i = 1; /* all ids start 1 */
4378 DNPRINTF(XT_D_CMD, "%s", __func__);
4380 if (t == NULL) {
4381 show_oops(NULL, "%s invalid parameters", __func__);
4382 return (1);
4385 /* Generate a new session key */
4386 if (!updating_hl_tabs)
4387 generate_xtp_session_key(&hl_session_key);
4389 /* body */
4390 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4391 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4393 RB_FOREACH_REVERSE(h, history_list, &hl) {
4394 tmp = body;
4395 body = g_strdup_printf(
4396 "%s\n<tr>"
4397 "<td><a href='%s'>%s</a></td>"
4398 "<td>%s</td>"
4399 "<td style='text-align: center'>"
4400 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4401 body, h->uri, h->uri, h->title,
4402 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4403 XT_XTP_HL_REMOVE, i);
4405 g_free(tmp);
4406 i++;
4409 /* small message if there are none */
4410 if (i == 1) {
4411 tmp = body;
4412 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4413 "colspan='3'>No History</td></tr>\n", body);
4414 g_free(tmp);
4417 tmp = body;
4418 body = g_strdup_printf("%s</table>", body);
4419 g_free(tmp);
4421 page = get_html_page("History", body, "", TRUE);
4422 g_free(body);
4425 * update all history manager tabs as the xtp session
4426 * key has now changed. No need to update the current tab.
4427 * Already did that above.
4429 update_history_tabs(t);
4431 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4432 g_free(page);
4434 return (0);
4438 * Generate a web page detailing the status of any downloads
4441 xtp_page_dl(struct tab *t, struct karg *args)
4443 struct download *dl;
4444 char *body, *page, *tmp;
4445 char *ref;
4446 int n_dl = 1;
4448 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4450 if (t == NULL) {
4451 show_oops(NULL, "%s invalid parameters", __func__);
4452 return (1);
4456 * Generate a new session key for next page instance.
4457 * This only happens for the top level call to xtp_page_dl()
4458 * in which case updating_dl_tabs is 0.
4460 if (!updating_dl_tabs)
4461 generate_xtp_session_key(&dl_session_key);
4463 /* header - with refresh so as to update */
4464 if (refresh_interval >= 1)
4465 ref = g_strdup_printf(
4466 "<meta http-equiv='refresh' content='%u"
4467 ";url=%s%d/%s/%d' />\n",
4468 refresh_interval,
4469 XT_XTP_STR,
4470 XT_XTP_DL,
4471 dl_session_key,
4472 XT_XTP_DL_LIST);
4473 else
4474 ref = g_strdup("");
4476 body = g_strdup_printf("<div align='center'>"
4477 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4478 "</p><table><tr><th style='width: 60%%'>"
4479 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4480 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4482 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4483 body = xtp_page_dl_row(t, body, dl);
4484 n_dl++;
4487 /* message if no downloads in list */
4488 if (n_dl == 1) {
4489 tmp = body;
4490 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4491 " style='text-align: center'>"
4492 "No downloads</td></tr>\n", body);
4493 g_free(tmp);
4496 tmp = body;
4497 body = g_strdup_printf("%s</table></div>", body);
4498 g_free(tmp);
4500 page = get_html_page("Downloads", body, ref, 1);
4501 g_free(ref);
4502 g_free(body);
4505 * update all download manager tabs as the xtp session
4506 * key has now changed. No need to update the current tab.
4507 * Already did that above.
4509 update_download_tabs(t);
4511 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4512 g_free(page);
4514 return (0);
4518 search(struct tab *t, struct karg *args)
4520 gboolean d;
4522 if (t == NULL || args == NULL) {
4523 show_oops(NULL, "search invalid parameters");
4524 return (1);
4526 if (t->search_text == NULL) {
4527 if (global_search == NULL)
4528 return (XT_CB_PASSTHROUGH);
4529 else {
4530 t->search_text = g_strdup(global_search);
4531 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4532 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4536 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4537 t->tab_id, args->i, t->search_forward, t->search_text);
4539 switch (args->i) {
4540 case XT_SEARCH_NEXT:
4541 d = t->search_forward;
4542 break;
4543 case XT_SEARCH_PREV:
4544 d = !t->search_forward;
4545 break;
4546 default:
4547 return (XT_CB_PASSTHROUGH);
4550 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4552 return (XT_CB_HANDLED);
4555 struct settings_args {
4556 char **body;
4557 int i;
4560 void
4561 print_setting(struct settings *s, char *val, void *cb_args)
4563 char *tmp, *color;
4564 struct settings_args *sa = cb_args;
4566 if (sa == NULL)
4567 return;
4569 if (s->flags & XT_SF_RUNTIME)
4570 color = "#22cc22";
4571 else
4572 color = "#cccccc";
4574 tmp = *sa->body;
4575 *sa->body = g_strdup_printf(
4576 "%s\n<tr>"
4577 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4578 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4579 *sa->body,
4580 color,
4581 s->name,
4582 color,
4585 g_free(tmp);
4586 sa->i++;
4590 set(struct tab *t, struct karg *args)
4592 char *body, *page, *tmp;
4593 int i = 1;
4594 struct settings_args sa;
4596 bzero(&sa, sizeof sa);
4597 sa.body = &body;
4599 /* body */
4600 body = g_strdup_printf("<div align='center'><table><tr>"
4601 "<th align='left'>Setting</th>"
4602 "<th align='left'>Value</th></tr>\n");
4604 settings_walk(print_setting, &sa);
4605 i = sa.i;
4607 /* small message if there are none */
4608 if (i == 1) {
4609 tmp = body;
4610 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4611 "colspan='2'>No settings</td></tr>\n", body);
4612 g_free(tmp);
4615 tmp = body;
4616 body = g_strdup_printf("%s</table></div>", body);
4617 g_free(tmp);
4619 page = get_html_page("Settings", body, "", 0);
4621 g_free(body);
4623 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4625 g_free(page);
4627 return (XT_CB_PASSTHROUGH);
4631 session_save(struct tab *t, char *filename)
4633 struct karg a;
4634 int rv = 1;
4636 if (strlen(filename) == 0)
4637 goto done;
4639 if (filename[0] == '.' || filename[0] == '/')
4640 goto done;
4642 a.s = filename;
4643 if (save_tabs(t, &a))
4644 goto done;
4645 strlcpy(named_session, filename, sizeof named_session);
4647 rv = 0;
4648 done:
4649 return (rv);
4653 session_open(struct tab *t, char *filename)
4655 struct karg a;
4656 int rv = 1;
4658 if (strlen(filename) == 0)
4659 goto done;
4661 if (filename[0] == '.' || filename[0] == '/')
4662 goto done;
4664 a.s = filename;
4665 a.i = XT_SES_CLOSETABS;
4666 if (open_tabs(t, &a))
4667 goto done;
4669 strlcpy(named_session, filename, sizeof named_session);
4671 rv = 0;
4672 done:
4673 return (rv);
4677 session_delete(struct tab *t, char *filename)
4679 char file[PATH_MAX];
4680 int rv = 1;
4682 if (strlen(filename) == 0)
4683 goto done;
4685 if (filename[0] == '.' || filename[0] == '/')
4686 goto done;
4688 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4689 if (unlink(file))
4690 goto done;
4692 if (!strcmp(filename, named_session))
4693 strlcpy(named_session, XT_SAVED_TABS_FILE,
4694 sizeof named_session);
4696 rv = 0;
4697 done:
4698 return (rv);
4702 session_cmd(struct tab *t, struct karg *args)
4704 char *filename = args->s;
4706 if (t == NULL)
4707 return (1);
4709 if (args->i & XT_SHOW)
4710 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4711 XT_SAVED_TABS_FILE : named_session);
4712 else if (args->i & XT_SAVE) {
4713 if (session_save(t, filename)) {
4714 show_oops(t, "Can't save session: %s",
4715 filename ? filename : "INVALID");
4716 goto done;
4718 } else if (args->i & XT_OPEN) {
4719 if (session_open(t, filename)) {
4720 show_oops(t, "Can't open session: %s",
4721 filename ? filename : "INVALID");
4722 goto done;
4724 } else if (args->i & XT_DELETE) {
4725 if (session_delete(t, filename)) {
4726 show_oops(t, "Can't delete session: %s",
4727 filename ? filename : "INVALID");
4728 goto done;
4731 done:
4732 return (XT_CB_PASSTHROUGH);
4736 * Make a hardcopy of the page
4739 print_page(struct tab *t, struct karg *args)
4741 WebKitWebFrame *frame;
4742 GtkPageSetup *ps;
4743 GtkPrintOperation *op;
4744 GtkPrintOperationAction action;
4745 GtkPrintOperationResult print_res;
4746 GError *g_err = NULL;
4747 int marg_l, marg_r, marg_t, marg_b;
4749 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4751 ps = gtk_page_setup_new();
4752 op = gtk_print_operation_new();
4753 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4754 frame = webkit_web_view_get_main_frame(t->wv);
4756 /* the default margins are too small, so we will bump them */
4757 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4758 XT_PRINT_EXTRA_MARGIN;
4759 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4760 XT_PRINT_EXTRA_MARGIN;
4761 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4762 XT_PRINT_EXTRA_MARGIN;
4763 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4764 XT_PRINT_EXTRA_MARGIN;
4766 /* set margins */
4767 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4768 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4769 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4770 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4772 gtk_print_operation_set_default_page_setup(op, ps);
4774 /* this appears to free 'op' and 'ps' */
4775 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4777 /* check it worked */
4778 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4779 show_oops(NULL, "can't print: %s", g_err->message);
4780 g_error_free (g_err);
4781 return (1);
4784 return (0);
4788 go_home(struct tab *t, struct karg *args)
4790 load_uri(t, home);
4791 return (0);
4795 restart(struct tab *t, struct karg *args)
4797 struct karg a;
4799 a.s = XT_RESTART_TABS_FILE;
4800 save_tabs(t, &a);
4801 execvp(start_argv[0], start_argv);
4802 /* NOTREACHED */
4804 return (0);
4807 #define CTRL GDK_CONTROL_MASK
4808 #define MOD1 GDK_MOD1_MASK
4809 #define SHFT GDK_SHIFT_MASK
4811 /* inherent to GTK not all keys will be caught at all times */
4812 /* XXX sort key bindings */
4813 struct key_binding {
4814 char *cmd;
4815 guint mask;
4816 guint use_in_entry;
4817 guint key;
4818 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4819 } keys[] = {
4820 { "cookiejar", MOD1, 0, GDK_j },
4821 { "downloadmgr", MOD1, 0, GDK_d },
4822 { "history", MOD1, 0, GDK_h },
4823 { "print", CTRL, 0, GDK_p },
4824 { "search", 0, 0, GDK_slash },
4825 { "searchb", 0, 0, GDK_question },
4826 { "command", 0, 0, GDK_colon },
4827 { "qa", CTRL, 0, GDK_q },
4828 { "restart", MOD1, 0, GDK_q },
4829 { "js toggle", CTRL, 0, GDK_j },
4830 { "cookie toggle", MOD1, 0, GDK_c },
4831 { "togglesrc", CTRL, 0, GDK_s },
4832 { "yankuri", 0, 0, GDK_y },
4833 { "pasteuricur", 0, 0, GDK_p },
4834 { "pasteurinew", 0, 0, GDK_P },
4835 { "toplevel toggle", 0, 0, GDK_F4 },
4836 { "help", 0, 0, GDK_F1 },
4838 /* search */
4839 { "searchnext", 0, 0, GDK_n },
4840 { "searchprevious", 0, 0, GDK_N },
4842 /* focus */
4843 { "focusaddress", 0, 0, GDK_F6 },
4844 { "focussearch", 0, 0, GDK_F7 },
4846 /* hinting */
4847 { "hinting", 0, 0, GDK_f },
4849 /* custom stylesheet */
4850 { "userstyle", 0, 0, GDK_i },
4852 /* navigation */
4853 { "goback", 0, 0, GDK_BackSpace },
4854 { "goback", MOD1, 0, GDK_Left },
4855 { "goforward", SHFT, 0, GDK_BackSpace },
4856 { "goforward", MOD1, 0, GDK_Right },
4857 { "reload", 0, 0, GDK_F5 },
4858 { "reload", CTRL, 0, GDK_r },
4859 { "reloadforce", CTRL, 0, GDK_R },
4860 { "reload", CTRL, 0, GDK_l },
4861 { "favorites", MOD1, 1, GDK_f },
4863 /* vertical movement */
4864 { "scrolldown", 0, 0, GDK_j },
4865 { "scrolldown", 0, 0, GDK_Down },
4866 { "scrollup", 0, 0, GDK_Up },
4867 { "scrollup", 0, 0, GDK_k },
4868 { "scrollbottom", 0, 0, GDK_G },
4869 { "scrollbottom", 0, 0, GDK_End },
4870 { "scrolltop", 0, 0, GDK_Home },
4871 { "scrolltop", 0, 0, GDK_g },
4872 { "scrollpagedown", 0, 0, GDK_space },
4873 { "scrollpagedown", CTRL, 0, GDK_f },
4874 { "scrollhalfdown", CTRL, 0, GDK_d },
4875 { "scrollpagedown", 0, 0, GDK_Page_Down },
4876 { "scrollpageup", 0, 0, GDK_Page_Up },
4877 { "scrollpageup", CTRL, 0, GDK_b },
4878 { "scrollhalfup", CTRL, 0, GDK_u },
4879 /* horizontal movement */
4880 { "scrollright", 0, 0, GDK_l },
4881 { "scrollright", 0, 0, GDK_Right },
4882 { "scrollleft", 0, 0, GDK_Left },
4883 { "scrollleft", 0, 0, GDK_h },
4884 { "scrollfarright", 0, 0, GDK_dollar },
4885 { "scrollfarleft", 0, 0, GDK_0 },
4887 /* tabs */
4888 { "tabnew", CTRL, 0, GDK_t },
4889 { "999tabnew", CTRL, 0, GDK_T },
4890 { "tabclose", CTRL, 1, GDK_w },
4891 { "tabundoclose", 0, 0, GDK_U },
4892 { "tabnext 1", CTRL, 0, GDK_1 },
4893 { "tabnext 2", CTRL, 0, GDK_2 },
4894 { "tabnext 3", CTRL, 0, GDK_3 },
4895 { "tabnext 4", CTRL, 0, GDK_4 },
4896 { "tabnext 5", CTRL, 0, GDK_5 },
4897 { "tabnext 6", CTRL, 0, GDK_6 },
4898 { "tabnext 7", CTRL, 0, GDK_7 },
4899 { "tabnext 8", CTRL, 0, GDK_8 },
4900 { "tabnext 9", CTRL, 0, GDK_9 },
4901 { "tabnext 10", CTRL, 0, GDK_0 },
4902 { "tabfirst", CTRL, 0, GDK_less },
4903 { "tablast", CTRL, 0, GDK_greater },
4904 { "tabprevious", CTRL, 0, GDK_Left },
4905 { "tabnext", CTRL, 0, GDK_Right },
4906 { "focusout", CTRL, 0, GDK_minus },
4907 { "focusin", CTRL, 0, GDK_plus },
4908 { "focusin", CTRL, 0, GDK_equal },
4910 /* command aliases (handy when -S flag is used) */
4911 { "promptopen", 0, 0, GDK_F9 },
4912 { "promptopencurrent", 0, 0, GDK_F10 },
4913 { "prompttabnew", 0, 0, GDK_F11 },
4914 { "prompttabnewcurrent",0, 0, GDK_F12 },
4916 TAILQ_HEAD(keybinding_list, key_binding);
4918 void
4919 walk_kb(struct settings *s,
4920 void (*cb)(struct settings *, char *, void *), void *cb_args)
4922 struct key_binding *k;
4923 char str[1024];
4925 if (s == NULL || cb == NULL) {
4926 show_oops(NULL, "walk_kb invalid parameters");
4927 return;
4930 TAILQ_FOREACH(k, &kbl, entry) {
4931 if (k->cmd == NULL)
4932 continue;
4933 str[0] = '\0';
4935 /* sanity */
4936 if (gdk_keyval_name(k->key) == NULL)
4937 continue;
4939 strlcat(str, k->cmd, sizeof str);
4940 strlcat(str, ",", sizeof str);
4942 if (k->mask & GDK_SHIFT_MASK)
4943 strlcat(str, "S-", sizeof str);
4944 if (k->mask & GDK_CONTROL_MASK)
4945 strlcat(str, "C-", sizeof str);
4946 if (k->mask & GDK_MOD1_MASK)
4947 strlcat(str, "M1-", sizeof str);
4948 if (k->mask & GDK_MOD2_MASK)
4949 strlcat(str, "M2-", sizeof str);
4950 if (k->mask & GDK_MOD3_MASK)
4951 strlcat(str, "M3-", sizeof str);
4952 if (k->mask & GDK_MOD4_MASK)
4953 strlcat(str, "M4-", sizeof str);
4954 if (k->mask & GDK_MOD5_MASK)
4955 strlcat(str, "M5-", sizeof str);
4957 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4958 cb(s, str, cb_args);
4962 void
4963 init_keybindings(void)
4965 int i;
4966 struct key_binding *k;
4968 for (i = 0; i < LENGTH(keys); i++) {
4969 k = g_malloc0(sizeof *k);
4970 k->cmd = keys[i].cmd;
4971 k->mask = keys[i].mask;
4972 k->use_in_entry = keys[i].use_in_entry;
4973 k->key = keys[i].key;
4974 TAILQ_INSERT_HEAD(&kbl, k, entry);
4976 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4977 k->cmd ? k->cmd : "unnamed key");
4981 void
4982 keybinding_clearall(void)
4984 struct key_binding *k, *next;
4986 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4987 next = TAILQ_NEXT(k, entry);
4988 if (k->cmd == NULL)
4989 continue;
4991 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4992 k->cmd ? k->cmd : "unnamed key");
4993 TAILQ_REMOVE(&kbl, k, entry);
4994 g_free(k);
4999 keybinding_add(char *cmd, char *key, int use_in_entry)
5001 struct key_binding *k;
5002 guint keyval, mask = 0;
5003 int i;
5005 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5007 /* Keys which are to be used in entry have been prefixed with an
5008 * exclamation mark. */
5009 if (use_in_entry)
5010 key++;
5012 /* find modifier keys */
5013 if (strstr(key, "S-"))
5014 mask |= GDK_SHIFT_MASK;
5015 if (strstr(key, "C-"))
5016 mask |= GDK_CONTROL_MASK;
5017 if (strstr(key, "M1-"))
5018 mask |= GDK_MOD1_MASK;
5019 if (strstr(key, "M2-"))
5020 mask |= GDK_MOD2_MASK;
5021 if (strstr(key, "M3-"))
5022 mask |= GDK_MOD3_MASK;
5023 if (strstr(key, "M4-"))
5024 mask |= GDK_MOD4_MASK;
5025 if (strstr(key, "M5-"))
5026 mask |= GDK_MOD5_MASK;
5028 /* find keyname */
5029 for (i = strlen(key) - 1; i > 0; i--)
5030 if (key[i] == '-')
5031 key = &key[i + 1];
5033 /* validate keyname */
5034 keyval = gdk_keyval_from_name(key);
5035 if (keyval == GDK_VoidSymbol) {
5036 warnx("invalid keybinding name %s", key);
5037 return (1);
5039 /* must run this test too, gtk+ doesn't handle 10 for example */
5040 if (gdk_keyval_name(keyval) == NULL) {
5041 warnx("invalid keybinding name %s", key);
5042 return (1);
5045 /* Remove eventual dupes. */
5046 TAILQ_FOREACH(k, &kbl, entry)
5047 if (k->key == keyval && k->mask == mask) {
5048 TAILQ_REMOVE(&kbl, k, entry);
5049 g_free(k);
5050 break;
5053 /* add keyname */
5054 k = g_malloc0(sizeof *k);
5055 k->cmd = g_strdup(cmd);
5056 k->mask = mask;
5057 k->use_in_entry = use_in_entry;
5058 k->key = keyval;
5060 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5061 k->cmd,
5062 k->mask,
5063 k->use_in_entry,
5064 k->key);
5065 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5066 k->cmd, gdk_keyval_name(keyval));
5068 TAILQ_INSERT_HEAD(&kbl, k, entry);
5070 return (0);
5074 add_kb(struct settings *s, char *entry)
5076 char *kb, *key;
5078 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5080 /* clearall is special */
5081 if (!strcmp(entry, "clearall")) {
5082 keybinding_clearall();
5083 return (0);
5086 kb = strstr(entry, ",");
5087 if (kb == NULL)
5088 return (1);
5089 *kb = '\0';
5090 key = kb + 1;
5092 return (keybinding_add(entry, key, key[0] == '!'));
5095 struct cmd {
5096 char *cmd;
5097 int level;
5098 int (*func)(struct tab *, struct karg *);
5099 int arg;
5100 int type;
5101 } cmds[] = {
5102 { "command", 0, command, ':', 0 },
5103 { "search", 0, command, '/', 0 },
5104 { "searchb", 0, command, '?', 0 },
5105 { "togglesrc", 0, toggle_src, 0, 0 },
5107 /* yanking and pasting */
5108 { "yankuri", 0, yank_uri, 0, 0 },
5109 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5110 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5111 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5113 /* search */
5114 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5115 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5117 /* focus */
5118 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5119 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5121 /* hinting */
5122 { "hinting", 0, hint, 0, 0 },
5124 /* custom stylesheet */
5125 { "userstyle", 0, userstyle, 0, 0 },
5127 /* navigation */
5128 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5129 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5130 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5131 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5133 /* vertical movement */
5134 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5135 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5136 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5137 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5138 { "1", 0, move, XT_MOVE_TOP, 0 },
5139 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5140 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5141 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5142 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5143 /* horizontal movement */
5144 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5145 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5146 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5147 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5150 { "favorites", 0, xtp_page_fl, 0, 0 },
5151 { "fav", 0, xtp_page_fl, 0, 0 },
5152 { "favadd", 0, add_favorite, 0, 0 },
5154 { "qall", 0, quit, 0, 0 },
5155 { "quitall", 0, quit, 0, 0 },
5156 { "w", 0, save_tabs, 0, 0 },
5157 { "wq", 0, save_tabs_and_quit, 0, 0 },
5158 { "help", 0, help, 0, 0 },
5159 { "about", 0, about, 0, 0 },
5160 { "stats", 0, stats, 0, 0 },
5161 { "version", 0, about, 0, 0 },
5163 /* js command */
5164 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5165 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5166 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5167 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5168 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5169 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5170 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5171 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5172 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5173 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5174 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5176 /* cookie command */
5177 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5178 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5179 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5180 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5181 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5182 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5183 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5184 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5185 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5186 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5187 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5189 /* toplevel (domain) command */
5190 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5191 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5193 /* cookie jar */
5194 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5196 /* cert command */
5197 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5198 { "save", 1, cert_cmd, XT_SAVE, 0 },
5199 { "show", 1, cert_cmd, XT_SHOW, 0 },
5201 { "ca", 0, ca_cmd, 0, 0 },
5202 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5203 { "dl", 0, xtp_page_dl, 0, 0 },
5204 { "h", 0, xtp_page_hl, 0, 0 },
5205 { "history", 0, xtp_page_hl, 0, 0 },
5206 { "home", 0, go_home, 0, 0 },
5207 { "restart", 0, restart, 0, 0 },
5208 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5209 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5210 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5211 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5213 { "print", 0, print_page, 0, 0 },
5215 /* tabs */
5216 { "focusin", 0, resizetab, 1, 0 },
5217 { "focusout", 0, resizetab, -1, 0 },
5218 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5219 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5220 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5221 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5222 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5223 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5224 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5225 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5226 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5227 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5228 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5229 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5230 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5231 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5232 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5233 { "buffers", 0, buffers, 0, 0 },
5234 { "ls", 0, buffers, 0, 0 },
5235 { "tabs", 0, buffers, 0, 0 },
5237 /* command aliases (handy when -S flag is used) */
5238 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5239 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5240 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5241 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5243 /* settings */
5244 { "set", 0, set, 0, 0 },
5245 { "fullscreen", 0, fullscreen, 0, 0 },
5246 { "f", 0, fullscreen, 0, 0 },
5248 /* sessions */
5249 { "session", 0, session_cmd, XT_SHOW, 0 },
5250 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5251 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5252 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5253 { "show", 1, session_cmd, XT_SHOW, 0 },
5256 struct {
5257 int index;
5258 int len;
5259 gchar *list[256];
5260 } cmd_status = {-1, 0};
5262 gboolean
5263 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5265 struct karg a;
5267 hide_oops(t);
5268 hide_buffers(t);
5270 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5271 /* go backward */
5272 a.i = XT_NAV_BACK;
5273 navaction(t, &a);
5275 return (TRUE);
5276 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5277 /* go forward */
5278 a.i = XT_NAV_FORWARD;
5279 navaction(t, &a);
5281 return (TRUE);
5284 return (FALSE);
5287 gboolean
5288 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5290 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5292 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5293 delete_tab(t);
5295 return (FALSE);
5299 * cancel, remove, etc. downloads
5301 void
5302 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5304 struct download find, *d = NULL;
5306 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5308 /* some commands require a valid download id */
5309 if (cmd != XT_XTP_DL_LIST) {
5310 /* lookup download in question */
5311 find.id = id;
5312 d = RB_FIND(download_list, &downloads, &find);
5314 if (d == NULL) {
5315 show_oops(t, "%s: no such download", __func__);
5316 return;
5320 /* decide what to do */
5321 switch (cmd) {
5322 case XT_XTP_DL_CANCEL:
5323 webkit_download_cancel(d->download);
5324 break;
5325 case XT_XTP_DL_REMOVE:
5326 webkit_download_cancel(d->download); /* just incase */
5327 g_object_unref(d->download);
5328 RB_REMOVE(download_list, &downloads, d);
5329 break;
5330 case XT_XTP_DL_LIST:
5331 /* Nothing */
5332 break;
5333 default:
5334 show_oops(t, "%s: unknown command", __func__);
5335 break;
5337 xtp_page_dl(t, NULL);
5341 * Actions on history, only does one thing for now, but
5342 * we provide the function for future actions
5344 void
5345 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5347 struct history *h, *next;
5348 int i = 1;
5350 switch (cmd) {
5351 case XT_XTP_HL_REMOVE:
5352 /* walk backwards, as listed in reverse */
5353 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5354 next = RB_PREV(history_list, &hl, h);
5355 if (id == i) {
5356 RB_REMOVE(history_list, &hl, h);
5357 g_free((gpointer) h->title);
5358 g_free((gpointer) h->uri);
5359 g_free(h);
5360 break;
5362 i++;
5364 break;
5365 case XT_XTP_HL_LIST:
5366 /* Nothing - just xtp_page_hl() below */
5367 break;
5368 default:
5369 show_oops(t, "%s: unknown command", __func__);
5370 break;
5373 xtp_page_hl(t, NULL);
5376 /* remove a favorite */
5377 void
5378 remove_favorite(struct tab *t, int index)
5380 char file[PATH_MAX], *title, *uri = NULL;
5381 char *new_favs, *tmp;
5382 FILE *f;
5383 int i;
5384 size_t len, lineno;
5386 /* open favorites */
5387 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5389 if ((f = fopen(file, "r")) == NULL) {
5390 show_oops(t, "%s: can't open favorites: %s",
5391 __func__, strerror(errno));
5392 return;
5395 /* build a string which will become the new favroites file */
5396 new_favs = g_strdup("");
5398 for (i = 1;;) {
5399 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5400 if (feof(f) || ferror(f))
5401 break;
5402 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5403 if (len == 0) {
5404 free(title);
5405 title = NULL;
5406 continue;
5409 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5410 if (feof(f) || ferror(f)) {
5411 show_oops(t, "%s: can't parse favorites %s",
5412 __func__, strerror(errno));
5413 goto clean;
5417 /* as long as this isn't the one we are deleting add to file */
5418 if (i != index) {
5419 tmp = new_favs;
5420 new_favs = g_strdup_printf("%s%s\n%s\n",
5421 new_favs, title, uri);
5422 g_free(tmp);
5425 free(uri);
5426 uri = NULL;
5427 free(title);
5428 title = NULL;
5429 i++;
5431 fclose(f);
5433 /* write back new favorites file */
5434 if ((f = fopen(file, "w")) == NULL) {
5435 show_oops(t, "%s: can't open favorites: %s",
5436 __func__, strerror(errno));
5437 goto clean;
5440 fwrite(new_favs, strlen(new_favs), 1, f);
5441 fclose(f);
5443 clean:
5444 if (uri)
5445 free(uri);
5446 if (title)
5447 free(title);
5449 g_free(new_favs);
5452 void
5453 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5455 switch (cmd) {
5456 case XT_XTP_FL_LIST:
5457 /* nothing, just the below call to xtp_page_fl() */
5458 break;
5459 case XT_XTP_FL_REMOVE:
5460 remove_favorite(t, arg);
5461 break;
5462 default:
5463 show_oops(t, "%s: invalid favorites command", __func__);
5464 break;
5467 xtp_page_fl(t, NULL);
5470 void
5471 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5473 switch (cmd) {
5474 case XT_XTP_CL_LIST:
5475 /* nothing, just xtp_page_cl() */
5476 break;
5477 case XT_XTP_CL_REMOVE:
5478 remove_cookie(arg);
5479 break;
5480 default:
5481 show_oops(t, "%s: unknown cookie xtp command", __func__);
5482 break;
5485 xtp_page_cl(t, NULL);
5488 /* link an XTP class to it's session key and handler function */
5489 struct xtp_despatch {
5490 uint8_t xtp_class;
5491 char **session_key;
5492 void (*handle_func)(struct tab *, uint8_t, int);
5495 struct xtp_despatch xtp_despatches[] = {
5496 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5497 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5498 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5499 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5500 { XT_XTP_INVALID, NULL, NULL }
5504 * is the url xtp protocol? (xxxt://)
5505 * if so, parse and despatch correct bahvior
5508 parse_xtp_url(struct tab *t, const char *url)
5510 char *dup = NULL, *p, *last;
5511 uint8_t n_tokens = 0;
5512 char *tokens[4] = {NULL, NULL, NULL, ""};
5513 struct xtp_despatch *dsp, *dsp_match = NULL;
5514 uint8_t req_class;
5515 int ret = FALSE;
5518 * tokens array meaning:
5519 * tokens[0] = class
5520 * tokens[1] = session key
5521 * tokens[2] = action
5522 * tokens[3] = optional argument
5525 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5527 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5528 goto clean;
5530 dup = g_strdup(url + strlen(XT_XTP_STR));
5532 /* split out the url */
5533 for ((p = strtok_r(dup, "/", &last)); p;
5534 (p = strtok_r(NULL, "/", &last))) {
5535 if (n_tokens < 4)
5536 tokens[n_tokens++] = p;
5539 /* should be atleast three fields 'class/seskey/command/arg' */
5540 if (n_tokens < 3)
5541 goto clean;
5543 dsp = xtp_despatches;
5544 req_class = atoi(tokens[0]);
5545 while (dsp->xtp_class) {
5546 if (dsp->xtp_class == req_class) {
5547 dsp_match = dsp;
5548 break;
5550 dsp++;
5553 /* did we find one atall? */
5554 if (dsp_match == NULL) {
5555 show_oops(t, "%s: no matching xtp despatch found", __func__);
5556 goto clean;
5559 /* check session key and call despatch function */
5560 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5561 ret = TRUE; /* all is well, this was a valid xtp request */
5562 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5565 clean:
5566 if (dup)
5567 g_free(dup);
5569 return (ret);
5574 void
5575 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5577 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5579 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5581 if (t == NULL) {
5582 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5583 return;
5586 if (uri == NULL) {
5587 show_oops(t, "activate_uri_entry_cb no uri");
5588 return;
5591 uri += strspn(uri, "\t ");
5593 /* if xxxt:// treat specially */
5594 if (parse_xtp_url(t, uri))
5595 return;
5597 /* otherwise continue to load page normally */
5598 load_uri(t, (gchar *)uri);
5599 focus_webview(t);
5602 void
5603 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5605 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5606 char *newuri = NULL;
5607 gchar *enc_search;
5609 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5611 if (t == NULL) {
5612 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5613 return;
5616 if (search_string == NULL) {
5617 show_oops(t, "no search_string");
5618 return;
5621 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5622 newuri = g_strdup_printf(search_string, enc_search);
5623 g_free(enc_search);
5625 webkit_web_view_load_uri(t->wv, newuri);
5626 focus_webview(t);
5628 if (newuri)
5629 g_free(newuri);
5632 void
5633 check_and_set_js(const gchar *uri, struct tab *t)
5635 struct domain *d = NULL;
5636 int es = 0;
5638 if (uri == NULL || t == NULL)
5639 return;
5641 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5642 es = 0;
5643 else
5644 es = 1;
5646 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5647 es ? "enable" : "disable", uri);
5649 g_object_set(G_OBJECT(t->settings),
5650 "enable-scripts", es, (char *)NULL);
5651 g_object_set(G_OBJECT(t->settings),
5652 "javascript-can-open-windows-automatically", es, (char *)NULL);
5653 webkit_web_view_set_settings(t->wv, t->settings);
5655 button_set_stockid(t->js_toggle,
5656 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5659 void
5660 show_ca_status(struct tab *t, const char *uri)
5662 WebKitWebFrame *frame;
5663 WebKitWebDataSource *source;
5664 WebKitNetworkRequest *request;
5665 SoupMessage *message;
5666 GdkColor color;
5667 gchar *col_str = XT_COLOR_WHITE;
5668 int r;
5670 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5671 ssl_strict_certs, ssl_ca_file, uri);
5673 if (uri == NULL)
5674 goto done;
5675 if (ssl_ca_file == NULL) {
5676 if (g_str_has_prefix(uri, "http://"))
5677 goto done;
5678 if (g_str_has_prefix(uri, "https://")) {
5679 col_str = XT_COLOR_RED;
5680 goto done;
5682 return;
5684 if (g_str_has_prefix(uri, "http://") ||
5685 !g_str_has_prefix(uri, "https://"))
5686 goto done;
5688 frame = webkit_web_view_get_main_frame(t->wv);
5689 source = webkit_web_frame_get_data_source(frame);
5690 request = webkit_web_data_source_get_request(source);
5691 message = webkit_network_request_get_message(request);
5693 if (message && (soup_message_get_flags(message) &
5694 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5695 col_str = XT_COLOR_GREEN;
5696 goto done;
5697 } else {
5698 r = load_compare_cert(t, NULL);
5699 if (r == 0)
5700 col_str = XT_COLOR_BLUE;
5701 else if (r == 1)
5702 col_str = XT_COLOR_YELLOW;
5703 else
5704 col_str = XT_COLOR_RED;
5705 goto done;
5707 done:
5708 if (col_str) {
5709 gdk_color_parse(col_str, &color);
5710 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5712 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5713 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5714 &color);
5715 gdk_color_parse(XT_COLOR_BLACK, &color);
5716 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5717 &color);
5718 } else {
5719 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5720 &color);
5721 gdk_color_parse(XT_COLOR_BLACK, &color);
5722 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5723 &color);
5728 void
5729 free_favicon(struct tab *t)
5731 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5732 __func__, t->icon_download, t->icon_request);
5734 if (t->icon_request)
5735 g_object_unref(t->icon_request);
5736 if (t->icon_dest_uri)
5737 g_free(t->icon_dest_uri);
5739 t->icon_request = NULL;
5740 t->icon_dest_uri = NULL;
5743 void
5744 xt_icon_from_name(struct tab *t, gchar *name)
5746 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5747 GTK_ENTRY_ICON_PRIMARY, "text-html");
5748 if (show_url == 0)
5749 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5750 GTK_ENTRY_ICON_PRIMARY, "text-html");
5751 else
5752 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5753 GTK_ENTRY_ICON_PRIMARY, NULL);
5756 void
5757 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5759 GdkPixbuf *pb_scaled;
5761 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5762 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16, GDK_INTERP_BILINEAR);
5763 else
5764 pb_scaled = pb;
5766 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5767 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5768 if (show_url == 0)
5769 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5770 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5771 else
5772 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5773 GTK_ENTRY_ICON_PRIMARY, NULL);
5775 if (pb_scaled != pb)
5776 g_object_unref(pb_scaled);
5779 void
5780 xt_icon_from_file(struct tab *t, char *file)
5782 GdkPixbuf *pb;
5784 if (g_str_has_prefix(file, "file://"))
5785 file += strlen("file://");
5787 pb = gdk_pixbuf_new_from_file(file, NULL);
5788 if (pb) {
5789 xt_icon_from_pixbuf(t, pb);
5790 g_object_unref(pb);
5791 } else
5792 xt_icon_from_name(t, "text-html");
5795 gboolean
5796 is_valid_icon(char *file)
5798 gboolean valid = 0;
5799 const char *mime_type;
5800 GFileInfo *fi;
5801 GFile *gf;
5803 gf = g_file_new_for_path(file);
5804 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5805 NULL, NULL);
5806 mime_type = g_file_info_get_content_type(fi);
5807 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5808 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5809 g_strcmp0(mime_type, "image/png") == 0 ||
5810 g_strcmp0(mime_type, "image/gif") == 0 ||
5811 g_strcmp0(mime_type, "application/octet-stream") == 0;
5812 g_object_unref(fi);
5813 g_object_unref(gf);
5815 return (valid);
5818 void
5819 set_favicon_from_file(struct tab *t, char *file)
5821 struct stat sb;
5823 if (t == NULL || file == NULL)
5824 return;
5826 if (g_str_has_prefix(file, "file://"))
5827 file += strlen("file://");
5828 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5830 if (!stat(file, &sb)) {
5831 if (sb.st_size == 0 || !is_valid_icon(file)) {
5832 /* corrupt icon so trash it */
5833 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5834 __func__, file);
5835 unlink(file);
5836 /* no need to set icon to default here */
5837 return;
5840 xt_icon_from_file(t, file);
5843 void
5844 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5845 WebKitWebView *wv)
5847 WebKitDownloadStatus status = webkit_download_get_status(download);
5848 struct tab *tt = NULL, *t = NULL;
5851 * find the webview instead of passing in the tab as it could have been
5852 * deleted from underneath us.
5854 TAILQ_FOREACH(tt, &tabs, entry) {
5855 if (tt->wv == wv) {
5856 t = tt;
5857 break;
5860 if (t == NULL)
5861 return;
5863 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5864 __func__, t->tab_id, status);
5866 switch (status) {
5867 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5868 /* -1 */
5869 t->icon_download = NULL;
5870 free_favicon(t);
5871 break;
5872 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5873 /* 0 */
5874 break;
5875 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5876 /* 1 */
5877 break;
5878 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5879 /* 2 */
5880 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5881 __func__, t->tab_id);
5882 t->icon_download = NULL;
5883 free_favicon(t);
5884 break;
5885 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5886 /* 3 */
5888 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5889 __func__, t->icon_dest_uri);
5890 set_favicon_from_file(t, t->icon_dest_uri);
5891 /* these will be freed post callback */
5892 t->icon_request = NULL;
5893 t->icon_download = NULL;
5894 break;
5895 default:
5896 break;
5900 void
5901 abort_favicon_download(struct tab *t)
5903 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5905 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5906 if (t->icon_download) {
5907 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5908 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5909 webkit_download_cancel(t->icon_download);
5910 t->icon_download = NULL;
5911 } else
5912 free_favicon(t);
5915 xt_icon_from_name(t, "text-html");
5918 void
5919 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5921 GdkPixbuf *pb;
5922 gchar *name_hash, file[PATH_MAX];
5923 struct stat sb;
5925 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5927 if (uri == NULL || t == NULL)
5928 return;
5930 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5931 /* take icon from WebKitIconDatabase */
5932 pb = webkit_web_view_get_icon_pixbuf(wv);
5933 if (pb) {
5934 xt_icon_from_pixbuf(t, pb);
5935 g_object_unref(pb);
5936 } else
5937 xt_icon_from_name(t, "text-html");
5939 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5940 /* download icon to cache dir */
5941 if (t->icon_request) {
5942 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5943 return;
5946 /* check to see if we got the icon in cache */
5947 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5948 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5949 g_free(name_hash);
5951 if (!stat(file, &sb)) {
5952 if (sb.st_size > 0) {
5953 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5954 __func__, file);
5955 set_favicon_from_file(t, file);
5956 return;
5959 /* corrupt icon so trash it */
5960 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5961 __func__, file);
5962 unlink(file);
5965 /* create download for icon */
5966 t->icon_request = webkit_network_request_new(uri);
5967 if (t->icon_request == NULL) {
5968 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5969 __func__, uri);
5970 return;
5973 t->icon_download = webkit_download_new(t->icon_request);
5974 if (t->icon_download == NULL)
5975 return;
5977 /* we have to free icon_dest_uri later */
5978 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5979 webkit_download_set_destination_uri(t->icon_download,
5980 t->icon_dest_uri);
5982 if (webkit_download_get_status(t->icon_download) ==
5983 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5984 g_object_unref(t->icon_request);
5985 g_free(t->icon_dest_uri);
5986 t->icon_request = NULL;
5987 t->icon_dest_uri = NULL;
5988 return;
5991 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5992 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5994 webkit_download_start(t->icon_download);
5998 void
5999 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6001 const gchar *set = NULL, *uri = NULL, *title = NULL;
6002 struct history *h, find;
6003 const gchar *s_loading;
6004 struct karg a;
6006 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6007 webkit_web_view_get_load_status(wview), get_uri(t) ? get_uri(t) : "NOTHING");
6009 if (t == NULL) {
6010 show_oops(NULL, "notify_load_status_cb invalid parameters");
6011 return;
6014 switch (webkit_web_view_get_load_status(wview)) {
6015 case WEBKIT_LOAD_PROVISIONAL:
6016 /* 0 */
6017 abort_favicon_download(t);
6018 #if GTK_CHECK_VERSION(2, 20, 0)
6019 gtk_widget_show(t->spinner);
6020 gtk_spinner_start(GTK_SPINNER(t->spinner));
6021 #endif
6022 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6024 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6026 /* take focus if we are visible */
6027 focus_webview(t);
6028 t->focus_wv = 1;
6030 break;
6032 case WEBKIT_LOAD_COMMITTED:
6033 /* 1 */
6034 if ((uri = get_uri(t)) != NULL) {
6035 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6037 if (t->status) {
6038 g_free(t->status);
6039 t->status = NULL;
6041 set_status(t, (char *)uri, XT_STATUS_LOADING);
6044 /* check if js white listing is enabled */
6045 if (enable_js_whitelist) {
6046 uri = get_uri(t);
6047 check_and_set_js(uri, t);
6050 if (t->styled)
6051 apply_style(t);
6053 show_ca_status(t, uri);
6055 /* we know enough to autosave the session */
6056 if (session_autosave) {
6057 a.s = NULL;
6058 save_tabs(t, &a);
6060 break;
6062 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6063 /* 3 */
6064 break;
6066 case WEBKIT_LOAD_FINISHED:
6067 /* 2 */
6068 uri = get_uri(t);
6069 if (uri == NULL)
6070 return;
6072 if (!strncmp(uri, "http://", strlen("http://")) ||
6073 !strncmp(uri, "https://", strlen("https://")) ||
6074 !strncmp(uri, "file://", strlen("file://"))) {
6075 find.uri = uri;
6076 h = RB_FIND(history_list, &hl, &find);
6077 if (!h) {
6078 title = webkit_web_view_get_title(wview);
6079 set = title ? title: uri;
6080 h = g_malloc(sizeof *h);
6081 h->uri = g_strdup(uri);
6082 h->title = g_strdup(set);
6083 RB_INSERT(history_list, &hl, h);
6084 completion_add_uri(h->uri);
6085 update_history_tabs(NULL);
6089 set_status(t, (char *)uri, XT_STATUS_URI);
6090 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6091 case WEBKIT_LOAD_FAILED:
6092 /* 4 */
6093 #endif
6094 #if GTK_CHECK_VERSION(2, 20, 0)
6095 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6096 gtk_widget_hide(t->spinner);
6097 #endif
6098 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
6099 if (s_loading && !strcmp(s_loading, "Loading"))
6100 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6101 default:
6102 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6103 break;
6106 if (t->item)
6107 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6108 else
6109 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6110 webkit_web_view_can_go_back(wview));
6112 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6113 webkit_web_view_can_go_forward(wview));
6116 void
6117 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6119 const gchar *set = NULL, *title = NULL;
6121 title = webkit_web_view_get_title(wview);
6122 set = title ? title : get_uri(t);
6123 if (set) {
6124 gtk_label_set_text(GTK_LABEL(t->label), set);
6125 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), set);
6126 gtk_window_set_title(GTK_WINDOW(main_window), set);
6127 } else {
6128 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6129 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), "(untitled)");
6130 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6134 void
6135 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6137 run_script(t, JS_HINTING);
6140 void
6141 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6143 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6144 progress == 100 ? 0 : (double)progress / 100);
6145 if (show_url == 0) {
6146 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
6147 progress == 100 ? 0 : (double)progress / 100);
6152 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6153 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6154 WebKitWebPolicyDecision *pd, struct tab *t)
6156 char *uri;
6157 WebKitWebNavigationReason reason;
6158 struct domain *d = NULL;
6160 if (t == NULL) {
6161 show_oops(NULL, "webview_npd_cb invalid parameters");
6162 return (FALSE);
6165 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6166 t->ctrl_click,
6167 webkit_network_request_get_uri(request));
6169 uri = (char *)webkit_network_request_get_uri(request);
6171 /* if this is an xtp url, we don't load anything else */
6172 if (parse_xtp_url(t, uri))
6173 return (TRUE);
6175 if (t->ctrl_click) {
6176 t->ctrl_click = 0;
6177 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6178 webkit_web_policy_decision_ignore(pd);
6179 return (TRUE); /* we made the decission */
6183 * This is a little hairy but it comes down to this:
6184 * when we run in whitelist mode we have to assist the browser in
6185 * opening the URL that it would have opened in a new tab.
6187 reason = webkit_web_navigation_action_get_reason(na);
6188 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6189 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6190 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6191 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6192 load_uri(t, uri);
6193 webkit_web_policy_decision_use(pd);
6194 return (TRUE); /* we made the decision */
6197 return (FALSE);
6200 WebKitWebView *
6201 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6203 struct tab *tt;
6204 struct domain *d = NULL;
6205 const gchar *uri;
6206 WebKitWebView *webview = NULL;
6208 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6209 webkit_web_view_get_uri(wv));
6211 if (tabless) {
6212 /* open in current tab */
6213 webview = t->wv;
6214 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6215 uri = webkit_web_view_get_uri(wv);
6216 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6217 return (NULL);
6219 tt = create_new_tab(NULL, NULL, 1, -1);
6220 webview = tt->wv;
6221 } else if (enable_scripts == 1) {
6222 tt = create_new_tab(NULL, NULL, 1, -1);
6223 webview = tt->wv;
6226 return (webview);
6229 gboolean
6230 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6232 const gchar *uri;
6233 struct domain *d = NULL;
6235 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6237 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6238 uri = webkit_web_view_get_uri(wv);
6239 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6240 return (FALSE);
6242 delete_tab(t);
6243 } else if (enable_scripts == 1)
6244 delete_tab(t);
6246 return (TRUE);
6250 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6252 /* we can not eat the event without throwing gtk off so defer it */
6254 /* catch middle click */
6255 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6256 t->ctrl_click = 1;
6257 goto done;
6260 /* catch ctrl click */
6261 if (e->type == GDK_BUTTON_RELEASE &&
6262 CLEAN(e->state) == GDK_CONTROL_MASK)
6263 t->ctrl_click = 1;
6264 else
6265 t->ctrl_click = 0;
6266 done:
6267 return (XT_CB_PASSTHROUGH);
6271 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6273 struct mime_type *m;
6275 m = find_mime_type(mime_type);
6276 if (m == NULL)
6277 return (1);
6278 if (m->mt_download)
6279 return (1);
6281 switch (fork()) {
6282 case -1:
6283 show_oops(t, "can't fork mime handler");
6284 /* NOTREACHED */
6285 case 0:
6286 break;
6287 default:
6288 return (0);
6291 /* child */
6292 execlp(m->mt_action, m->mt_action,
6293 webkit_network_request_get_uri(request), (void *)NULL);
6295 _exit(0);
6297 /* NOTREACHED */
6298 return (0);
6301 const gchar *
6302 get_mime_type(char *file)
6304 const char *mime_type;
6305 GFileInfo *fi;
6306 GFile *gf;
6308 if (g_str_has_prefix(file, "file://"))
6309 file += strlen("file://");
6311 gf = g_file_new_for_path(file);
6312 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6313 NULL, NULL);
6314 mime_type = g_file_info_get_content_type(fi);
6315 g_object_unref(fi);
6316 g_object_unref(gf);
6318 return (mime_type);
6322 run_download_mimehandler(char *mime_type, char *file)
6324 struct mime_type *m;
6326 m = find_mime_type(mime_type);
6327 if (m == NULL)
6328 return (1);
6330 switch (fork()) {
6331 case -1:
6332 show_oops(NULL, "can't fork download mime handler");
6333 return (1);
6334 /* NOTREACHED */
6335 case 0:
6336 break;
6337 default:
6338 return (0);
6341 /* child */
6342 if (g_str_has_prefix(file, "file://"))
6343 file += strlen("file://");
6344 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6346 _exit(0);
6348 /* NOTREACHED */
6349 return (0);
6352 void
6353 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6354 WebKitWebView *wv)
6356 WebKitDownloadStatus status;
6357 const gchar *file = NULL, *mime = NULL;
6359 if (download == NULL)
6360 return;
6361 status = webkit_download_get_status(download);
6362 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6363 return;
6365 file = webkit_download_get_destination_uri(download);
6366 if (file == NULL)
6367 return;
6368 mime = get_mime_type((char *)file);
6369 if (mime == NULL)
6370 return;
6372 run_download_mimehandler((char *)mime, (char *)file);
6376 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6377 WebKitNetworkRequest *request, char *mime_type,
6378 WebKitWebPolicyDecision *decision, struct tab *t)
6380 if (t == NULL) {
6381 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6382 return (FALSE);
6385 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6386 t->tab_id, mime_type);
6388 if (run_mimehandler(t, mime_type, request) == 0) {
6389 webkit_web_policy_decision_ignore(decision);
6390 focus_webview(t);
6391 return (TRUE);
6394 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6395 webkit_web_policy_decision_download(decision);
6396 return (TRUE);
6399 return (FALSE);
6403 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6404 struct tab *t)
6406 struct stat sb;
6407 const gchar *suggested_name;
6408 gchar *filename = NULL;
6409 char *uri = NULL;
6410 struct download *download_entry;
6411 int i, ret = TRUE;
6413 if (wk_download == NULL || t == NULL) {
6414 show_oops(NULL, "%s invalid parameters", __func__);
6415 return (FALSE);
6418 suggested_name = webkit_download_get_suggested_filename(wk_download);
6419 if (suggested_name == NULL)
6420 return (FALSE); /* abort download */
6422 i = 0;
6423 do {
6424 if (filename) {
6425 g_free(filename);
6426 filename = NULL;
6428 if (i) {
6429 g_free(uri);
6430 uri = NULL;
6431 filename = g_strdup_printf("%d%s", i, suggested_name);
6433 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6434 filename : suggested_name);
6435 i++;
6436 } while (!stat(uri + strlen("file://"), &sb));
6438 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6439 "local %s\n", __func__, t->tab_id, filename, uri);
6441 webkit_download_set_destination_uri(wk_download, uri);
6443 if (webkit_download_get_status(wk_download) ==
6444 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6445 show_oops(t, "%s: download failed to start", __func__);
6446 ret = FALSE;
6447 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6448 } else {
6449 /* connect "download first" mime handler */
6450 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6451 G_CALLBACK(download_status_changed_cb), NULL);
6453 download_entry = g_malloc(sizeof(struct download));
6454 download_entry->download = wk_download;
6455 download_entry->tab = t;
6456 download_entry->id = next_download_id++;
6457 RB_INSERT(download_list, &downloads, download_entry);
6458 /* get from history */
6459 g_object_ref(wk_download);
6460 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6461 show_oops(t, "Download of '%s' started...",
6462 basename((char *)webkit_download_get_destination_uri(wk_download)));
6465 if (uri)
6466 g_free(uri);
6468 if (filename)
6469 g_free(filename);
6471 /* sync other download manager tabs */
6472 update_download_tabs(NULL);
6475 * NOTE: never redirect/render the current tab before this
6476 * function returns. This will cause the download to never start.
6478 return (ret); /* start download */
6481 void
6482 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6484 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6486 if (t == NULL) {
6487 show_oops(NULL, "webview_hover_cb");
6488 return;
6491 if (uri)
6492 set_status(t, uri, XT_STATUS_LINK);
6493 else {
6494 if (t->status)
6495 set_status(t, t->status, XT_STATUS_NOTHING);
6499 gboolean
6500 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6502 struct key_binding *k;
6504 TAILQ_FOREACH(k, &kbl, entry)
6505 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6506 if (k->mask == 0) {
6507 if ((e->state & (CTRL | MOD1)) == 0)
6508 return (cmd_execute(t, k->cmd));
6509 } else if ((e->state & k->mask) == k->mask) {
6510 return (cmd_execute(t, k->cmd));
6514 return (XT_CB_PASSTHROUGH);
6518 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6520 char s[2], buf[128];
6521 const char *errstr = NULL;
6522 long long link;
6524 /* don't use w directly; use t->whatever instead */
6526 if (t == NULL) {
6527 show_oops(NULL, "wv_keypress_after_cb");
6528 return (XT_CB_PASSTHROUGH);
6531 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6532 e->keyval, e->state, t);
6534 if (t->hints_on) {
6535 /* ESC */
6536 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6537 disable_hints(t);
6538 return (XT_CB_HANDLED);
6541 /* RETURN */
6542 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6543 link = strtonum(t->hint_num, 1, 1000, &errstr);
6544 if (errstr) {
6545 /* we have a string */
6546 } else {
6547 /* we have a number */
6548 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6549 t->hint_num);
6550 run_script(t, buf);
6552 disable_hints(t);
6555 /* BACKSPACE */
6556 /* XXX unfuck this */
6557 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6558 if (t->hint_mode == XT_HINT_NUMERICAL) {
6559 /* last input was numerical */
6560 int l;
6561 l = strlen(t->hint_num);
6562 if (l > 0) {
6563 l--;
6564 if (l == 0) {
6565 disable_hints(t);
6566 enable_hints(t);
6567 } else {
6568 t->hint_num[l] = '\0';
6569 goto num;
6572 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6573 /* last input was alphanumerical */
6574 int l;
6575 l = strlen(t->hint_buf);
6576 if (l > 0) {
6577 l--;
6578 if (l == 0) {
6579 disable_hints(t);
6580 enable_hints(t);
6581 } else {
6582 t->hint_buf[l] = '\0';
6583 goto anum;
6586 } else {
6587 /* bogus */
6588 disable_hints(t);
6592 /* numerical input */
6593 if (CLEAN(e->state) == 0 &&
6594 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6595 snprintf(s, sizeof s, "%c", e->keyval);
6596 strlcat(t->hint_num, s, sizeof t->hint_num);
6597 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6598 t->hint_num);
6599 num:
6600 link = strtonum(t->hint_num, 1, 1000, &errstr);
6601 if (errstr) {
6602 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6603 disable_hints(t);
6604 } else {
6605 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6606 t->hint_num);
6607 t->hint_mode = XT_HINT_NUMERICAL;
6608 run_script(t, buf);
6611 /* empty the counter buffer */
6612 bzero(t->hint_buf, sizeof t->hint_buf);
6613 return (XT_CB_HANDLED);
6616 /* alphanumerical input */
6617 if (
6618 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6619 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6620 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6621 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6622 snprintf(s, sizeof s, "%c", e->keyval);
6623 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6624 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6625 t->hint_buf);
6626 anum:
6627 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6628 run_script(t, buf);
6630 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6631 t->hint_buf);
6632 t->hint_mode = XT_HINT_ALPHANUM;
6633 run_script(t, buf);
6635 /* empty the counter buffer */
6636 bzero(t->hint_num, sizeof t->hint_num);
6637 return (XT_CB_HANDLED);
6640 return (XT_CB_HANDLED);
6641 } else {
6642 /* prefix input*/
6643 snprintf(s, sizeof s, "%c", e->keyval);
6644 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6645 cmd_prefix = 10 * cmd_prefix + atoi(s);
6649 return (handle_keypress(t, e, 0));
6653 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6655 hide_oops(t);
6657 /* Hide buffers, if they are visible, with escape. */
6658 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6659 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6660 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6661 hide_buffers(t);
6662 return (XT_CB_HANDLED);
6665 return (XT_CB_PASSTHROUGH);
6669 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6671 const gchar *c = gtk_entry_get_text(w);
6672 GdkColor color;
6673 int forward = TRUE;
6675 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6676 e->keyval, e->state, t);
6678 if (t == NULL) {
6679 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6680 return (XT_CB_PASSTHROUGH);
6683 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6684 e->keyval, e->state, t);
6686 if (c[0] == ':')
6687 goto done;
6688 if (strlen(c) == 1) {
6689 webkit_web_view_unmark_text_matches(t->wv);
6690 goto done;
6693 if (c[0] == '/')
6694 forward = TRUE;
6695 else if (c[0] == '?')
6696 forward = FALSE;
6697 else
6698 goto done;
6700 /* search */
6701 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6702 FALSE) {
6703 /* not found, mark red */
6704 gdk_color_parse(XT_COLOR_RED, &color);
6705 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6706 /* unmark and remove selection */
6707 webkit_web_view_unmark_text_matches(t->wv);
6708 /* my kingdom for a way to unselect text in webview */
6709 } else {
6710 /* found, highlight all */
6711 webkit_web_view_unmark_text_matches(t->wv);
6712 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6713 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6714 gdk_color_parse(XT_COLOR_WHITE, &color);
6715 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6717 done:
6718 return (XT_CB_PASSTHROUGH);
6721 gboolean
6722 match_uri(const gchar *uri, const gchar *key) {
6723 gchar *voffset;
6724 size_t len;
6725 gboolean match = FALSE;
6727 len = strlen(key);
6729 if (!strncmp(key, uri, len))
6730 match = TRUE;
6731 else {
6732 voffset = strstr(uri, "/") + 2;
6733 if (!strncmp(key, voffset, len))
6734 match = TRUE;
6735 else if (g_str_has_prefix(voffset, "www.")) {
6736 voffset = voffset + strlen("www.");
6737 if (!strncmp(key, voffset, len))
6738 match = TRUE;
6742 return (match);
6745 void
6746 cmd_getlist(int id, char *key)
6748 int i, dep, c = 0;
6749 struct history *h;
6751 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6752 RB_FOREACH_REVERSE(h, history_list, &hl)
6753 if (match_uri(h->uri, key)) {
6754 cmd_status.list[c] = (char *)h->uri;
6755 if (++c > 255)
6756 break;
6759 cmd_status.len = c;
6760 return;
6763 dep = (id == -1) ? 0 : cmds[id].level + 1;
6765 for (i = id + 1; i < LENGTH(cmds); i++) {
6766 if(cmds[i].level < dep)
6767 break;
6768 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6769 cmd_status.list[c++] = cmds[i].cmd;
6773 cmd_status.len = c;
6776 char *
6777 cmd_getnext(int dir)
6779 cmd_status.index += dir;
6781 if (cmd_status.index < 0)
6782 cmd_status.index = cmd_status.len - 1;
6783 else if (cmd_status.index >= cmd_status.len)
6784 cmd_status.index = 0;
6786 return cmd_status.list[cmd_status.index];
6790 cmd_tokenize(char *s, char *tokens[])
6792 int i = 0;
6793 char *tok, *last;
6794 size_t len = strlen(s);
6795 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6797 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6798 tokens[i] = tok;
6800 if (blank && i < 3)
6801 tokens[i++] = "";
6803 return (i);
6806 void
6807 cmd_complete(struct tab *t, char *str, int dir)
6809 GtkEntry *w = GTK_ENTRY(t->cmd);
6810 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6811 char *tok, *match, *s = g_strdup(str);
6812 char *tokens[3];
6813 char res[XT_MAX_URL_LENGTH + 32] = ":";
6814 char *sc = s;
6816 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6818 /* copy prefix*/
6819 for (i = 0; isdigit(s[i]); i++)
6820 res[i + 1] = s[i];
6822 for (; isspace(s[i]); i++)
6823 res[i + 1] = s[i];
6825 s += i;
6827 levels = cmd_tokenize(s, tokens);
6829 for (i = 0; i < levels - 1; i++) {
6830 tok = tokens[i];
6831 matchcount = 0;
6832 for (j = c; j < LENGTH(cmds); j++) {
6833 if (cmds[j].level < dep)
6834 break;
6835 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6836 matchcount++;
6837 c = j + 1;
6838 if (strlen(tok) == strlen(cmds[j].cmd)) {
6839 matchcount = 1;
6840 break;
6845 if (matchcount == 1) {
6846 strlcat(res, tok, sizeof res);
6847 strlcat(res, " ", sizeof res);
6848 dep++;
6849 } else {
6850 g_free(sc);
6851 return;
6854 parent = c - 1;
6857 if (cmd_status.index == -1)
6858 cmd_getlist(parent, tokens[i]);
6860 if (cmd_status.len > 0) {
6861 match = cmd_getnext(dir);
6862 strlcat(res, match, sizeof res);
6863 gtk_entry_set_text(w, res);
6864 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6867 g_free(sc);
6870 gboolean
6871 cmd_execute(struct tab *t, char *str)
6873 struct cmd *cmd = NULL;
6874 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6875 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6876 struct karg arg = {0, NULL, -1};
6877 int rv = XT_CB_PASSTHROUGH;
6879 sc = s;
6881 /* copy prefix*/
6882 for (j = 0; j<3 && isdigit(s[j]); j++)
6883 prefixstr[j]=s[j];
6885 prefixstr[j]='\0';
6887 s += j;
6888 while (isspace(s[0]))
6889 s++;
6891 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6892 prefix = atoi(prefixstr);
6893 else
6894 s = sc;
6896 for (tok = strtok_r(s, " ", &last); tok;
6897 tok = strtok_r(NULL, " ", &last)) {
6898 matchcount = 0;
6899 for (j = c; j < LENGTH(cmds); j++) {
6900 if (cmds[j].level < dep)
6901 break;
6902 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6903 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6904 matchcount++;
6905 c = j + 1;
6906 cmd = &cmds[j];
6907 if (len == strlen(cmds[j].cmd)) {
6908 matchcount = 1;
6909 break;
6913 if (matchcount == 1) {
6914 if (cmd->type > 0)
6915 goto execute_cmd;
6916 dep++;
6917 } else {
6918 show_oops(t, "Invalid command: %s", str);
6919 goto done;
6922 execute_cmd:
6923 arg.i = cmd->arg;
6925 if (prefix != -1)
6926 arg.p = prefix;
6927 else if (cmd_prefix > 0)
6928 arg.p = cmd_prefix;
6930 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6931 show_oops(t, "No prefix allowed: %s", str);
6932 goto done;
6934 if (cmd->type > 1)
6935 arg.s = last ? g_strdup(last) : g_strdup("");
6936 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6937 arg.p = atoi(arg.s);
6938 if (arg.p <= 0) {
6939 if (arg.s[0]=='0')
6940 show_oops(t, "Zero count");
6941 else
6942 show_oops(t, "Trailing characters");
6943 goto done;
6947 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6949 cmd->func(t, &arg);
6951 rv = XT_CB_HANDLED;
6952 done:
6953 if (j > 0)
6954 cmd_prefix = 0;
6955 g_free(sc);
6956 if (arg.s)
6957 g_free(arg.s);
6959 return (rv);
6963 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6965 if (t == NULL) {
6966 show_oops(NULL, "entry_key_cb invalid parameters");
6967 return (XT_CB_PASSTHROUGH);
6970 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6971 e->keyval, e->state, t);
6973 hide_oops(t);
6975 if (e->keyval == GDK_Escape) {
6976 /* don't use focus_webview(t) because we want to type :cmds */
6977 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6980 return (handle_keypress(t, e, 1));
6984 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6986 int rv = XT_CB_HANDLED;
6987 const gchar *c = gtk_entry_get_text(w);
6989 if (t == NULL) {
6990 show_oops(NULL, "cmd_keypress_cb parameters");
6991 return (XT_CB_PASSTHROUGH);
6994 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6995 e->keyval, e->state, t);
6997 /* sanity */
6998 if (c == NULL)
6999 e->keyval = GDK_Escape;
7000 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7001 e->keyval = GDK_Escape;
7003 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
7004 cmd_status.index = -1;
7006 switch (e->keyval) {
7007 case GDK_Tab:
7008 if (c[0] == ':')
7009 cmd_complete(t, (char *)&c[1], 1);
7010 goto done;
7011 case GDK_ISO_Left_Tab:
7012 if (c[0] == ':')
7013 cmd_complete(t, (char *)&c[1], -1);
7015 goto done;
7016 case GDK_BackSpace:
7017 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7018 break;
7019 /* FALLTHROUGH */
7020 case GDK_Escape:
7021 hide_cmd(t);
7022 focus_webview(t);
7024 /* cancel search */
7025 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7026 webkit_web_view_unmark_text_matches(t->wv);
7027 goto done;
7030 rv = XT_CB_PASSTHROUGH;
7031 done:
7032 return (rv);
7036 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7038 if (t == NULL) {
7039 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7040 return (XT_CB_PASSTHROUGH);
7042 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7044 hide_cmd(t);
7045 hide_oops(t);
7047 if (show_url == 0 || t->focus_wv)
7048 focus_webview(t);
7049 else
7050 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7052 return (XT_CB_PASSTHROUGH);
7055 void
7056 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7058 char *s;
7059 const gchar *c = gtk_entry_get_text(entry);
7061 if (t == NULL) {
7062 show_oops(NULL, "cmd_activate_cb invalid parameters");
7063 return;
7066 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7068 hide_cmd(t);
7070 /* sanity */
7071 if (c == NULL)
7072 goto done;
7073 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7074 goto done;
7075 if (strlen(c) < 2)
7076 goto done;
7077 s = (char *)&c[1];
7079 if (c[0] == '/' || c[0] == '?') {
7080 if (t->search_text) {
7081 g_free(t->search_text);
7082 t->search_text = NULL;
7085 t->search_text = g_strdup(s);
7086 if (global_search)
7087 g_free(global_search);
7088 global_search = g_strdup(s);
7089 t->search_forward = c[0] == '/';
7091 goto done;
7094 cmd_execute(t, s);
7096 done:
7097 return;
7100 void
7101 backward_cb(GtkWidget *w, struct tab *t)
7103 struct karg a;
7105 if (t == NULL) {
7106 show_oops(NULL, "backward_cb invalid parameters");
7107 return;
7110 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7112 a.i = XT_NAV_BACK;
7113 navaction(t, &a);
7116 void
7117 forward_cb(GtkWidget *w, struct tab *t)
7119 struct karg a;
7121 if (t == NULL) {
7122 show_oops(NULL, "forward_cb invalid parameters");
7123 return;
7126 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7128 a.i = XT_NAV_FORWARD;
7129 navaction(t, &a);
7132 void
7133 home_cb(GtkWidget *w, struct tab *t)
7135 if (t == NULL) {
7136 show_oops(NULL, "home_cb invalid parameters");
7137 return;
7140 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7142 load_uri(t, home);
7145 void
7146 stop_cb(GtkWidget *w, struct tab *t)
7148 WebKitWebFrame *frame;
7150 if (t == NULL) {
7151 show_oops(NULL, "stop_cb invalid parameters");
7152 return;
7155 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7157 frame = webkit_web_view_get_main_frame(t->wv);
7158 if (frame == NULL) {
7159 show_oops(t, "stop_cb: no frame");
7160 return;
7163 webkit_web_frame_stop_loading(frame);
7164 abort_favicon_download(t);
7167 void
7168 setup_webkit(struct tab *t)
7170 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7171 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7172 FALSE, (char *)NULL);
7173 else
7174 warnx("webkit does not have \"enable-dns-prefetching\" property");
7175 g_object_set(G_OBJECT(t->settings),
7176 "user-agent", t->user_agent, (char *)NULL);
7177 g_object_set(G_OBJECT(t->settings),
7178 "enable-scripts", enable_scripts, (char *)NULL);
7179 g_object_set(G_OBJECT(t->settings),
7180 "enable-plugins", enable_plugins, (char *)NULL);
7181 g_object_set(G_OBJECT(t->settings),
7182 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7183 g_object_set(G_OBJECT(t->settings),
7184 "enable-html5-database", FALSE, (char *)NULL);
7185 g_object_set(G_OBJECT(t->settings),
7186 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7187 g_object_set(G_OBJECT(t->settings),
7188 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7189 g_object_set(G_OBJECT(t->settings),
7190 "spell_checking_languages", spell_check_languages, (char *)NULL);
7191 g_object_set(G_OBJECT(t->wv),
7192 "full-content-zoom", TRUE, (char *)NULL);
7193 adjustfont_webkit(t, XT_FONT_SET);
7195 webkit_web_view_set_settings(t->wv, t->settings);
7198 GtkWidget *
7199 create_browser(struct tab *t)
7201 GtkWidget *w;
7202 gchar *strval;
7204 if (t == NULL) {
7205 show_oops(NULL, "create_browser invalid parameters");
7206 return (NULL);
7209 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7210 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7211 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7212 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7214 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7215 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7216 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7218 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7219 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7221 /* set defaults */
7222 t->settings = webkit_web_settings_new();
7224 if (user_agent == NULL) {
7225 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7226 (char *)NULL);
7227 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7228 g_free(strval);
7229 } else
7230 t->user_agent = g_strdup(user_agent);
7232 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7234 setup_webkit(t);
7236 return (w);
7239 GtkWidget *
7240 create_window(void)
7242 GtkWidget *w;
7244 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7245 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7246 gtk_widget_set_name(w, "xxxterm");
7247 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7248 g_signal_connect(G_OBJECT(w), "delete_event",
7249 G_CALLBACK (gtk_main_quit), NULL);
7251 return (w);
7254 GtkWidget *
7255 create_kiosk_toolbar(struct tab *t)
7257 GtkWidget *toolbar = NULL, *b;
7259 b = gtk_hbox_new(FALSE, 0);
7260 toolbar = b;
7261 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7263 /* backward button */
7264 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7265 gtk_widget_set_sensitive(t->backward, FALSE);
7266 g_signal_connect(G_OBJECT(t->backward), "clicked",
7267 G_CALLBACK(backward_cb), t);
7268 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7270 /* forward button */
7271 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7272 gtk_widget_set_sensitive(t->forward, FALSE);
7273 g_signal_connect(G_OBJECT(t->forward), "clicked",
7274 G_CALLBACK(forward_cb), t);
7275 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7277 /* home button */
7278 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7279 gtk_widget_set_sensitive(t->gohome, true);
7280 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7281 G_CALLBACK(home_cb), t);
7282 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7284 /* create widgets but don't use them */
7285 t->uri_entry = gtk_entry_new();
7286 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7287 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7288 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7290 return (toolbar);
7293 GtkWidget *
7294 create_toolbar(struct tab *t)
7296 GtkWidget *toolbar = NULL, *b, *eb1;
7298 b = gtk_hbox_new(FALSE, 0);
7299 toolbar = b;
7300 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7302 if (fancy_bar) {
7303 /* backward button */
7304 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7305 gtk_widget_set_sensitive(t->backward, FALSE);
7306 g_signal_connect(G_OBJECT(t->backward), "clicked",
7307 G_CALLBACK(backward_cb), t);
7308 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7310 /* forward button */
7311 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7312 gtk_widget_set_sensitive(t->forward, FALSE);
7313 g_signal_connect(G_OBJECT(t->forward), "clicked",
7314 G_CALLBACK(forward_cb), t);
7315 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7316 FALSE, 0);
7318 /* stop button */
7319 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7320 gtk_widget_set_sensitive(t->stop, FALSE);
7321 g_signal_connect(G_OBJECT(t->stop), "clicked",
7322 G_CALLBACK(stop_cb), t);
7323 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7324 FALSE, 0);
7326 /* JS button */
7327 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7328 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7329 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7330 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7331 G_CALLBACK(js_toggle_cb), t);
7332 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7335 t->uri_entry = gtk_entry_new();
7336 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7337 G_CALLBACK(activate_uri_entry_cb), t);
7338 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7339 G_CALLBACK(entry_key_cb), t);
7340 completion_add(t);
7341 eb1 = gtk_hbox_new(FALSE, 0);
7342 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7343 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7344 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7346 /* search entry */
7347 if (fancy_bar && search_string) {
7348 GtkWidget *eb2;
7349 t->search_entry = gtk_entry_new();
7350 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7351 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7352 G_CALLBACK(activate_search_entry_cb), t);
7353 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7354 G_CALLBACK(entry_key_cb), t);
7355 gtk_widget_set_size_request(t->search_entry, -1, -1);
7356 eb2 = gtk_hbox_new(FALSE, 0);
7357 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7358 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7360 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7362 return (toolbar);
7365 GtkWidget *
7366 create_buffers(struct tab *t)
7368 GtkCellRenderer *renderer;
7369 GtkWidget *view;
7371 view = gtk_tree_view_new();
7373 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7375 renderer = gtk_cell_renderer_text_new();
7376 gtk_tree_view_insert_column_with_attributes
7377 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7379 renderer = gtk_cell_renderer_text_new();
7380 gtk_tree_view_insert_column_with_attributes
7381 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE, NULL);
7383 gtk_tree_view_set_model
7384 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7386 return view;
7389 void
7390 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7391 GtkTreeViewColumn *col, struct tab *t)
7393 GtkTreeIter iter;
7394 guint id;
7396 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7398 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path)) {
7399 gtk_tree_model_get
7400 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7401 set_current_tab(id - 1);
7404 hide_buffers(t);
7407 /* after tab reordering/creation/removal */
7408 void
7409 recalc_tabs(void)
7411 struct tab *t;
7412 int maxid = 0;
7413 int curid = 0;
7415 TAILQ_FOREACH(t, &tabs, entry) {
7416 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7417 if (t->tab_id > maxid)
7418 maxid = t->tab_id;
7420 gtk_widget_show(t->tab_elems.sep);
7423 curid = gtk_notebook_get_current_page(notebook);
7424 TAILQ_FOREACH(t, &tabs, entry) {
7425 if (t->tab_id == maxid) {
7426 gtk_widget_hide(t->tab_elems.sep);
7427 break;
7432 /* after active tab change */
7433 void
7434 recolor_compact_tabs(void)
7436 struct tab *t;
7437 int curid = 0;
7438 GdkColor color;
7440 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7441 TAILQ_FOREACH(t, &tabs, entry)
7442 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7444 curid = gtk_notebook_get_current_page(notebook);
7445 TAILQ_FOREACH(t, &tabs, entry)
7446 if (t->tab_id == curid) {
7447 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7448 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7449 break;
7453 void
7454 set_current_tab(int page_num)
7456 gtk_notebook_set_current_page(notebook, page_num);
7457 recolor_compact_tabs();
7461 undo_close_tab_save(struct tab *t)
7463 int m, n;
7464 const gchar *uri;
7465 struct undo *u1, *u2;
7466 GList *items;
7467 WebKitWebHistoryItem *item;
7469 if ((uri = get_uri(t)) == NULL)
7470 return (1);
7472 u1 = g_malloc0(sizeof(struct undo));
7473 u1->uri = g_strdup(uri);
7475 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7477 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7478 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7479 u1->back = n;
7481 /* forward history */
7482 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7484 while (items) {
7485 item = items->data;
7486 u1->history = g_list_prepend(u1->history,
7487 webkit_web_history_item_copy(item));
7488 items = g_list_next(items);
7491 /* current item */
7492 if (m) {
7493 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7494 u1->history = g_list_prepend(u1->history,
7495 webkit_web_history_item_copy(item));
7498 /* back history */
7499 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7501 while (items) {
7502 item = items->data;
7503 u1->history = g_list_prepend(u1->history,
7504 webkit_web_history_item_copy(item));
7505 items = g_list_next(items);
7508 TAILQ_INSERT_HEAD(&undos, u1, entry);
7510 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7511 u2 = TAILQ_LAST(&undos, undo_tailq);
7512 TAILQ_REMOVE(&undos, u2, entry);
7513 g_free(u2->uri);
7514 g_list_free(u2->history);
7515 g_free(u2);
7516 } else
7517 undo_count++;
7519 return (0);
7522 void
7523 delete_tab(struct tab *t)
7525 struct karg a;
7527 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7529 if (t == NULL)
7530 return;
7532 TAILQ_REMOVE(&tabs, t, entry);
7534 /* Halt all webkit activity. */
7535 abort_favicon_download(t);
7536 webkit_web_view_stop_loading(t->wv);
7538 /* Save the tab, so we can undo the close. */
7539 undo_close_tab_save(t);
7541 if (browser_mode == XT_BM_KIOSK) {
7542 gtk_widget_destroy(t->uri_entry);
7543 gtk_widget_destroy(t->stop);
7544 gtk_widget_destroy(t->js_toggle);
7547 gtk_widget_destroy(t->tab_elems.eventbox);
7548 gtk_widget_destroy(t->vbox);
7550 g_free(t->user_agent);
7551 g_free(t->stylesheet);
7552 g_free(t);
7554 if (TAILQ_EMPTY(&tabs)) {
7555 if (browser_mode == XT_BM_KIOSK)
7556 create_new_tab(home, NULL, 1, -1);
7557 else
7558 create_new_tab(NULL, NULL, 1, -1);
7561 /* recreate session */
7562 if (session_autosave) {
7563 a.s = NULL;
7564 save_tabs(t, &a);
7567 recalc_tabs();
7568 recolor_compact_tabs();
7571 void
7572 adjustfont_webkit(struct tab *t, int adjust)
7574 gfloat zoom;
7576 if (t == NULL) {
7577 show_oops(NULL, "adjustfont_webkit invalid parameters");
7578 return;
7581 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7582 if (adjust == XT_FONT_SET) {
7583 t->font_size = default_font_size;
7584 zoom = default_zoom_level;
7585 t->font_size += adjust;
7586 g_object_set(G_OBJECT(t->settings), "default-font-size",
7587 t->font_size, (char *)NULL);
7588 g_object_get(G_OBJECT(t->settings), "default-font-size",
7589 &t->font_size, (char *)NULL);
7590 } else {
7591 t->font_size += adjust;
7592 zoom += adjust/25.0;
7593 if (zoom < 0.0) {
7594 zoom = 0.04;
7597 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7598 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7601 gboolean
7602 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
7604 struct tab *t = (struct tab *) data;
7606 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
7608 switch (event->button) {
7609 case 1:
7610 set_current_tab(t->tab_id);
7611 break;
7612 case 2:
7613 delete_tab(t);
7614 break;
7617 return TRUE;
7620 gboolean
7621 page_reordered_cb(GtkWidget *nb, GtkWidget *eventbox, guint pn, gpointer data)
7623 struct tab *t = (struct tab *) data;
7625 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
7627 recalc_tabs();
7628 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, t->tab_id);
7630 return TRUE;
7633 void
7634 append_tab(struct tab *t)
7636 if (t == NULL)
7637 return;
7639 TAILQ_INSERT_TAIL(&tabs, t, entry);
7640 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7643 struct tab *
7644 create_new_tab(char *title, struct undo *u, int focus, int position)
7646 struct tab *t;
7647 int load = 1, id;
7648 GtkWidget *b, *bb;
7649 WebKitWebHistoryItem *item;
7650 GList *items;
7651 GdkColor color;
7653 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7655 if (tabless && !TAILQ_EMPTY(&tabs)) {
7656 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7657 return (NULL);
7660 t = g_malloc0(sizeof *t);
7662 if (title == NULL) {
7663 title = "(untitled)";
7664 load = 0;
7667 t->vbox = gtk_vbox_new(FALSE, 0);
7669 /* label + button for tab */
7670 b = gtk_hbox_new(FALSE, 0);
7671 t->tab_content = b;
7673 #if GTK_CHECK_VERSION(2, 20, 0)
7674 t->spinner = gtk_spinner_new();
7675 #endif
7676 t->label = gtk_label_new(title);
7677 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7678 gtk_widget_set_size_request(t->label, 100, 0);
7679 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7680 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7681 gtk_widget_set_size_request(b, 130, 0);
7683 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7684 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7685 #if GTK_CHECK_VERSION(2, 20, 0)
7686 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7687 #endif
7689 /* toolbar */
7690 if (browser_mode == XT_BM_KIOSK)
7691 t->toolbar = create_kiosk_toolbar(t);
7692 else
7693 t->toolbar = create_toolbar(t);
7695 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7697 /* browser */
7698 t->browser_win = create_browser(t);
7699 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7701 /* oops message for user feedback */
7702 t->oops = gtk_entry_new();
7703 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7704 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7705 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7706 gdk_color_parse(XT_COLOR_RED, &color);
7707 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7708 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7710 /* command entry */
7711 t->cmd = gtk_entry_new();
7712 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7713 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7714 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7715 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7717 /* status bar */
7718 t->statusbar = gtk_entry_new();
7719 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7720 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7721 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7722 gdk_color_parse(XT_COLOR_BLACK, &color);
7723 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7724 gdk_color_parse(XT_COLOR_WHITE, &color);
7725 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7726 gtk_widget_modify_font(GTK_WIDGET(t->statusbar), statusbar_font);
7727 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7729 /* buffer list */
7730 t->buffers = create_buffers(t);
7731 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
7733 /* xtp meaning is normal by default */
7734 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7736 /* set empty favicon */
7737 xt_icon_from_name(t, "text-html");
7739 /* and show it all */
7740 gtk_widget_show_all(b);
7741 gtk_widget_show_all(t->vbox);
7743 /* compact tab bar */
7744 t->tab_elems.label = gtk_label_new(title);
7745 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
7746 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
7747 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
7749 t->tab_elems.eventbox = gtk_event_box_new();
7750 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
7751 t->tab_elems.sep = gtk_vseparator_new();
7753 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
7754 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
7755 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7756 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7757 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
7758 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
7760 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE, TRUE, 0);
7761 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE, FALSE, 0);
7762 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox), t->tab_elems.box);
7764 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE, TRUE, 0);
7765 gtk_widget_show_all(t->tab_elems.eventbox);
7767 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7768 append_tab(t);
7769 else {
7770 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7771 if (id > gtk_notebook_get_n_pages(notebook))
7772 append_tab(t);
7773 else {
7774 TAILQ_INSERT_TAIL(&tabs, t, entry);
7775 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7776 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, id);
7777 recalc_tabs();
7781 #if GTK_CHECK_VERSION(2, 20, 0)
7782 /* turn spinner off if we are a new tab without uri */
7783 if (!load) {
7784 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7785 gtk_widget_hide(t->spinner);
7787 #endif
7788 /* make notebook tabs reorderable */
7789 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7791 /* compact tabs clickable */
7792 g_signal_connect(GTK_OBJECT(t->tab_elems.eventbox),
7793 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
7795 g_signal_connect(GTK_OBJECT(notebook),
7796 "page_reordered", G_CALLBACK(page_reordered_cb), t);
7798 g_object_connect(G_OBJECT(t->cmd),
7799 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7800 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7801 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7802 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7803 (char *)NULL);
7805 /* reuse wv_button_cb to hide oops */
7806 g_object_connect(G_OBJECT(t->oops),
7807 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7808 (char *)NULL);
7810 g_signal_connect(t->buffers,
7811 "row-activated", G_CALLBACK(row_activated_cb), t);
7812 g_object_connect(G_OBJECT(t->buffers),
7813 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
7815 g_object_connect(G_OBJECT(t->wv),
7816 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7817 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7818 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7819 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7820 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7821 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7822 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7823 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7824 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7825 "signal::event", G_CALLBACK(webview_event_cb), t,
7826 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7827 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7828 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7829 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7830 (char *)NULL);
7831 g_signal_connect(t->wv,
7832 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7833 g_signal_connect(t->wv,
7834 "notify::title", G_CALLBACK(notify_title_cb), t);
7836 /* hijack the unused keys as if we were the browser */
7837 g_object_connect(G_OBJECT(t->toolbar),
7838 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7839 (char *)NULL);
7841 g_signal_connect(G_OBJECT(bb), "button_press_event",
7842 G_CALLBACK(tab_close_cb), t);
7844 /* hide stuff */
7845 hide_cmd(t);
7846 hide_oops(t);
7847 hide_buffers(t);
7848 url_set_visibility();
7849 statusbar_set_visibility();
7851 if (focus) {
7852 set_current_tab(t->tab_id);
7853 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7854 t->tab_id);
7857 if (load) {
7858 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7859 load_uri(t, title);
7860 } else {
7861 if (show_url == 1)
7862 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7863 else
7864 focus_webview(t);
7867 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7868 /* restore the tab's history */
7869 if (u && u->history) {
7870 items = u->history;
7871 while (items) {
7872 item = items->data;
7873 webkit_web_back_forward_list_add_item(t->bfl, item);
7874 items = g_list_next(items);
7877 item = g_list_nth_data(u->history, u->back);
7878 if (item)
7879 webkit_web_view_go_to_back_forward_item(t->wv, item);
7881 g_list_free(items);
7882 g_list_free(u->history);
7883 } else
7884 webkit_web_back_forward_list_clear(t->bfl);
7886 recalc_tabs();
7887 recolor_compact_tabs();
7888 return (t);
7891 void
7892 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7893 gpointer *udata)
7895 struct tab *t;
7896 const gchar *uri;
7898 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7900 if (gtk_notebook_get_current_page(notebook) == -1)
7901 recalc_tabs();
7903 TAILQ_FOREACH(t, &tabs, entry) {
7904 if (t->tab_id == pn) {
7905 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7906 "%d\n", pn);
7908 uri = webkit_web_view_get_title(t->wv);
7909 if (uri == NULL)
7910 uri = XT_NAME;
7911 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7913 hide_cmd(t);
7914 hide_oops(t);
7916 if (t->focus_wv) {
7917 /* can't use focus_webview here */
7918 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7924 void
7925 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7926 gpointer *udata)
7928 recalc_tabs();
7931 void
7932 menuitem_response(struct tab *t)
7934 gtk_notebook_set_current_page(notebook, t->tab_id);
7937 gboolean
7938 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7940 GtkWidget *menu, *menu_items;
7941 GdkEventButton *bevent;
7942 const gchar *uri;
7943 struct tab *ti;
7945 if (event->type == GDK_BUTTON_PRESS) {
7946 bevent = (GdkEventButton *) event;
7947 menu = gtk_menu_new();
7949 TAILQ_FOREACH(ti, &tabs, entry) {
7950 if ((uri = get_uri(ti)) == NULL)
7951 /* XXX make sure there is something to print */
7952 /* XXX add gui pages in here to look purdy */
7953 uri = "(untitled)";
7954 menu_items = gtk_menu_item_new_with_label(uri);
7955 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7956 gtk_widget_show(menu_items);
7958 g_signal_connect_swapped((menu_items),
7959 "activate", G_CALLBACK(menuitem_response),
7960 (gpointer)ti);
7963 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7964 bevent->button, bevent->time);
7966 /* unref object so it'll free itself when popped down */
7967 #if !GTK_CHECK_VERSION(3, 0, 0)
7968 /* XXX does not need unref with gtk+3? */
7969 g_object_ref_sink(menu);
7970 g_object_unref(menu);
7971 #endif
7973 return (TRUE /* eat event */);
7976 return (FALSE /* propagate */);
7980 icon_size_map(int icon_size)
7982 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7983 icon_size > GTK_ICON_SIZE_DIALOG)
7984 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7986 return (icon_size);
7989 GtkWidget *
7990 create_button(char *name, char *stockid, int size)
7992 GtkWidget *button, *image;
7993 gchar *rcstring;
7994 int gtk_icon_size;
7996 rcstring = g_strdup_printf(
7997 "style \"%s-style\"\n"
7998 "{\n"
7999 " GtkWidget::focus-padding = 0\n"
8000 " GtkWidget::focus-line-width = 0\n"
8001 " xthickness = 0\n"
8002 " ythickness = 0\n"
8003 "}\n"
8004 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8005 gtk_rc_parse_string(rcstring);
8006 g_free(rcstring);
8007 button = gtk_button_new();
8008 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8009 gtk_icon_size = icon_size_map(size ? size : icon_size);
8011 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8012 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8013 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8014 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8015 gtk_widget_set_name(button, name);
8016 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8018 return (button);
8021 void
8022 button_set_stockid(GtkWidget *button, char *stockid)
8024 GtkWidget *image;
8026 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8027 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8028 gtk_button_set_image(GTK_BUTTON(button), image);
8031 void
8032 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8034 GtkClipboard *clipboard;
8035 gchar *p = NULL, *s = NULL;
8038 * This code is very aggressive!
8039 * It basically ensures that the primary and regular clipboard are
8040 * always set the same. This obviously messes with standard X protocol
8041 * but those clowns should have come up with something better.
8044 /* XXX make this setting? */
8045 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8046 p = gtk_clipboard_wait_for_text(primary);
8047 if (p == NULL) {
8048 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
8049 p = gtk_clipboard_wait_for_text(clipboard);
8050 if (p)
8051 gtk_clipboard_set_text(primary, p, -1);
8052 } else {
8053 DNPRINTF(XT_D_CLIP, "primary got selection\n");
8054 s = gtk_clipboard_wait_for_text(clipboard);
8055 if (s) {
8057 * if s and p are the same the string was set by
8058 * clipb_clipboard_cb so do nothing in that case
8059 * to prevent endless loop
8061 if (!strcmp(s, p))
8062 goto done;
8064 gtk_clipboard_set_text(clipboard, p, -1);
8066 done:
8067 if (p)
8068 g_free(p);
8069 if (s)
8070 g_free(s);
8073 void
8074 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
8076 GtkClipboard *primary;
8077 gchar *p = NULL, *s = NULL;
8079 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
8081 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8082 p = gtk_clipboard_wait_for_text(clipboard);
8083 if (p) {
8084 s = gtk_clipboard_wait_for_text(primary);
8085 if (s) {
8087 * if s and p are the same the string was set by
8088 * clipb_primary_cb so do nothing in that case
8089 * to prevent endless loop and deselection of text
8091 if (!strcmp(s, p))
8092 goto done;
8094 gtk_clipboard_set_text(primary, p, -1);
8096 done:
8097 if (p)
8098 g_free(p);
8099 if (s)
8100 g_free(s);
8103 void
8104 create_canvas(void)
8106 GtkWidget *vbox;
8107 GList *l = NULL;
8108 GdkPixbuf *pb;
8109 char file[PATH_MAX];
8110 int i;
8112 vbox = gtk_vbox_new(FALSE, 0);
8113 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8114 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8115 #if !GTK_CHECK_VERSION(3, 0, 0)
8116 /* XXX seems to be needed with gtk+2 */
8117 gtk_notebook_set_tab_hborder(notebook, 0);
8118 gtk_notebook_set_tab_vborder(notebook, 0);
8119 #endif
8120 gtk_notebook_set_scrollable(notebook, TRUE);
8121 gtk_notebook_set_show_border(notebook, FALSE);
8122 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8124 abtn = gtk_button_new();
8125 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8126 gtk_widget_set_size_request(arrow, -1, -1);
8127 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8128 gtk_widget_set_size_request(abtn, -1, 20);
8130 #if GTK_CHECK_VERSION(2, 20, 0)
8131 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8132 #endif
8133 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8135 /* compact tab bar */
8136 tab_bar = gtk_hbox_new(TRUE, 0);
8138 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8139 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8140 gtk_widget_set_size_request(vbox, -1, -1);
8142 g_object_connect(G_OBJECT(notebook),
8143 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8144 (char *)NULL);
8145 g_object_connect(G_OBJECT(notebook),
8146 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
8147 (char *)NULL);
8148 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8149 G_CALLBACK(arrow_cb), NULL);
8151 main_window = create_window();
8152 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8153 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
8155 /* icons */
8156 for (i = 0; i < LENGTH(icons); i++) {
8157 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8158 pb = gdk_pixbuf_new_from_file(file, NULL);
8159 l = g_list_append(l, pb);
8161 gtk_window_set_default_icon_list(l);
8163 /* clipboard */
8164 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8165 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8166 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
8167 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
8169 gtk_widget_show_all(abtn);
8170 gtk_widget_show_all(main_window);
8171 notebook_tab_set_visibility();
8174 void
8175 set_hook(void **hook, char *name)
8177 if (hook == NULL)
8178 errx(1, "set_hook");
8180 if (*hook == NULL) {
8181 *hook = dlsym(RTLD_NEXT, name);
8182 if (*hook == NULL)
8183 errx(1, "can't hook %s", name);
8187 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8188 gboolean
8189 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8191 g_return_val_if_fail(cookie1, FALSE);
8192 g_return_val_if_fail(cookie2, FALSE);
8194 return (!strcmp (cookie1->name, cookie2->name) &&
8195 !strcmp (cookie1->value, cookie2->value) &&
8196 !strcmp (cookie1->path, cookie2->path) &&
8197 !strcmp (cookie1->domain, cookie2->domain));
8200 void
8201 transfer_cookies(void)
8203 GSList *cf;
8204 SoupCookie *sc, *pc;
8206 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8208 for (;cf; cf = cf->next) {
8209 pc = cf->data;
8210 sc = soup_cookie_copy(pc);
8211 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8214 soup_cookies_free(cf);
8217 void
8218 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8220 GSList *cf;
8221 SoupCookie *ci;
8223 print_cookie("soup_cookie_jar_delete_cookie", c);
8225 if (cookies_enabled == 0)
8226 return;
8228 if (jar == NULL || c == NULL)
8229 return;
8231 /* find and remove from persistent jar */
8232 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8234 for (;cf; cf = cf->next) {
8235 ci = cf->data;
8236 if (soup_cookie_equal(ci, c)) {
8237 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8238 break;
8242 soup_cookies_free(cf);
8244 /* delete from session jar */
8245 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8248 void
8249 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8251 struct domain *d = NULL;
8252 SoupCookie *c;
8253 FILE *r_cookie_f;
8255 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8256 jar, p_cookiejar, s_cookiejar);
8258 if (cookies_enabled == 0)
8259 return;
8261 /* see if we are up and running */
8262 if (p_cookiejar == NULL) {
8263 _soup_cookie_jar_add_cookie(jar, cookie);
8264 return;
8266 /* disallow p_cookiejar adds, shouldn't happen */
8267 if (jar == p_cookiejar)
8268 return;
8270 /* sanity */
8271 if (jar == NULL || cookie == NULL)
8272 return;
8274 if (enable_cookie_whitelist &&
8275 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8276 blocked_cookies++;
8277 DNPRINTF(XT_D_COOKIE,
8278 "soup_cookie_jar_add_cookie: reject %s\n",
8279 cookie->domain);
8280 if (save_rejected_cookies) {
8281 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8282 show_oops(NULL, "can't open reject cookie file");
8283 return;
8285 fseek(r_cookie_f, 0, SEEK_END);
8286 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8287 cookie->http_only ? "#HttpOnly_" : "",
8288 cookie->domain,
8289 *cookie->domain == '.' ? "TRUE" : "FALSE",
8290 cookie->path,
8291 cookie->secure ? "TRUE" : "FALSE",
8292 cookie->expires ?
8293 (gulong)soup_date_to_time_t(cookie->expires) :
8295 cookie->name,
8296 cookie->value);
8297 fflush(r_cookie_f);
8298 fclose(r_cookie_f);
8300 if (!allow_volatile_cookies)
8301 return;
8304 if (cookie->expires == NULL && session_timeout) {
8305 soup_cookie_set_expires(cookie,
8306 soup_date_new_from_now(session_timeout));
8307 print_cookie("modified add cookie", cookie);
8310 /* see if we are white listed for persistence */
8311 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8312 /* add to persistent jar */
8313 c = soup_cookie_copy(cookie);
8314 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8315 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8318 /* add to session jar */
8319 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8320 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8323 void
8324 setup_cookies(void)
8326 char file[PATH_MAX];
8328 set_hook((void *)&_soup_cookie_jar_add_cookie,
8329 "soup_cookie_jar_add_cookie");
8330 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8331 "soup_cookie_jar_delete_cookie");
8333 if (cookies_enabled == 0)
8334 return;
8337 * the following code is intricate due to overriding several libsoup
8338 * functions.
8339 * do not alter order of these operations.
8342 /* rejected cookies */
8343 if (save_rejected_cookies)
8344 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8346 /* persistent cookies */
8347 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8348 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8350 /* session cookies */
8351 s_cookiejar = soup_cookie_jar_new();
8352 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8353 cookie_policy, (void *)NULL);
8354 transfer_cookies();
8356 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8359 void
8360 setup_proxy(char *uri)
8362 if (proxy_uri) {
8363 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8364 soup_uri_free(proxy_uri);
8365 proxy_uri = NULL;
8367 if (http_proxy) {
8368 if (http_proxy != uri) {
8369 g_free(http_proxy);
8370 http_proxy = NULL;
8374 if (uri) {
8375 http_proxy = g_strdup(uri);
8376 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8377 proxy_uri = soup_uri_new(http_proxy);
8378 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8383 send_cmd_to_socket(char *cmd)
8385 int s, len, rv = 1;
8386 struct sockaddr_un sa;
8388 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8389 warnx("%s: socket", __func__);
8390 return (rv);
8393 sa.sun_family = AF_UNIX;
8394 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8395 work_dir, XT_SOCKET_FILE);
8396 len = SUN_LEN(&sa);
8398 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8399 warnx("%s: connect", __func__);
8400 goto done;
8403 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8404 warnx("%s: send", __func__);
8405 goto done;
8408 rv = 0;
8409 done:
8410 close(s);
8411 return (rv);
8414 gboolean
8415 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8417 int s, n;
8418 char str[XT_MAX_URL_LENGTH];
8419 socklen_t t = sizeof(struct sockaddr_un);
8420 struct sockaddr_un sa;
8421 struct passwd *p;
8422 uid_t uid;
8423 gid_t gid;
8424 struct tab *tt;
8425 gint fd = g_io_channel_unix_get_fd(source);
8427 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8428 warn("accept");
8429 return (FALSE);
8432 if (getpeereid(s, &uid, &gid) == -1) {
8433 warn("getpeereid");
8434 return (FALSE);
8436 if (uid != getuid() || gid != getgid()) {
8437 warnx("unauthorized user");
8438 return (FALSE);
8441 p = getpwuid(uid);
8442 if (p == NULL) {
8443 warnx("not a valid user");
8444 return (FALSE);
8447 n = recv(s, str, sizeof(str), 0);
8448 if (n <= 0)
8449 return (FALSE);
8451 tt = TAILQ_LAST(&tabs, tab_list);
8452 cmd_execute(tt, str);
8453 return (TRUE);
8457 is_running(void)
8459 int s, len, rv = 1;
8460 struct sockaddr_un sa;
8462 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8463 warn("is_running: socket");
8464 return (-1);
8467 sa.sun_family = AF_UNIX;
8468 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8469 work_dir, XT_SOCKET_FILE);
8470 len = SUN_LEN(&sa);
8472 /* connect to see if there is a listener */
8473 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8474 rv = 0; /* not running */
8475 else
8476 rv = 1; /* already running */
8478 close(s);
8480 return (rv);
8484 build_socket(void)
8486 int s, len;
8487 struct sockaddr_un sa;
8489 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8490 warn("build_socket: socket");
8491 return (-1);
8494 sa.sun_family = AF_UNIX;
8495 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8496 work_dir, XT_SOCKET_FILE);
8497 len = SUN_LEN(&sa);
8499 /* connect to see if there is a listener */
8500 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8501 /* no listener so we will */
8502 unlink(sa.sun_path);
8504 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8505 warn("build_socket: bind");
8506 goto done;
8509 if (listen(s, 1) == -1) {
8510 warn("build_socket: listen");
8511 goto done;
8514 return (s);
8517 done:
8518 close(s);
8519 return (-1);
8522 gboolean
8523 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8524 GtkTreeIter *iter, struct tab *t)
8526 gchar *value;
8528 gtk_tree_model_get(model, iter, 0, &value, -1);
8529 load_uri(t, value);
8530 g_free(value);
8532 return (FALSE);
8535 gboolean
8536 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8537 GtkTreeIter *iter, struct tab *t)
8539 gchar *value;
8541 gtk_tree_model_get(model, iter, 0, &value, -1);
8542 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8543 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8544 g_free(value);
8546 return (TRUE);
8549 void
8550 completion_add_uri(const gchar *uri)
8552 GtkTreeIter iter;
8554 /* add uri to list_store */
8555 gtk_list_store_append(completion_model, &iter);
8556 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8559 gboolean
8560 completion_match(GtkEntryCompletion *completion, const gchar *key,
8561 GtkTreeIter *iter, gpointer user_data)
8563 gchar *value;
8564 gboolean match = FALSE;
8566 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8567 -1);
8569 if (value == NULL)
8570 return FALSE;
8572 match = match_uri(value, key);
8574 g_free(value);
8575 return (match);
8578 void
8579 completion_add(struct tab *t)
8581 /* enable completion for tab */
8582 t->completion = gtk_entry_completion_new();
8583 gtk_entry_completion_set_text_column(t->completion, 0);
8584 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8585 gtk_entry_completion_set_model(t->completion,
8586 GTK_TREE_MODEL(completion_model));
8587 gtk_entry_completion_set_match_func(t->completion, completion_match,
8588 NULL, NULL);
8589 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8590 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8591 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8592 G_CALLBACK(completion_select_cb), t);
8593 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8594 G_CALLBACK(completion_hover_cb), t);
8597 void
8598 xxx_dir(char *dir)
8600 struct stat sb;
8602 if (stat(dir, &sb)) {
8603 if (mkdir(dir, S_IRWXU) == -1)
8604 err(1, "mkdir %s", dir);
8605 if (stat(dir, &sb))
8606 err(1, "stat %s", dir);
8608 if (S_ISDIR(sb.st_mode) == 0)
8609 errx(1, "%s not a dir", dir);
8610 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8611 warnx("fixing invalid permissions on %s", dir);
8612 if (chmod(dir, S_IRWXU) == -1)
8613 err(1, "chmod %s", dir);
8617 void
8618 usage(void)
8620 fprintf(stderr,
8621 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8622 exit(0);
8627 main(int argc, char *argv[])
8629 struct stat sb;
8630 int c, s, optn = 0, opte = 0, focus = 1;
8631 char conf[PATH_MAX] = { '\0' };
8632 char file[PATH_MAX];
8633 char *env_proxy = NULL;
8634 FILE *f = NULL;
8635 struct karg a;
8636 struct sigaction sact;
8637 GIOChannel *channel;
8638 struct rlimit rlp;
8640 start_argv = argv;
8642 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8644 /* fiddle with ulimits */
8645 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8646 warn("getrlimit");
8647 else {
8648 /* just use them all */
8649 rlp.rlim_cur = rlp.rlim_max;
8650 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8651 warn("setrlimit");
8652 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8653 warn("getrlimit");
8654 else if (rlp.rlim_cur <= 256)
8655 warnx("%s requires at least 256 file descriptors",
8656 __progname);
8659 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8660 switch (c) {
8661 case 'S':
8662 show_url = 0;
8663 break;
8664 case 'T':
8665 show_tabs = 0;
8666 break;
8667 case 'V':
8668 errx(0 , "Version: %s", version);
8669 break;
8670 case 'f':
8671 strlcpy(conf, optarg, sizeof(conf));
8672 break;
8673 case 's':
8674 strlcpy(named_session, optarg, sizeof(named_session));
8675 break;
8676 case 't':
8677 tabless = 1;
8678 break;
8679 case 'n':
8680 optn = 1;
8681 break;
8682 case 'e':
8683 opte = 1;
8684 break;
8685 default:
8686 usage();
8687 /* NOTREACHED */
8690 argc -= optind;
8691 argv += optind;
8693 RB_INIT(&hl);
8694 RB_INIT(&js_wl);
8695 RB_INIT(&downloads);
8697 TAILQ_INIT(&tabs);
8698 TAILQ_INIT(&mtl);
8699 TAILQ_INIT(&aliases);
8700 TAILQ_INIT(&undos);
8701 TAILQ_INIT(&kbl);
8703 init_keybindings();
8705 gnutls_global_init();
8707 /* generate session keys for xtp pages */
8708 generate_xtp_session_key(&dl_session_key);
8709 generate_xtp_session_key(&hl_session_key);
8710 generate_xtp_session_key(&cl_session_key);
8711 generate_xtp_session_key(&fl_session_key);
8713 /* prepare gtk */
8714 gtk_init(&argc, &argv);
8715 if (!g_thread_supported())
8716 g_thread_init(NULL);
8718 /* signals */
8719 bzero(&sact, sizeof(sact));
8720 sigemptyset(&sact.sa_mask);
8721 sact.sa_handler = sigchild;
8722 sact.sa_flags = SA_NOCLDSTOP;
8723 sigaction(SIGCHLD, &sact, NULL);
8725 /* set download dir */
8726 pwd = getpwuid(getuid());
8727 if (pwd == NULL)
8728 errx(1, "invalid user %d", getuid());
8729 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8731 /* set default string settings */
8732 home = g_strdup("https://www.cyphertite.com");
8733 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8734 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8735 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
8736 cmd_font_name = g_strdup("monospace normal 9");
8737 statusbar_font_name = g_strdup("monospace normal 9");
8740 /* read config file */
8741 if (strlen(conf) == 0)
8742 snprintf(conf, sizeof conf, "%s/.%s",
8743 pwd->pw_dir, XT_CONF_FILE);
8744 config_parse(conf, 0);
8746 /* init fonts */
8747 cmd_font = pango_font_description_from_string(cmd_font_name);
8748 statusbar_font = pango_font_description_from_string(statusbar_font_name);
8750 /* working directory */
8751 if (strlen(work_dir) == 0)
8752 snprintf(work_dir, sizeof work_dir, "%s/%s",
8753 pwd->pw_dir, XT_DIR);
8754 xxx_dir(work_dir);
8756 /* icon cache dir */
8757 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8758 xxx_dir(cache_dir);
8760 /* certs dir */
8761 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8762 xxx_dir(certs_dir);
8764 /* sessions dir */
8765 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8766 work_dir, XT_SESSIONS_DIR);
8767 xxx_dir(sessions_dir);
8769 /* runtime settings that can override config file */
8770 if (runtime_settings[0] != '\0')
8771 config_parse(runtime_settings, 1);
8773 /* download dir */
8774 if (!strcmp(download_dir, pwd->pw_dir))
8775 strlcat(download_dir, "/downloads", sizeof download_dir);
8776 xxx_dir(download_dir);
8778 /* favorites file */
8779 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8780 if (stat(file, &sb)) {
8781 warnx("favorites file doesn't exist, creating it");
8782 if ((f = fopen(file, "w")) == NULL)
8783 err(1, "favorites");
8784 fclose(f);
8787 /* cookies */
8788 session = webkit_get_default_session();
8789 setup_cookies();
8791 /* certs */
8792 if (ssl_ca_file) {
8793 if (stat(ssl_ca_file, &sb)) {
8794 warnx("no CA file: %s", ssl_ca_file);
8795 g_free(ssl_ca_file);
8796 ssl_ca_file = NULL;
8797 } else
8798 g_object_set(session,
8799 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8800 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8801 (void *)NULL);
8804 /* proxy */
8805 env_proxy = getenv("http_proxy");
8806 if (env_proxy)
8807 setup_proxy(env_proxy);
8808 else
8809 setup_proxy(http_proxy);
8811 if (opte) {
8812 send_cmd_to_socket(argv[0]);
8813 exit(0);
8816 /* set some connection parameters */
8817 /* XXX webkit 1.4.X overwrites these values! */
8818 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
8819 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8820 g_object_set(session, "max-conns-per-host", max_host_connections,
8821 (char *)NULL);
8823 /* see if there is already an xxxterm running */
8824 if (single_instance && is_running()) {
8825 optn = 1;
8826 warnx("already running");
8829 char *cmd = NULL;
8830 if (optn) {
8831 while (argc) {
8832 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8833 send_cmd_to_socket(cmd);
8834 if (cmd)
8835 g_free(cmd);
8837 argc--;
8838 argv++;
8840 exit(0);
8843 /* uri completion */
8844 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8846 /* buffers */
8847 buffers_store = gtk_list_store_new
8848 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
8850 /* go graphical */
8851 create_canvas();
8852 notebook_tab_set_visibility();
8854 if (save_global_history)
8855 restore_global_history();
8857 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8858 restore_saved_tabs();
8859 else {
8860 a.s = named_session;
8861 a.i = XT_SES_DONOTHING;
8862 open_tabs(NULL, &a);
8865 while (argc) {
8866 create_new_tab(argv[0], NULL, focus, -1);
8867 focus = 0;
8869 argc--;
8870 argv++;
8873 if (TAILQ_EMPTY(&tabs))
8874 create_new_tab(home, NULL, 1, -1);
8876 if (enable_socket)
8877 if ((s = build_socket()) != -1) {
8878 channel = g_io_channel_unix_new(s);
8879 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8882 gtk_main();
8884 gnutls_global_deinit();
8886 return (0);