add more settable fonts
[xxxterm.git] / xxxterm.c
blobcec3c964dc83c454dd9e428155473929584b4fe8
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <err.h>
35 #include <pwd.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <pthread.h>
39 #include <dlfcn.h>
40 #include <errno.h>
41 #include <signal.h>
42 #include <libgen.h>
43 #include <ctype.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #if defined(__linux__)
48 #include "linux/util.h"
49 #include "linux/tree.h"
50 #elif defined(__FreeBSD__)
51 #include <libutil.h>
52 #include "freebsd/util.h"
53 #include <sys/tree.h>
54 #else /* OpenBSD */
55 #include <util.h>
56 #include <sys/tree.h>
57 #endif
58 #include <sys/queue.h>
59 #include <sys/stat.h>
60 #include <sys/socket.h>
61 #include <sys/un.h>
62 #include <sys/time.h>
63 #include <sys/resource.h>
65 #include <gtk/gtk.h>
66 #include <gdk/gdkkeysyms.h>
68 #if GTK_CHECK_VERSION(3,0,0)
69 /* we still use GDK_* instead of GDK_KEY_* */
70 #include <gdk/gdkkeysyms-compat.h>
71 #endif
73 #include <webkit/webkit.h>
74 #include <libsoup/soup.h>
75 #include <gnutls/gnutls.h>
76 #include <JavaScriptCore/JavaScript.h>
77 #include <gnutls/x509.h>
79 #include "javascript.h"
82 javascript.h borrowed from vimprobable2 under the following license:
84 Copyright (c) 2009 Leon Winter
85 Copyright (c) 2009 Hannes Schueller
86 Copyright (c) 2009 Matto Fransen
88 Permission is hereby granted, free of charge, to any person obtaining a copy
89 of this software and associated documentation files (the "Software"), to deal
90 in the Software without restriction, including without limitation the rights
91 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
92 copies of the Software, and to permit persons to whom the Software is
93 furnished to do so, subject to the following conditions:
95 The above copyright notice and this permission notice shall be included in
96 all copies or substantial portions of the Software.
98 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
99 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
100 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
101 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
102 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
103 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
104 THE SOFTWARE.
107 static char *version = "$xxxterm$";
109 /* hooked functions */
110 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
111 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
112 SoupCookie *);
114 /*#define XT_DEBUG*/
115 #ifdef XT_DEBUG
116 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
117 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
118 #define XT_D_MOVE 0x0001
119 #define XT_D_KEY 0x0002
120 #define XT_D_TAB 0x0004
121 #define XT_D_URL 0x0008
122 #define XT_D_CMD 0x0010
123 #define XT_D_NAV 0x0020
124 #define XT_D_DOWNLOAD 0x0040
125 #define XT_D_CONFIG 0x0080
126 #define XT_D_JS 0x0100
127 #define XT_D_FAVORITE 0x0200
128 #define XT_D_PRINTING 0x0400
129 #define XT_D_COOKIE 0x0800
130 #define XT_D_KEYBINDING 0x1000
131 #define XT_D_CLIP 0x2000
132 u_int32_t swm_debug = 0
133 | XT_D_MOVE
134 | XT_D_KEY
135 | XT_D_TAB
136 | XT_D_URL
137 | XT_D_CMD
138 | XT_D_NAV
139 | XT_D_DOWNLOAD
140 | XT_D_CONFIG
141 | XT_D_JS
142 | XT_D_FAVORITE
143 | XT_D_PRINTING
144 | XT_D_COOKIE
145 | XT_D_KEYBINDING
146 | XT_D_CLIP
148 #else
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
151 #endif
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
159 ~(GDK_BUTTON5_MASK))
161 char *icons[] = {
162 "xxxtermicon16.png",
163 "xxxtermicon32.png",
164 "xxxtermicon48.png",
165 "xxxtermicon64.png",
166 "xxxtermicon128.png"
169 struct tab {
170 TAILQ_ENTRY(tab) entry;
171 GtkWidget *vbox;
172 GtkWidget *tab_content;
173 struct {
174 GtkWidget *label;
175 GtkWidget *eventbox;
176 GtkWidget *box;
177 GtkWidget *sep;
178 } tab_elems;
179 GtkWidget *label;
180 GtkWidget *spinner;
181 GtkWidget *uri_entry;
182 GtkWidget *search_entry;
183 GtkWidget *toolbar;
184 GtkWidget *browser_win;
185 GtkWidget *statusbar_box;
186 struct {
187 GtkWidget *statusbar;
188 GtkWidget *position;
189 } sbe;
190 GtkWidget *cmd;
191 GtkWidget *buffers;
192 GtkWidget *oops;
193 GtkWidget *backward;
194 GtkWidget *forward;
195 GtkWidget *stop;
196 GtkWidget *gohome;
197 GtkWidget *js_toggle;
198 GtkEntryCompletion *completion;
199 guint tab_id;
200 WebKitWebView *wv;
202 WebKitWebHistoryItem *item;
203 WebKitWebBackForwardList *bfl;
205 /* favicon */
206 WebKitNetworkRequest *icon_request;
207 WebKitDownload *icon_download;
208 gchar *icon_dest_uri;
210 /* adjustments for browser */
211 GtkScrollbar *sb_h;
212 GtkScrollbar *sb_v;
213 GtkAdjustment *adjust_h;
214 GtkAdjustment *adjust_v;
216 /* flags */
217 int focus_wv;
218 int ctrl_click;
219 gchar *status;
220 int xtp_meaning; /* identifies dls/favorites */
222 /* hints */
223 int hints_on;
224 int hint_mode;
225 #define XT_HINT_NONE (0)
226 #define XT_HINT_NUMERICAL (1)
227 #define XT_HINT_ALPHANUM (2)
228 char hint_buf[128];
229 char hint_num[128];
231 /* custom stylesheet */
232 int styled;
233 char *stylesheet;
235 /* search */
236 char *search_text;
237 int search_forward;
239 /* settings */
240 WebKitWebSettings *settings;
241 int font_size;
242 gchar *user_agent;
244 TAILQ_HEAD(tab_list, tab);
246 struct history {
247 RB_ENTRY(history) entry;
248 const gchar *uri;
249 const gchar *title;
251 RB_HEAD(history_list, history);
253 struct download {
254 RB_ENTRY(download) entry;
255 int id;
256 WebKitDownload *download;
257 struct tab *tab;
259 RB_HEAD(download_list, download);
261 struct domain {
262 RB_ENTRY(domain) entry;
263 gchar *d;
264 int handy; /* app use */
266 RB_HEAD(domain_list, domain);
268 struct undo {
269 TAILQ_ENTRY(undo) entry;
270 gchar *uri;
271 GList *history;
272 int back; /* Keeps track of how many back
273 * history items there are. */
275 TAILQ_HEAD(undo_tailq, undo);
277 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
278 int next_download_id = 1;
280 struct karg {
281 int i;
282 char *s;
283 int p;
286 /* defines */
287 #define XT_NAME ("XXXTerm")
288 #define XT_DIR (".xxxterm")
289 #define XT_CACHE_DIR ("cache")
290 #define XT_CERT_DIR ("certs/")
291 #define XT_SESSIONS_DIR ("sessions/")
292 #define XT_CONF_FILE ("xxxterm.conf")
293 #define XT_FAVS_FILE ("favorites")
294 #define XT_SAVED_TABS_FILE ("main_session")
295 #define XT_RESTART_TABS_FILE ("restart_tabs")
296 #define XT_SOCKET_FILE ("socket")
297 #define XT_HISTORY_FILE ("history")
298 #define XT_REJECT_FILE ("rejected.txt")
299 #define XT_COOKIE_FILE ("cookies.txt")
300 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
301 #define XT_CB_HANDLED (TRUE)
302 #define XT_CB_PASSTHROUGH (FALSE)
303 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
304 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
305 #define XT_DLMAN_REFRESH "10"
306 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
307 "td{overflow: hidden;" \
308 " padding: 2px 2px 2px 2px;" \
309 " border: 1px solid black;" \
310 " vertical-align:top;" \
311 " word-wrap: break-word}\n" \
312 "tr:hover{background: #ffff99}\n" \
313 "th{background-color: #cccccc;" \
314 " border: 1px solid black}\n" \
315 "table{width: 100%%;" \
316 " border: 1px black solid;" \
317 " border-collapse:collapse}\n" \
318 ".progress-outer{" \
319 "border: 1px solid black;" \
320 " height: 8px;" \
321 " width: 90%%}\n" \
322 ".progress-inner{float: left;" \
323 " height: 8px;" \
324 " background: green}\n" \
325 ".dlstatus{font-size: small;" \
326 " text-align: center}\n" \
327 "</style>\n"
328 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
329 #define XT_MAX_UNDO_CLOSE_TAB (32)
330 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
331 #define XT_PRINT_EXTRA_MARGIN 10
333 /* colors */
334 #define XT_COLOR_RED "#cc0000"
335 #define XT_COLOR_YELLOW "#ffff66"
336 #define XT_COLOR_BLUE "lightblue"
337 #define XT_COLOR_GREEN "#99ff66"
338 #define XT_COLOR_WHITE "white"
339 #define XT_COLOR_BLACK "black"
341 #define XT_COLOR_CT_BACKGROUND "#000000"
342 #define XT_COLOR_CT_INACTIVE "#dddddd"
343 #define XT_COLOR_CT_ACTIVE "#bbbb00"
344 #define XT_COLOR_CT_SEPARATOR "#555555"
347 * xxxterm "protocol" (xtp)
348 * We use this for managing stuff like downloads and favorites. They
349 * make magical HTML pages in memory which have xxxt:// links in order
350 * to communicate with xxxterm's internals. These links take the format:
351 * xxxt://class/session_key/action/arg
353 * Don't begin xtp class/actions as 0. atoi returns that on error.
355 * Typically we have not put addition of items in this framework, as
356 * adding items is either done via an ex-command or via a keybinding instead.
359 #define XT_XTP_STR "xxxt://"
361 /* XTP classes (xxxt://<class>) */
362 #define XT_XTP_INVALID 0 /* invalid */
363 #define XT_XTP_DL 1 /* downloads */
364 #define XT_XTP_HL 2 /* history */
365 #define XT_XTP_CL 3 /* cookies */
366 #define XT_XTP_FL 4 /* favorites */
368 /* XTP download actions */
369 #define XT_XTP_DL_LIST 1
370 #define XT_XTP_DL_CANCEL 2
371 #define XT_XTP_DL_REMOVE 3
373 /* XTP history actions */
374 #define XT_XTP_HL_LIST 1
375 #define XT_XTP_HL_REMOVE 2
377 /* XTP cookie actions */
378 #define XT_XTP_CL_LIST 1
379 #define XT_XTP_CL_REMOVE 2
381 /* XTP cookie actions */
382 #define XT_XTP_FL_LIST 1
383 #define XT_XTP_FL_REMOVE 2
385 /* actions */
386 #define XT_MOVE_INVALID (0)
387 #define XT_MOVE_DOWN (1)
388 #define XT_MOVE_UP (2)
389 #define XT_MOVE_BOTTOM (3)
390 #define XT_MOVE_TOP (4)
391 #define XT_MOVE_PAGEDOWN (5)
392 #define XT_MOVE_PAGEUP (6)
393 #define XT_MOVE_HALFDOWN (7)
394 #define XT_MOVE_HALFUP (8)
395 #define XT_MOVE_LEFT (9)
396 #define XT_MOVE_FARLEFT (10)
397 #define XT_MOVE_RIGHT (11)
398 #define XT_MOVE_FARRIGHT (12)
400 #define XT_TAB_LAST (-4)
401 #define XT_TAB_FIRST (-3)
402 #define XT_TAB_PREV (-2)
403 #define XT_TAB_NEXT (-1)
404 #define XT_TAB_INVALID (0)
405 #define XT_TAB_NEW (1)
406 #define XT_TAB_DELETE (2)
407 #define XT_TAB_DELQUIT (3)
408 #define XT_TAB_OPEN (4)
409 #define XT_TAB_UNDO_CLOSE (5)
410 #define XT_TAB_SHOW (6)
411 #define XT_TAB_HIDE (7)
412 #define XT_TAB_NEXTSTYLE (8)
414 #define XT_NAV_INVALID (0)
415 #define XT_NAV_BACK (1)
416 #define XT_NAV_FORWARD (2)
417 #define XT_NAV_RELOAD (3)
418 #define XT_NAV_RELOAD_CACHE (4)
420 #define XT_FOCUS_INVALID (0)
421 #define XT_FOCUS_URI (1)
422 #define XT_FOCUS_SEARCH (2)
424 #define XT_SEARCH_INVALID (0)
425 #define XT_SEARCH_NEXT (1)
426 #define XT_SEARCH_PREV (2)
428 #define XT_PASTE_CURRENT_TAB (0)
429 #define XT_PASTE_NEW_TAB (1)
431 #define XT_FONT_SET (0)
433 #define XT_URL_SHOW (1)
434 #define XT_URL_HIDE (2)
436 #define XT_WL_TOGGLE (1<<0)
437 #define XT_WL_ENABLE (1<<1)
438 #define XT_WL_DISABLE (1<<2)
439 #define XT_WL_FQDN (1<<3) /* default */
440 #define XT_WL_TOPLEVEL (1<<4)
441 #define XT_WL_PERSISTENT (1<<5)
442 #define XT_WL_SESSION (1<<6)
443 #define XT_WL_RELOAD (1<<7)
445 #define XT_SHOW (1<<7)
446 #define XT_DELETE (1<<8)
447 #define XT_SAVE (1<<9)
448 #define XT_OPEN (1<<10)
450 #define XT_CMD_OPEN (0)
451 #define XT_CMD_OPEN_CURRENT (1)
452 #define XT_CMD_TABNEW (2)
453 #define XT_CMD_TABNEW_CURRENT (3)
455 #define XT_STATUS_NOTHING (0)
456 #define XT_STATUS_LINK (1)
457 #define XT_STATUS_URI (2)
458 #define XT_STATUS_LOADING (3)
460 #define XT_SES_DONOTHING (0)
461 #define XT_SES_CLOSETABS (1)
463 #define XT_BM_NORMAL (0)
464 #define XT_BM_WHITELIST (1)
465 #define XT_BM_KIOSK (2)
467 #define XT_PREFIX (1<<0)
468 #define XT_USERARG (1<<1)
469 #define XT_URLARG (1<<2)
470 #define XT_INTARG (1<<3)
472 #define XT_TABS_NORMAL 0
473 #define XT_TABS_COMPACT 1
475 /* mime types */
476 struct mime_type {
477 char *mt_type;
478 char *mt_action;
479 int mt_default;
480 int mt_download;
481 TAILQ_ENTRY(mime_type) entry;
483 TAILQ_HEAD(mime_type_list, mime_type);
485 /* uri aliases */
486 struct alias {
487 char *a_name;
488 char *a_uri;
489 TAILQ_ENTRY(alias) entry;
491 TAILQ_HEAD(alias_list, alias);
493 /* settings that require restart */
494 int tabless = 0; /* allow only 1 tab */
495 int enable_socket = 0;
496 int single_instance = 0; /* only allow one xxxterm to run */
497 int fancy_bar = 1; /* fancy toolbar */
498 int browser_mode = XT_BM_NORMAL;
499 int enable_localstorage = 0;
501 /* runtime settings */
502 int show_tabs = 1; /* show tabs on notebook */
503 int tab_style = XT_TABS_NORMAL; /* tab bar style */
504 int show_url = 1; /* show url toolbar on notebook */
505 int show_statusbar = 0; /* vimperator style status bar */
506 int ctrl_click_focus = 0; /* ctrl click gets focus */
507 int cookies_enabled = 1; /* enable cookies */
508 int read_only_cookies = 0; /* enable to not write cookies */
509 int enable_scripts = 1;
510 int enable_plugins = 0;
511 int default_font_size = 12;
512 gfloat default_zoom_level = 1.0;
513 int window_height = 768;
514 int window_width = 1024;
515 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
516 int refresh_interval = 10; /* download refresh interval */
517 int enable_cookie_whitelist = 0;
518 int enable_js_whitelist = 0;
519 int session_timeout = 3600; /* cookie session timeout */
520 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
521 char *ssl_ca_file = NULL;
522 char *resource_dir = NULL;
523 gboolean ssl_strict_certs = FALSE;
524 int append_next = 1; /* append tab after current tab */
525 char *home = NULL;
526 char *search_string = NULL;
527 char *http_proxy = NULL;
528 char download_dir[PATH_MAX];
529 char runtime_settings[PATH_MAX]; /* override of settings */
530 int allow_volatile_cookies = 0;
531 int save_global_history = 0; /* save global history to disk */
532 char *user_agent = NULL;
533 int save_rejected_cookies = 0;
534 int session_autosave = 0;
535 int guess_search = 0;
536 int dns_prefetch = FALSE;
537 gint max_connections = 25;
538 gint max_host_connections = 5;
539 gint enable_spell_checking = 0;
540 char *spell_check_languages = NULL;
542 char *cmd_font_name = NULL;
543 char *oops_font_name = NULL;
544 char *statusbar_font_name = NULL;
545 char *tabbar_font_name = NULL;
546 PangoFontDescription *cmd_font;
547 PangoFontDescription *oops_font;
548 PangoFontDescription *statusbar_font;
549 PangoFontDescription *tabbar_font;
551 int btn_down; /* M1 down in any wv */
553 struct settings;
554 struct key_binding;
555 int set_browser_mode(struct settings *, char *);
556 int set_cookie_policy(struct settings *, char *);
557 int set_download_dir(struct settings *, char *);
558 int set_runtime_dir(struct settings *, char *);
559 int set_tab_style(struct settings *, char *);
560 int set_work_dir(struct settings *, char *);
561 int add_alias(struct settings *, char *);
562 int add_mime_type(struct settings *, char *);
563 int add_cookie_wl(struct settings *, char *);
564 int add_js_wl(struct settings *, char *);
565 int add_kb(struct settings *, char *);
566 void button_set_stockid(GtkWidget *, char *);
567 GtkWidget * create_button(char *, char *, int);
569 char *get_browser_mode(struct settings *);
570 char *get_cookie_policy(struct settings *);
571 char *get_download_dir(struct settings *);
572 char *get_runtime_dir(struct settings *);
573 char *get_tab_style(struct settings *);
574 char *get_work_dir(struct settings *);
576 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
577 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
578 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
579 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
580 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
582 void recalc_tabs(void);
583 void recolor_compact_tabs(void);
584 void set_current_tab(int page_num);
585 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
587 struct special {
588 int (*set)(struct settings *, char *);
589 char *(*get)(struct settings *);
590 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
593 struct special s_browser_mode = {
594 set_browser_mode,
595 get_browser_mode,
596 NULL
599 struct special s_cookie = {
600 set_cookie_policy,
601 get_cookie_policy,
602 NULL
605 struct special s_alias = {
606 add_alias,
607 NULL,
608 walk_alias
611 struct special s_mime = {
612 add_mime_type,
613 NULL,
614 walk_mime_type
617 struct special s_js = {
618 add_js_wl,
619 NULL,
620 walk_js_wl
623 struct special s_kb = {
624 add_kb,
625 NULL,
626 walk_kb
629 struct special s_cookie_wl = {
630 add_cookie_wl,
631 NULL,
632 walk_cookie_wl
635 struct special s_download_dir = {
636 set_download_dir,
637 get_download_dir,
638 NULL
641 struct special s_work_dir = {
642 set_work_dir,
643 get_work_dir,
644 NULL
647 struct special s_tab_style = {
648 set_tab_style,
649 get_tab_style,
650 NULL
653 struct settings {
654 char *name;
655 int type;
656 #define XT_S_INVALID (0)
657 #define XT_S_INT (1)
658 #define XT_S_STR (2)
659 #define XT_S_FLOAT (3)
660 uint32_t flags;
661 #define XT_SF_RESTART (1<<0)
662 #define XT_SF_RUNTIME (1<<1)
663 int *ival;
664 char **sval;
665 struct special *s;
666 gfloat *fval;
667 } rs[] = {
668 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
669 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
670 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
671 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
672 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
673 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
674 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
675 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
676 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
677 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
678 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
679 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
680 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
681 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
682 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
683 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
684 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
685 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
686 { "home", XT_S_STR, 0, NULL, &home, NULL },
687 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
688 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
689 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
690 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
691 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
692 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
693 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
694 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
695 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
696 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
697 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
698 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
699 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
700 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
701 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
702 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
703 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
704 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
705 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
706 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
707 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
708 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
709 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
710 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
712 /* font settings */
713 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
714 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
715 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
716 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
718 /* runtime settings */
719 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
720 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
721 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
722 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
723 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
726 int about(struct tab *, struct karg *);
727 int blank(struct tab *, struct karg *);
728 int ca_cmd(struct tab *, struct karg *);
729 int cookie_show_wl(struct tab *, struct karg *);
730 int js_show_wl(struct tab *, struct karg *);
731 int help(struct tab *, struct karg *);
732 int set(struct tab *, struct karg *);
733 int stats(struct tab *, struct karg *);
734 int marco(struct tab *, struct karg *);
735 const char * marco_message(int *);
736 int xtp_page_cl(struct tab *, struct karg *);
737 int xtp_page_dl(struct tab *, struct karg *);
738 int xtp_page_fl(struct tab *, struct karg *);
739 int xtp_page_hl(struct tab *, struct karg *);
740 void xt_icon_from_file(struct tab *, char *);
741 const gchar *get_uri(struct tab *);
742 const gchar *get_title(struct tab *, bool);
744 #define XT_URI_ABOUT ("about:")
745 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
746 #define XT_URI_ABOUT_ABOUT ("about")
747 #define XT_URI_ABOUT_BLANK ("blank")
748 #define XT_URI_ABOUT_CERTS ("certs")
749 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
750 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
751 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
752 #define XT_URI_ABOUT_FAVORITES ("favorites")
753 #define XT_URI_ABOUT_HELP ("help")
754 #define XT_URI_ABOUT_HISTORY ("history")
755 #define XT_URI_ABOUT_JSWL ("jswl")
756 #define XT_URI_ABOUT_SET ("set")
757 #define XT_URI_ABOUT_STATS ("stats")
758 #define XT_URI_ABOUT_MARCO ("marco")
760 struct about_type {
761 char *name;
762 int (*func)(struct tab *, struct karg *);
763 } about_list[] = {
764 { XT_URI_ABOUT_ABOUT, about },
765 { XT_URI_ABOUT_BLANK, blank },
766 { XT_URI_ABOUT_CERTS, ca_cmd },
767 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
768 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
769 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
770 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
771 { XT_URI_ABOUT_HELP, help },
772 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
773 { XT_URI_ABOUT_JSWL, js_show_wl },
774 { XT_URI_ABOUT_SET, set },
775 { XT_URI_ABOUT_STATS, stats },
776 { XT_URI_ABOUT_MARCO, marco },
779 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
780 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
781 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
782 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
783 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
784 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
785 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
787 /* globals */
788 extern char *__progname;
789 char **start_argv;
790 struct passwd *pwd;
791 GtkWidget *main_window;
792 GtkNotebook *notebook;
793 GtkWidget *tab_bar;
794 GtkWidget *arrow, *abtn;
795 struct tab_list tabs;
796 struct history_list hl;
797 struct download_list downloads;
798 struct domain_list c_wl;
799 struct domain_list js_wl;
800 struct undo_tailq undos;
801 struct keybinding_list kbl;
802 int undo_count;
803 int updating_dl_tabs = 0;
804 int updating_hl_tabs = 0;
805 int updating_cl_tabs = 0;
806 int updating_fl_tabs = 0;
807 char *global_search;
808 uint64_t blocked_cookies = 0;
809 char named_session[PATH_MAX];
810 int icon_size_map(int);
812 GtkListStore *completion_model;
813 void completion_add(struct tab *);
814 void completion_add_uri(const gchar *);
815 GtkListStore *buffers_store;
816 void xxx_dir(char *);
818 void
819 sigchild(int sig)
821 int saved_errno, status;
822 pid_t pid;
824 saved_errno = errno;
826 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
827 if (pid == -1) {
828 if (errno == EINTR)
829 continue;
830 if (errno != ECHILD) {
832 clog_warn("sigchild: waitpid:");
835 break;
838 if (WIFEXITED(status)) {
839 if (WEXITSTATUS(status) != 0) {
841 clog_warnx("sigchild: child exit status: %d",
842 WEXITSTATUS(status));
845 } else {
847 clog_warnx("sigchild: child is terminated abnormally");
852 errno = saved_errno;
856 is_g_object_setting(GObject *o, char *str)
858 guint n_props = 0, i;
859 GParamSpec **proplist;
861 if (! G_IS_OBJECT(o))
862 return (0);
864 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
865 &n_props);
867 for (i=0; i < n_props; i++) {
868 if (! strcmp(proplist[i]->name, str))
869 return (1);
871 return (0);
874 gchar *
875 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
877 gchar *r;
879 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
880 "<head>\n"
881 "<title>%s</title>\n"
882 "%s"
883 "%s"
884 "</head>\n"
885 "<body>\n"
886 "<h1>%s</h1>\n"
887 "%s\n</body>\n"
888 "</html>",
889 title,
890 addstyles ? XT_PAGE_STYLE : "",
891 head,
892 title,
893 body);
895 return r;
899 * Display a web page from a HTML string in memory, rather than from a URL
901 void
902 load_webkit_string(struct tab *t, const char *str, gchar *title)
904 char file[PATH_MAX];
905 int i;
907 /* we set this to indicate we want to manually do navaction */
908 if (t->bfl)
909 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
911 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
912 if (title) {
913 /* set t->xtp_meaning */
914 for (i = 0; i < LENGTH(about_list); i++)
915 if (!strcmp(title, about_list[i].name)) {
916 t->xtp_meaning = i;
917 break;
920 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
921 #if GTK_CHECK_VERSION(2, 20, 0)
922 gtk_spinner_stop(GTK_SPINNER(t->spinner));
923 gtk_widget_hide(t->spinner);
924 #endif
925 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
926 xt_icon_from_file(t, file);
930 struct tab *
931 get_current_tab(void)
933 struct tab *t;
935 TAILQ_FOREACH(t, &tabs, entry) {
936 if (t->tab_id == gtk_notebook_get_current_page(notebook))
937 return (t);
940 warnx("%s: no current tab", __func__);
942 return (NULL);
945 void
946 set_status(struct tab *t, gchar *s, int status)
948 gchar *type = NULL;
950 if (s == NULL)
951 return;
953 switch (status) {
954 case XT_STATUS_LOADING:
955 type = g_strdup_printf("Loading: %s", s);
956 s = type;
957 break;
958 case XT_STATUS_LINK:
959 type = g_strdup_printf("Link: %s", s);
960 if (!t->status)
961 t->status = g_strdup(gtk_entry_get_text(
962 GTK_ENTRY(t->sbe.statusbar)));
963 s = type;
964 break;
965 case XT_STATUS_URI:
966 type = g_strdup_printf("%s", s);
967 if (!t->status) {
968 t->status = g_strdup(type);
970 s = type;
971 if (!t->status)
972 t->status = g_strdup(s);
973 break;
974 case XT_STATUS_NOTHING:
975 /* FALL THROUGH */
976 default:
977 break;
979 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
980 if (type)
981 g_free(type);
984 void
985 hide_cmd(struct tab *t)
987 gtk_widget_hide(t->cmd);
990 void
991 show_cmd(struct tab *t)
993 gtk_widget_hide(t->oops);
994 gtk_widget_show(t->cmd);
997 void
998 hide_buffers(struct tab *t)
1000 gtk_widget_hide(t->buffers);
1001 gtk_list_store_clear(buffers_store);
1004 enum {
1005 COL_ID = 0,
1006 COL_TITLE,
1007 NUM_COLS
1011 sort_tabs_by_page_num(struct tab ***stabs)
1013 int num_tabs = 0;
1014 struct tab *t;
1016 num_tabs = gtk_notebook_get_n_pages(notebook);
1018 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1020 TAILQ_FOREACH(t, &tabs, entry)
1021 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1023 return (num_tabs);
1026 void
1027 buffers_make_list(void)
1029 int i, num_tabs;
1030 const gchar *title = NULL;
1031 GtkTreeIter iter;
1032 struct tab **stabs = NULL;
1034 num_tabs = sort_tabs_by_page_num(&stabs);
1036 for (i = 0; i < num_tabs; i++)
1037 if (stabs[i]) {
1038 gtk_list_store_append(buffers_store, &iter);
1039 title = get_title(stabs[i], FALSE);
1040 gtk_list_store_set(buffers_store, &iter,
1041 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1042 * rather than 0. */
1043 COL_TITLE, title,
1044 -1);
1047 g_free(stabs);
1050 void
1051 show_buffers(struct tab *t)
1053 buffers_make_list();
1054 gtk_widget_show(t->buffers);
1055 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1058 void
1059 toggle_buffers(struct tab *t)
1061 if (gtk_widget_get_visible(t->buffers))
1062 hide_buffers(t);
1063 else
1064 show_buffers(t);
1068 buffers(struct tab *t, struct karg *args)
1070 show_buffers(t);
1072 return (0);
1075 void
1076 hide_oops(struct tab *t)
1078 gtk_widget_hide(t->oops);
1081 void
1082 show_oops(struct tab *at, const char *fmt, ...)
1084 va_list ap;
1085 char *msg;
1086 struct tab *t = NULL;
1088 if (fmt == NULL)
1089 return;
1091 if (at == NULL) {
1092 if ((t = get_current_tab()) == NULL)
1093 return;
1094 } else
1095 t = at;
1097 va_start(ap, fmt);
1098 if (vasprintf(&msg, fmt, ap) == -1)
1099 errx(1, "show_oops failed");
1100 va_end(ap);
1102 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1103 gtk_widget_hide(t->cmd);
1104 gtk_widget_show(t->oops);
1107 char *
1108 get_as_string(struct settings *s)
1110 char *r = NULL;
1112 if (s == NULL)
1113 return (NULL);
1115 if (s->s) {
1116 if (s->s->get)
1117 r = s->s->get(s);
1118 else
1119 warnx("get_as_string skip %s\n", s->name);
1120 } else if (s->type == XT_S_INT)
1121 r = g_strdup_printf("%d", *s->ival);
1122 else if (s->type == XT_S_STR)
1123 r = g_strdup(*s->sval);
1124 else if (s->type == XT_S_FLOAT)
1125 r = g_strdup_printf("%f", *s->fval);
1126 else
1127 r = g_strdup_printf("INVALID TYPE");
1129 return (r);
1132 void
1133 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1135 int i;
1136 char *s;
1138 for (i = 0; i < LENGTH(rs); i++) {
1139 if (rs[i].s && rs[i].s->walk)
1140 rs[i].s->walk(&rs[i], cb, cb_args);
1141 else {
1142 s = get_as_string(&rs[i]);
1143 cb(&rs[i], s, cb_args);
1144 g_free(s);
1150 set_browser_mode(struct settings *s, char *val)
1152 if (!strcmp(val, "whitelist")) {
1153 browser_mode = XT_BM_WHITELIST;
1154 allow_volatile_cookies = 0;
1155 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1156 cookies_enabled = 1;
1157 enable_cookie_whitelist = 1;
1158 read_only_cookies = 0;
1159 save_rejected_cookies = 0;
1160 session_timeout = 3600;
1161 enable_scripts = 0;
1162 enable_js_whitelist = 1;
1163 enable_localstorage = 0;
1164 } else if (!strcmp(val, "normal")) {
1165 browser_mode = XT_BM_NORMAL;
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 } else if (!strcmp(val, "kiosk")) {
1177 browser_mode = XT_BM_KIOSK;
1178 allow_volatile_cookies = 0;
1179 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1180 cookies_enabled = 1;
1181 enable_cookie_whitelist = 0;
1182 read_only_cookies = 0;
1183 save_rejected_cookies = 0;
1184 session_timeout = 3600;
1185 enable_scripts = 1;
1186 enable_js_whitelist = 0;
1187 enable_localstorage = 1;
1188 show_tabs = 0;
1189 tabless = 1;
1190 } else
1191 return (1);
1193 return (0);
1196 char *
1197 get_browser_mode(struct settings *s)
1199 char *r = NULL;
1201 if (browser_mode == XT_BM_WHITELIST)
1202 r = g_strdup("whitelist");
1203 else if (browser_mode == XT_BM_NORMAL)
1204 r = g_strdup("normal");
1205 else if (browser_mode == XT_BM_KIOSK)
1206 r = g_strdup("kiosk");
1207 else
1208 return (NULL);
1210 return (r);
1214 set_cookie_policy(struct settings *s, char *val)
1216 if (!strcmp(val, "no3rdparty"))
1217 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1218 else if (!strcmp(val, "accept"))
1219 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1220 else if (!strcmp(val, "reject"))
1221 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1222 else
1223 return (1);
1225 return (0);
1228 char *
1229 get_cookie_policy(struct settings *s)
1231 char *r = NULL;
1233 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1234 r = g_strdup("no3rdparty");
1235 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1236 r = g_strdup("accept");
1237 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1238 r = g_strdup("reject");
1239 else
1240 return (NULL);
1242 return (r);
1245 char *
1246 get_download_dir(struct settings *s)
1248 if (download_dir[0] == '\0')
1249 return (0);
1250 return (g_strdup(download_dir));
1254 set_download_dir(struct settings *s, char *val)
1256 if (val[0] == '~')
1257 snprintf(download_dir, sizeof download_dir, "%s/%s",
1258 pwd->pw_dir, &val[1]);
1259 else
1260 strlcpy(download_dir, val, sizeof download_dir);
1262 return (0);
1266 * Session IDs.
1267 * We use these to prevent people putting xxxt:// URLs on
1268 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1270 #define XT_XTP_SES_KEY_SZ 8
1271 #define XT_XTP_SES_KEY_HEX_FMT \
1272 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1273 char *dl_session_key; /* downloads */
1274 char *hl_session_key; /* history list */
1275 char *cl_session_key; /* cookie list */
1276 char *fl_session_key; /* favorites list */
1278 char work_dir[PATH_MAX];
1279 char certs_dir[PATH_MAX];
1280 char cache_dir[PATH_MAX];
1281 char sessions_dir[PATH_MAX];
1282 char cookie_file[PATH_MAX];
1283 SoupURI *proxy_uri = NULL;
1284 SoupSession *session;
1285 SoupCookieJar *s_cookiejar;
1286 SoupCookieJar *p_cookiejar;
1287 char rc_fname[PATH_MAX];
1289 struct mime_type_list mtl;
1290 struct alias_list aliases;
1292 /* protos */
1293 struct tab *create_new_tab(char *, struct undo *, int, int);
1294 void delete_tab(struct tab *);
1295 void adjustfont_webkit(struct tab *, int);
1296 int run_script(struct tab *, char *);
1297 int download_rb_cmp(struct download *, struct download *);
1298 gboolean cmd_execute(struct tab *t, char *str);
1301 history_rb_cmp(struct history *h1, struct history *h2)
1303 return (strcmp(h1->uri, h2->uri));
1305 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1308 domain_rb_cmp(struct domain *d1, struct domain *d2)
1310 return (strcmp(d1->d, d2->d));
1312 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1314 char *
1315 get_work_dir(struct settings *s)
1317 if (work_dir[0] == '\0')
1318 return (0);
1319 return (g_strdup(work_dir));
1323 set_work_dir(struct settings *s, char *val)
1325 if (val[0] == '~')
1326 snprintf(work_dir, sizeof work_dir, "%s/%s",
1327 pwd->pw_dir, &val[1]);
1328 else
1329 strlcpy(work_dir, val, sizeof work_dir);
1331 return (0);
1334 char *
1335 get_tab_style(struct settings *s)
1337 if (tab_style == XT_TABS_NORMAL)
1338 return (g_strdup("normal"));
1339 else
1340 return (g_strdup("compact"));
1344 set_tab_style(struct settings *s, char *val)
1346 if (!strcmp(val, "normal"))
1347 tab_style = XT_TABS_NORMAL;
1348 else if (!strcmp(val, "compact"))
1349 tab_style = XT_TABS_COMPACT;
1350 else
1351 return (1);
1353 return (0);
1357 * generate a session key to secure xtp commands.
1358 * pass in a ptr to the key in question and it will
1359 * be modified in place.
1361 void
1362 generate_xtp_session_key(char **key)
1364 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1366 /* free old key */
1367 if (*key)
1368 g_free(*key);
1370 /* make a new one */
1371 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1372 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1373 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1374 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1376 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1380 * validate a xtp session key.
1381 * return 1 if OK
1384 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1386 if (strcmp(trusted, untrusted) != 0) {
1387 show_oops(t, "%s: xtp session key mismatch possible spoof",
1388 __func__);
1389 return (0);
1392 return (1);
1396 download_rb_cmp(struct download *e1, struct download *e2)
1398 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1400 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1402 struct valid_url_types {
1403 char *type;
1404 } vut[] = {
1405 { "http://" },
1406 { "https://" },
1407 { "ftp://" },
1408 { "file://" },
1409 { XT_XTP_STR },
1413 valid_url_type(char *url)
1415 int i;
1417 for (i = 0; i < LENGTH(vut); i++)
1418 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1419 return (0);
1421 return (1);
1424 void
1425 print_cookie(char *msg, SoupCookie *c)
1427 if (c == NULL)
1428 return;
1430 if (msg)
1431 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1432 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1433 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1434 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1435 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1436 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1437 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1438 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1439 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1440 DNPRINTF(XT_D_COOKIE, "====================================\n");
1443 void
1444 walk_alias(struct settings *s,
1445 void (*cb)(struct settings *, char *, void *), void *cb_args)
1447 struct alias *a;
1448 char *str;
1450 if (s == NULL || cb == NULL) {
1451 show_oops(NULL, "walk_alias invalid parameters");
1452 return;
1455 TAILQ_FOREACH(a, &aliases, entry) {
1456 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1457 cb(s, str, cb_args);
1458 g_free(str);
1462 char *
1463 match_alias(char *url_in)
1465 struct alias *a;
1466 char *arg;
1467 char *url_out = NULL, *search, *enc_arg;
1469 search = g_strdup(url_in);
1470 arg = search;
1471 if (strsep(&arg, " \t") == NULL) {
1472 show_oops(NULL, "match_alias: NULL URL");
1473 goto done;
1476 TAILQ_FOREACH(a, &aliases, entry) {
1477 if (!strcmp(search, a->a_name))
1478 break;
1481 if (a != NULL) {
1482 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1483 a->a_name);
1484 if (arg != NULL) {
1485 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1486 url_out = g_strdup_printf(a->a_uri, enc_arg);
1487 g_free(enc_arg);
1488 } else
1489 url_out = g_strdup_printf(a->a_uri, "");
1491 done:
1492 g_free(search);
1493 return (url_out);
1496 char *
1497 guess_url_type(char *url_in)
1499 struct stat sb;
1500 char *url_out = NULL, *enc_search = NULL;
1502 url_out = match_alias(url_in);
1503 if (url_out != NULL)
1504 return (url_out);
1506 if (guess_search) {
1508 * If there is no dot nor slash in the string and it isn't a
1509 * path to a local file and doesn't resolves to an IP, assume
1510 * that the user wants to search for the string.
1513 if (strchr(url_in, '.') == NULL &&
1514 strchr(url_in, '/') == NULL &&
1515 stat(url_in, &sb) != 0 &&
1516 gethostbyname(url_in) == NULL) {
1518 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1519 url_out = g_strdup_printf(search_string, enc_search);
1520 g_free(enc_search);
1521 return (url_out);
1525 /* XXX not sure about this heuristic */
1526 if (stat(url_in, &sb) == 0)
1527 url_out = g_strdup_printf("file://%s", url_in);
1528 else
1529 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1531 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1533 return (url_out);
1536 void
1537 load_uri(struct tab *t, gchar *uri)
1539 struct karg args;
1540 gchar *newuri = NULL;
1541 int i;
1543 if (uri == NULL)
1544 return;
1546 /* Strip leading spaces. */
1547 while (*uri && isspace(*uri))
1548 uri++;
1550 if (strlen(uri) == 0) {
1551 blank(t, NULL);
1552 return;
1555 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1557 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1558 for (i = 0; i < LENGTH(about_list); i++)
1559 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1560 bzero(&args, sizeof args);
1561 about_list[i].func(t, &args);
1562 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1563 FALSE);
1564 return;
1566 show_oops(t, "invalid about page");
1567 return;
1570 if (valid_url_type(uri)) {
1571 newuri = guess_url_type(uri);
1572 uri = newuri;
1575 set_status(t, (char *)uri, XT_STATUS_LOADING);
1576 webkit_web_view_load_uri(t->wv, uri);
1578 if (newuri)
1579 g_free(newuri);
1582 const gchar *
1583 get_uri(struct tab *t)
1585 const gchar *uri = NULL;
1587 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
1588 uri = webkit_web_view_get_uri(t->wv);
1589 else
1590 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, about_list[t->xtp_meaning].name);
1592 return uri;
1595 const gchar *
1596 get_title(struct tab *t, bool window)
1598 const gchar *set = NULL, *title = NULL;
1600 title = webkit_web_view_get_title(t->wv);
1601 set = title ? title : get_uri(t);
1603 if (!set || t->xtp_meaning == XT_XTP_TAB_MEANING_BL) {
1604 set = window ? XT_NAME : "(untitled)";
1607 return set;
1611 add_alias(struct settings *s, char *line)
1613 char *l, *alias;
1614 struct alias *a = NULL;
1616 if (s == NULL || line == NULL) {
1617 show_oops(NULL, "add_alias invalid parameters");
1618 return (1);
1621 l = line;
1622 a = g_malloc(sizeof(*a));
1624 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1625 show_oops(NULL, "add_alias: incomplete alias definition");
1626 goto bad;
1628 if (strlen(alias) == 0 || strlen(l) == 0) {
1629 show_oops(NULL, "add_alias: invalid alias definition");
1630 goto bad;
1633 a->a_name = g_strdup(alias);
1634 a->a_uri = g_strdup(l);
1636 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1638 TAILQ_INSERT_TAIL(&aliases, a, entry);
1640 return (0);
1641 bad:
1642 if (a)
1643 g_free(a);
1644 return (1);
1648 add_mime_type(struct settings *s, char *line)
1650 char *mime_type;
1651 char *l;
1652 struct mime_type *m = NULL;
1653 int downloadfirst = 0;
1655 /* XXX this could be smarter */
1657 if (line == NULL || strlen(line) == 0) {
1658 show_oops(NULL, "add_mime_type invalid parameters");
1659 return (1);
1662 l = line;
1663 if (*l == '@') {
1664 downloadfirst = 1;
1665 l++;
1667 m = g_malloc(sizeof(*m));
1669 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1670 show_oops(NULL, "add_mime_type: invalid mime_type");
1671 goto bad;
1673 if (mime_type[strlen(mime_type) - 1] == '*') {
1674 mime_type[strlen(mime_type) - 1] = '\0';
1675 m->mt_default = 1;
1676 } else
1677 m->mt_default = 0;
1679 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1680 show_oops(NULL, "add_mime_type: invalid mime_type");
1681 goto bad;
1684 m->mt_type = g_strdup(mime_type);
1685 m->mt_action = g_strdup(l);
1686 m->mt_download = downloadfirst;
1688 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1689 m->mt_type, m->mt_action, m->mt_default);
1691 TAILQ_INSERT_TAIL(&mtl, m, entry);
1693 return (0);
1694 bad:
1695 if (m)
1696 g_free(m);
1697 return (1);
1700 struct mime_type *
1701 find_mime_type(char *mime_type)
1703 struct mime_type *m, *def = NULL, *rv = NULL;
1705 TAILQ_FOREACH(m, &mtl, entry) {
1706 if (m->mt_default &&
1707 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1708 def = m;
1710 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1711 rv = m;
1712 break;
1716 if (rv == NULL)
1717 rv = def;
1719 return (rv);
1722 void
1723 walk_mime_type(struct settings *s,
1724 void (*cb)(struct settings *, char *, void *), void *cb_args)
1726 struct mime_type *m;
1727 char *str;
1729 if (s == NULL || cb == NULL) {
1730 show_oops(NULL, "walk_mime_type invalid parameters");
1731 return;
1734 TAILQ_FOREACH(m, &mtl, entry) {
1735 str = g_strdup_printf("%s%s --> %s",
1736 m->mt_type,
1737 m->mt_default ? "*" : "",
1738 m->mt_action);
1739 cb(s, str, cb_args);
1740 g_free(str);
1744 void
1745 wl_add(char *str, struct domain_list *wl, int handy)
1747 struct domain *d;
1748 int add_dot = 0;
1750 if (str == NULL || wl == NULL || strlen(str) < 2)
1751 return;
1753 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1755 /* treat *.moo.com the same as .moo.com */
1756 if (str[0] == '*' && str[1] == '.')
1757 str = &str[1];
1758 else if (str[0] == '.')
1759 str = &str[0];
1760 else
1761 add_dot = 1;
1763 d = g_malloc(sizeof *d);
1764 if (add_dot)
1765 d->d = g_strdup_printf(".%s", str);
1766 else
1767 d->d = g_strdup(str);
1768 d->handy = handy;
1770 if (RB_INSERT(domain_list, wl, d))
1771 goto unwind;
1773 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1774 return;
1775 unwind:
1776 if (d) {
1777 if (d->d)
1778 g_free(d->d);
1779 g_free(d);
1784 add_cookie_wl(struct settings *s, char *entry)
1786 wl_add(entry, &c_wl, 1);
1787 return (0);
1790 void
1791 walk_cookie_wl(struct settings *s,
1792 void (*cb)(struct settings *, char *, void *), void *cb_args)
1794 struct domain *d;
1796 if (s == NULL || cb == NULL) {
1797 show_oops(NULL, "walk_cookie_wl invalid parameters");
1798 return;
1801 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1802 cb(s, d->d, cb_args);
1805 void
1806 walk_js_wl(struct settings *s,
1807 void (*cb)(struct settings *, char *, void *), void *cb_args)
1809 struct domain *d;
1811 if (s == NULL || cb == NULL) {
1812 show_oops(NULL, "walk_js_wl invalid parameters");
1813 return;
1816 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1817 cb(s, d->d, cb_args);
1821 add_js_wl(struct settings *s, char *entry)
1823 wl_add(entry, &js_wl, 1 /* persistent */);
1824 return (0);
1827 struct domain *
1828 wl_find(const gchar *search, struct domain_list *wl)
1830 int i;
1831 struct domain *d = NULL, dfind;
1832 gchar *s = NULL;
1834 if (search == NULL || wl == NULL)
1835 return (NULL);
1836 if (strlen(search) < 2)
1837 return (NULL);
1839 if (search[0] != '.')
1840 s = g_strdup_printf(".%s", search);
1841 else
1842 s = g_strdup(search);
1844 for (i = strlen(s) - 1; i >= 0; i--) {
1845 if (s[i] == '.') {
1846 dfind.d = &s[i];
1847 d = RB_FIND(domain_list, wl, &dfind);
1848 if (d)
1849 goto done;
1853 done:
1854 if (s)
1855 g_free(s);
1857 return (d);
1860 struct domain *
1861 wl_find_uri(const gchar *s, struct domain_list *wl)
1863 int i;
1864 char *ss;
1865 struct domain *r;
1867 if (s == NULL || wl == NULL)
1868 return (NULL);
1870 if (!strncmp(s, "http://", strlen("http://")))
1871 s = &s[strlen("http://")];
1872 else if (!strncmp(s, "https://", strlen("https://")))
1873 s = &s[strlen("https://")];
1875 if (strlen(s) < 2)
1876 return (NULL);
1878 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1879 /* chop string at first slash */
1880 if (s[i] == '/' || s[i] == '\0') {
1881 ss = g_strdup(s);
1882 ss[i] = '\0';
1883 r = wl_find(ss, wl);
1884 g_free(ss);
1885 return (r);
1888 return (NULL);
1891 char *
1892 get_toplevel_domain(char *domain)
1894 char *s;
1895 int found = 0;
1897 if (domain == NULL)
1898 return (NULL);
1899 if (strlen(domain) < 2)
1900 return (NULL);
1902 s = &domain[strlen(domain) - 1];
1903 while (s != domain) {
1904 if (*s == '.') {
1905 found++;
1906 if (found == 2)
1907 return (s);
1909 s--;
1912 if (found)
1913 return (domain);
1915 return (NULL);
1919 settings_add(char *var, char *val)
1921 int i, rv, *p;
1922 gfloat *f;
1923 char **s;
1925 /* get settings */
1926 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1927 if (strcmp(var, rs[i].name))
1928 continue;
1930 if (rs[i].s) {
1931 if (rs[i].s->set(&rs[i], val))
1932 errx(1, "invalid value for %s: %s", var, val);
1933 rv = 1;
1934 break;
1935 } else
1936 switch (rs[i].type) {
1937 case XT_S_INT:
1938 p = rs[i].ival;
1939 *p = atoi(val);
1940 rv = 1;
1941 break;
1942 case XT_S_STR:
1943 s = rs[i].sval;
1944 if (s == NULL)
1945 errx(1, "invalid sval for %s",
1946 rs[i].name);
1947 if (*s)
1948 g_free(*s);
1949 *s = g_strdup(val);
1950 rv = 1;
1951 break;
1952 case XT_S_FLOAT:
1953 f = rs[i].fval;
1954 *f = atof(val);
1955 rv = 1;
1956 break;
1957 case XT_S_INVALID:
1958 default:
1959 errx(1, "invalid type for %s", var);
1961 break;
1963 return (rv);
1966 #define WS "\n= \t"
1967 void
1968 config_parse(char *filename, int runtime)
1970 FILE *config, *f;
1971 char *line, *cp, *var, *val;
1972 size_t len, lineno = 0;
1973 int handled;
1974 char file[PATH_MAX];
1975 struct stat sb;
1977 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1979 if (filename == NULL)
1980 return;
1982 if (runtime && runtime_settings[0] != '\0') {
1983 snprintf(file, sizeof file, "%s/%s",
1984 work_dir, runtime_settings);
1985 if (stat(file, &sb)) {
1986 warnx("runtime file doesn't exist, creating it");
1987 if ((f = fopen(file, "w")) == NULL)
1988 err(1, "runtime");
1989 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1990 fclose(f);
1992 } else
1993 strlcpy(file, filename, sizeof file);
1995 if ((config = fopen(file, "r")) == NULL) {
1996 warn("config_parse: cannot open %s", filename);
1997 return;
2000 for (;;) {
2001 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2002 if (feof(config) || ferror(config))
2003 break;
2005 cp = line;
2006 cp += (long)strspn(cp, WS);
2007 if (cp[0] == '\0') {
2008 /* empty line */
2009 free(line);
2010 continue;
2013 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2014 errx(1, "invalid config file entry: %s", line);
2016 cp += (long)strspn(cp, WS);
2018 if ((val = strsep(&cp, "\0")) == NULL)
2019 break;
2021 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2022 handled = settings_add(var, val);
2023 if (handled == 0)
2024 errx(1, "invalid conf file entry: %s=%s", var, val);
2026 free(line);
2029 fclose(config);
2032 char *
2033 js_ref_to_string(JSContextRef context, JSValueRef ref)
2035 char *s = NULL;
2036 size_t l;
2037 JSStringRef jsref;
2039 jsref = JSValueToStringCopy(context, ref, NULL);
2040 if (jsref == NULL)
2041 return (NULL);
2043 l = JSStringGetMaximumUTF8CStringSize(jsref);
2044 s = g_malloc(l);
2045 if (s)
2046 JSStringGetUTF8CString(jsref, s, l);
2047 JSStringRelease(jsref);
2049 return (s);
2052 void
2053 disable_hints(struct tab *t)
2055 bzero(t->hint_buf, sizeof t->hint_buf);
2056 bzero(t->hint_num, sizeof t->hint_num);
2057 run_script(t, "vimprobable_clear()");
2058 t->hints_on = 0;
2059 t->hint_mode = XT_HINT_NONE;
2062 void
2063 enable_hints(struct tab *t)
2065 bzero(t->hint_buf, sizeof t->hint_buf);
2066 run_script(t, "vimprobable_show_hints()");
2067 t->hints_on = 1;
2068 t->hint_mode = XT_HINT_NONE;
2071 #define XT_JS_OPEN ("open;")
2072 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2073 #define XT_JS_FIRE ("fire;")
2074 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2075 #define XT_JS_FOUND ("found;")
2076 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2079 run_script(struct tab *t, char *s)
2081 JSGlobalContextRef ctx;
2082 WebKitWebFrame *frame;
2083 JSStringRef str;
2084 JSValueRef val, exception;
2085 char *es, buf[128];
2087 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2088 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2090 frame = webkit_web_view_get_main_frame(t->wv);
2091 ctx = webkit_web_frame_get_global_context(frame);
2093 str = JSStringCreateWithUTF8CString(s);
2094 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2095 NULL, 0, &exception);
2096 JSStringRelease(str);
2098 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2099 if (val == NULL) {
2100 es = js_ref_to_string(ctx, exception);
2101 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2102 g_free(es);
2103 return (1);
2104 } else {
2105 es = js_ref_to_string(ctx, val);
2106 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2108 /* handle return value right here */
2109 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2110 disable_hints(t);
2111 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
2114 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2115 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2116 &es[XT_JS_FIRE_LEN]);
2117 run_script(t, buf);
2118 disable_hints(t);
2121 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2122 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2123 disable_hints(t);
2126 g_free(es);
2129 return (0);
2133 hint(struct tab *t, struct karg *args)
2136 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2138 if (t->hints_on == 0)
2139 enable_hints(t);
2140 else
2141 disable_hints(t);
2143 return (0);
2146 void
2147 apply_style(struct tab *t)
2149 g_object_set(G_OBJECT(t->settings),
2150 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2154 userstyle(struct tab *t, struct karg *args)
2156 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2158 if (t->styled) {
2159 t->styled = 0;
2160 g_object_set(G_OBJECT(t->settings),
2161 "user-stylesheet-uri", NULL, (char *)NULL);
2162 } else {
2163 t->styled = 1;
2164 apply_style(t);
2166 return (0);
2170 * Doesn't work fully, due to the following bug:
2171 * https://bugs.webkit.org/show_bug.cgi?id=51747
2174 restore_global_history(void)
2176 char file[PATH_MAX];
2177 FILE *f;
2178 struct history *h;
2179 gchar *uri;
2180 gchar *title;
2181 const char delim[3] = {'\\', '\\', '\0'};
2183 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2185 if ((f = fopen(file, "r")) == NULL) {
2186 warnx("%s: fopen", __func__);
2187 return (1);
2190 for (;;) {
2191 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2192 if (feof(f) || ferror(f))
2193 break;
2195 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2196 if (feof(f) || ferror(f)) {
2197 free(uri);
2198 warnx("%s: broken history file\n", __func__);
2199 return (1);
2202 if (uri && strlen(uri) && title && strlen(title)) {
2203 webkit_web_history_item_new_with_data(uri, title);
2204 h = g_malloc(sizeof(struct history));
2205 h->uri = g_strdup(uri);
2206 h->title = g_strdup(title);
2207 RB_INSERT(history_list, &hl, h);
2208 completion_add_uri(h->uri);
2209 } else {
2210 warnx("%s: failed to restore history\n", __func__);
2211 free(uri);
2212 free(title);
2213 return (1);
2216 free(uri);
2217 free(title);
2218 uri = NULL;
2219 title = NULL;
2222 return (0);
2226 save_global_history_to_disk(struct tab *t)
2228 char file[PATH_MAX];
2229 FILE *f;
2230 struct history *h;
2232 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2234 if ((f = fopen(file, "w")) == NULL) {
2235 show_oops(t, "%s: global history file: %s",
2236 __func__, strerror(errno));
2237 return (1);
2240 RB_FOREACH_REVERSE(h, history_list, &hl) {
2241 if (h->uri && h->title)
2242 fprintf(f, "%s\n%s\n", h->uri, h->title);
2245 fclose(f);
2247 return (0);
2251 quit(struct tab *t, struct karg *args)
2253 if (save_global_history)
2254 save_global_history_to_disk(t);
2256 gtk_main_quit();
2258 return (1);
2262 open_tabs(struct tab *t, struct karg *a)
2264 char file[PATH_MAX];
2265 FILE *f = NULL;
2266 char *uri = NULL;
2267 int rv = 1;
2268 struct tab *ti, *tt;
2270 if (a == NULL)
2271 goto done;
2273 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2274 if ((f = fopen(file, "r")) == NULL)
2275 goto done;
2277 ti = TAILQ_LAST(&tabs, tab_list);
2279 for (;;) {
2280 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2281 if (feof(f) || ferror(f))
2282 break;
2284 /* retrieve session name */
2285 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2286 strlcpy(named_session,
2287 &uri[strlen(XT_SAVE_SESSION_ID)],
2288 sizeof named_session);
2289 continue;
2292 if (uri && strlen(uri))
2293 create_new_tab(uri, NULL, 1, -1);
2295 free(uri);
2296 uri = NULL;
2299 /* close open tabs */
2300 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2301 for (;;) {
2302 tt = TAILQ_FIRST(&tabs);
2303 if (tt != ti) {
2304 delete_tab(tt);
2305 continue;
2307 delete_tab(tt);
2308 break;
2310 recalc_tabs();
2313 rv = 0;
2314 done:
2315 if (f)
2316 fclose(f);
2318 return (rv);
2322 restore_saved_tabs(void)
2324 char file[PATH_MAX];
2325 int unlink_file = 0;
2326 struct stat sb;
2327 struct karg a;
2328 int rv = 0;
2330 snprintf(file, sizeof file, "%s/%s",
2331 sessions_dir, XT_RESTART_TABS_FILE);
2332 if (stat(file, &sb) == -1)
2333 a.s = XT_SAVED_TABS_FILE;
2334 else {
2335 unlink_file = 1;
2336 a.s = XT_RESTART_TABS_FILE;
2339 a.i = XT_SES_DONOTHING;
2340 rv = open_tabs(NULL, &a);
2342 if (unlink_file)
2343 unlink(file);
2345 return (rv);
2349 save_tabs(struct tab *t, struct karg *a)
2351 char file[PATH_MAX];
2352 FILE *f;
2353 int num_tabs = 0, i;
2354 struct tab **stabs = NULL;
2356 if (a == NULL)
2357 return (1);
2358 if (a->s == NULL)
2359 snprintf(file, sizeof file, "%s/%s",
2360 sessions_dir, named_session);
2361 else
2362 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2364 if ((f = fopen(file, "w")) == NULL) {
2365 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2366 return (1);
2369 /* save session name */
2370 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2372 /* Save tabs, in the order they are arranged in the notebook. */
2373 num_tabs = sort_tabs_by_page_num(&stabs);
2375 for (i = 0; i < num_tabs; i++)
2376 if (stabs[i] && get_uri(stabs[i]) != NULL)
2377 fprintf(f, "%s\n", get_uri(stabs[i]));
2379 g_free(stabs);
2381 /* try and make sure this gets to disk NOW. XXX Backup first? */
2382 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2383 show_oops(t, "May not have managed to save session: %s",
2384 strerror(errno));
2387 fclose(f);
2389 return (0);
2393 save_tabs_and_quit(struct tab *t, struct karg *args)
2395 struct karg a;
2397 a.s = NULL;
2398 save_tabs(t, &a);
2399 quit(t, NULL);
2401 return (1);
2405 yank_uri(struct tab *t, struct karg *args)
2407 const gchar *uri;
2408 GtkClipboard *clipboard;
2410 if ((uri = get_uri(t)) == NULL)
2411 return (1);
2413 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2414 gtk_clipboard_set_text(clipboard, uri, -1);
2416 return (0);
2420 paste_uri(struct tab *t, struct karg *args)
2422 GtkClipboard *clipboard;
2423 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2424 gint len;
2425 gchar *p = NULL, *uri;
2427 /* try primary clipboard first */
2428 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2429 p = gtk_clipboard_wait_for_text(clipboard);
2431 /* if it failed get whatever text is in cut_buffer0 */
2432 if (p == NULL)
2433 if (gdk_property_get(gdk_get_default_root_window(),
2434 atom,
2435 gdk_atom_intern("STRING", FALSE),
2437 65536 /* picked out of my butt */,
2438 FALSE,
2439 NULL,
2440 NULL,
2441 &len,
2442 (guchar **)&p)) {
2443 /* yes sir, we need to NUL the string */
2444 p[len] = '\0';
2447 if (p) {
2448 uri = p;
2449 while (*uri && isspace(*uri))
2450 uri++;
2451 if (strlen(uri) == 0) {
2452 show_oops(t, "empty paste buffer");
2453 goto done;
2455 if (guess_search == 0 && valid_url_type(uri)) {
2456 /* we can be clever and paste this in search box */
2457 show_oops(t, "not a valid URL");
2458 goto done;
2461 if (args->i == XT_PASTE_CURRENT_TAB)
2462 load_uri(t, uri);
2463 else if (args->i == XT_PASTE_NEW_TAB)
2464 create_new_tab(uri, NULL, 1, -1);
2467 done:
2468 if (p)
2469 g_free(p);
2471 return (0);
2474 char *
2475 find_domain(const gchar *s, int add_dot)
2477 int i;
2478 char *r = NULL, *ss = NULL;
2480 if (s == NULL)
2481 return (NULL);
2483 if (!strncmp(s, "http://", strlen("http://")))
2484 s = &s[strlen("http://")];
2485 else if (!strncmp(s, "https://", strlen("https://")))
2486 s = &s[strlen("https://")];
2488 if (strlen(s) < 2)
2489 return (NULL);
2491 ss = g_strdup(s);
2492 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2493 /* chop string at first slash */
2494 if (ss[i] == '/' || ss[i] == '\0') {
2495 ss[i] = '\0';
2496 if (add_dot)
2497 r = g_strdup_printf(".%s", ss);
2498 else
2499 r = g_strdup(ss);
2500 break;
2502 g_free(ss);
2504 return (r);
2508 toggle_cwl(struct tab *t, struct karg *args)
2510 struct domain *d;
2511 const gchar *uri;
2512 char *dom = NULL, *dom_toggle = NULL;
2513 int es;
2515 if (args == NULL)
2516 return (1);
2518 uri = get_uri(t);
2519 dom = find_domain(uri, 1);
2520 d = wl_find(dom, &c_wl);
2522 if (d == NULL)
2523 es = 0;
2524 else
2525 es = 1;
2527 if (args->i & XT_WL_TOGGLE)
2528 es = !es;
2529 else if ((args->i & XT_WL_ENABLE) && es != 1)
2530 es = 1;
2531 else if ((args->i & XT_WL_DISABLE) && es != 0)
2532 es = 0;
2534 if (args->i & XT_WL_TOPLEVEL)
2535 dom_toggle = get_toplevel_domain(dom);
2536 else
2537 dom_toggle = dom;
2539 if (es)
2540 /* enable cookies for domain */
2541 wl_add(dom_toggle, &c_wl, 0);
2542 else
2543 /* disable cookies for domain */
2544 RB_REMOVE(domain_list, &c_wl, d);
2546 if (args->i & XT_WL_RELOAD)
2547 webkit_web_view_reload(t->wv);
2549 g_free(dom);
2550 return (0);
2554 toggle_js(struct tab *t, struct karg *args)
2556 int es;
2557 const gchar *uri;
2558 struct domain *d;
2559 char *dom = NULL, *dom_toggle = NULL;
2561 if (args == NULL)
2562 return (1);
2564 g_object_get(G_OBJECT(t->settings),
2565 "enable-scripts", &es, (char *)NULL);
2566 if (args->i & XT_WL_TOGGLE)
2567 es = !es;
2568 else if ((args->i & XT_WL_ENABLE) && es != 1)
2569 es = 1;
2570 else if ((args->i & XT_WL_DISABLE) && es != 0)
2571 es = 0;
2572 else
2573 return (1);
2575 uri = get_uri(t);
2576 dom = find_domain(uri, 1);
2578 if (uri == NULL || dom == NULL) {
2579 show_oops(t, "Can't toggle domain in JavaScript white list");
2580 goto done;
2583 if (args->i & XT_WL_TOPLEVEL)
2584 dom_toggle = get_toplevel_domain(dom);
2585 else
2586 dom_toggle = dom;
2588 if (es) {
2589 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2590 wl_add(dom_toggle, &js_wl, 0 /* session */);
2591 } else {
2592 d = wl_find(dom_toggle, &js_wl);
2593 if (d)
2594 RB_REMOVE(domain_list, &js_wl, d);
2595 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2597 g_object_set(G_OBJECT(t->settings),
2598 "enable-scripts", es, (char *)NULL);
2599 g_object_set(G_OBJECT(t->settings),
2600 "javascript-can-open-windows-automatically", es, (char *)NULL);
2601 webkit_web_view_set_settings(t->wv, t->settings);
2603 if (args->i & XT_WL_RELOAD)
2604 webkit_web_view_reload(t->wv);
2605 done:
2606 if (dom)
2607 g_free(dom);
2608 return (0);
2611 void
2612 js_toggle_cb(GtkWidget *w, struct tab *t)
2614 struct karg a;
2616 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2617 toggle_cwl(t, &a);
2619 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2620 toggle_js(t, &a);
2624 toggle_src(struct tab *t, struct karg *args)
2626 gboolean mode;
2628 if (t == NULL)
2629 return (0);
2631 mode = webkit_web_view_get_view_source_mode(t->wv);
2632 webkit_web_view_set_view_source_mode(t->wv, !mode);
2633 webkit_web_view_reload(t->wv);
2635 return (0);
2638 void
2639 focus_webview(struct tab *t)
2641 if (t == NULL)
2642 return;
2644 /* only grab focus if we are visible */
2645 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2646 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2650 focus(struct tab *t, struct karg *args)
2652 if (t == NULL || args == NULL)
2653 return (1);
2655 if (show_url == 0)
2656 return (0);
2658 if (args->i == XT_FOCUS_URI)
2659 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2660 else if (args->i == XT_FOCUS_SEARCH)
2661 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2663 return (0);
2667 stats(struct tab *t, struct karg *args)
2669 char *page, *body, *s, line[64 * 1024];
2670 uint64_t line_count = 0;
2671 FILE *r_cookie_f;
2673 if (t == NULL)
2674 show_oops(NULL, "stats invalid parameters");
2676 line[0] = '\0';
2677 if (save_rejected_cookies) {
2678 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2679 for (;;) {
2680 s = fgets(line, sizeof line, r_cookie_f);
2681 if (s == NULL || feof(r_cookie_f) ||
2682 ferror(r_cookie_f))
2683 break;
2684 line_count++;
2686 fclose(r_cookie_f);
2687 snprintf(line, sizeof line,
2688 "<br/>Cookies blocked(*) total: %llu", line_count);
2689 } else
2690 show_oops(t, "Can't open blocked cookies file: %s",
2691 strerror(errno));
2694 body = g_strdup_printf(
2695 "Cookies blocked(*) this session: %llu"
2696 "%s"
2697 "<p><small><b>*</b> results vary based on settings</small></p>",
2698 blocked_cookies,
2699 line);
2701 page = get_html_page("Statistics", body, "", 0);
2702 g_free(body);
2704 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2705 g_free(page);
2707 return (0);
2711 marco(struct tab *t, struct karg *args)
2713 char *page, line[64 * 1024];
2714 int len;
2716 if (t == NULL)
2717 show_oops(NULL, "marco invalid parameters");
2719 line[0] = '\0';
2720 snprintf(line, sizeof line, "%s", marco_message(&len));
2722 page = get_html_page("Marco Sez...", line, "", 0);
2724 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2725 g_free(page);
2727 return (0);
2731 blank(struct tab *t, struct karg *args)
2733 if (t == NULL)
2734 show_oops(NULL, "blank invalid parameters");
2736 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2738 return (0);
2741 about(struct tab *t, struct karg *args)
2743 char *page, *body;
2745 if (t == NULL)
2746 show_oops(NULL, "about invalid parameters");
2748 body = g_strdup_printf("<b>Version: %s</b><p>"
2749 "Authors:"
2750 "<ul>"
2751 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2752 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2753 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2754 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2755 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2756 "</ul>"
2757 "Copyrights and licenses can be found on the XXXTerm "
2758 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2759 version
2762 page = get_html_page("About", body, "", 0);
2763 g_free(body);
2765 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2766 g_free(page);
2768 return (0);
2772 help(struct tab *t, struct karg *args)
2774 char *page, *head, *body;
2776 if (t == NULL)
2777 show_oops(NULL, "help invalid parameters");
2779 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2780 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2781 "</head>\n";
2782 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2783 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2784 "cgi-bin/man-cgi?xxxterm</a>";
2786 page = get_html_page(XT_NAME, body, head, FALSE);
2788 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2789 g_free(page);
2791 return (0);
2795 * update all favorite tabs apart from one. Pass NULL if
2796 * you want to update all.
2798 void
2799 update_favorite_tabs(struct tab *apart_from)
2801 struct tab *t;
2802 if (!updating_fl_tabs) {
2803 updating_fl_tabs = 1; /* stop infinite recursion */
2804 TAILQ_FOREACH(t, &tabs, entry)
2805 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2806 && (t != apart_from))
2807 xtp_page_fl(t, NULL);
2808 updating_fl_tabs = 0;
2812 /* show a list of favorites (bookmarks) */
2814 xtp_page_fl(struct tab *t, struct karg *args)
2816 char file[PATH_MAX];
2817 FILE *f;
2818 char *uri = NULL, *title = NULL;
2819 size_t len, lineno = 0;
2820 int i, failed = 0;
2821 char *body, *tmp, *page = NULL;
2822 const char delim[3] = {'\\', '\\', '\0'};
2824 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2826 if (t == NULL)
2827 warn("%s: bad param", __func__);
2829 /* new session key */
2830 if (!updating_fl_tabs)
2831 generate_xtp_session_key(&fl_session_key);
2833 /* open favorites */
2834 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2835 if ((f = fopen(file, "r")) == NULL) {
2836 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2837 return (1);
2840 /* body */
2841 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2842 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2843 "<th style='width: 40px'>Rm</th></tr>\n");
2845 for (i = 1;;) {
2846 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2847 if (feof(f) || ferror(f))
2848 break;
2849 if (len == 0) {
2850 free(title);
2851 title = NULL;
2852 continue;
2855 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2856 if (feof(f) || ferror(f)) {
2857 show_oops(t, "favorites file corrupt");
2858 failed = 1;
2859 break;
2862 tmp = body;
2863 body = g_strdup_printf("%s<tr>"
2864 "<td>%d</td>"
2865 "<td><a href='%s'>%s</a></td>"
2866 "<td style='text-align: center'>"
2867 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2868 "</tr>\n",
2869 body, i, uri, title,
2870 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2872 g_free(tmp);
2874 free(uri);
2875 uri = NULL;
2876 free(title);
2877 title = NULL;
2878 i++;
2880 fclose(f);
2882 /* if none, say so */
2883 if (i == 1) {
2884 tmp = body;
2885 body = g_strdup_printf("%s<tr>"
2886 "<td colspan='3' style='text-align: center'>"
2887 "No favorites - To add one use the 'favadd' command."
2888 "</td></tr>", body);
2889 g_free(tmp);
2892 tmp = body;
2893 body = g_strdup_printf("%s</table>", body);
2894 g_free(tmp);
2896 if (uri)
2897 free(uri);
2898 if (title)
2899 free(title);
2901 /* render */
2902 if (!failed) {
2903 page = get_html_page("Favorites", body, "", 1);
2904 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2905 g_free(page);
2908 update_favorite_tabs(t);
2910 if (body)
2911 g_free(body);
2913 return (failed);
2916 void
2917 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2918 size_t cert_count, char *title)
2920 gnutls_datum_t cinfo;
2921 char *tmp, *body;
2922 int i;
2924 body = g_strdup("");
2926 for (i = 0; i < cert_count; i++) {
2927 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2928 &cinfo))
2929 return;
2931 tmp = body;
2932 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2933 body, i, cinfo.data);
2934 gnutls_free(cinfo.data);
2935 g_free(tmp);
2938 tmp = get_html_page(title, body, "", 0);
2939 g_free(body);
2941 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2942 g_free(tmp);
2946 ca_cmd(struct tab *t, struct karg *args)
2948 FILE *f = NULL;
2949 int rv = 1, certs = 0, certs_read;
2950 struct stat sb;
2951 gnutls_datum_t dt;
2952 gnutls_x509_crt_t *c = NULL;
2953 char *certs_buf = NULL, *s;
2955 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2956 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2957 return (1);
2960 if (fstat(fileno(f), &sb) == -1) {
2961 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2962 goto done;
2965 certs_buf = g_malloc(sb.st_size + 1);
2966 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2967 show_oops(t, "Can't read CA file: %s", strerror(errno));
2968 goto done;
2970 certs_buf[sb.st_size] = '\0';
2972 s = certs_buf;
2973 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2974 certs++;
2975 s += strlen("BEGIN CERTIFICATE");
2978 bzero(&dt, sizeof dt);
2979 dt.data = (unsigned char *)certs_buf;
2980 dt.size = sb.st_size;
2981 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2982 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
2983 GNUTLS_X509_FMT_PEM, 0);
2984 if (certs_read <= 0) {
2985 show_oops(t, "No cert(s) available");
2986 goto done;
2988 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2989 done:
2990 if (c)
2991 g_free(c);
2992 if (certs_buf)
2993 g_free(certs_buf);
2994 if (f)
2995 fclose(f);
2997 return (rv);
3001 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
3003 SoupURI *su = NULL;
3004 struct addrinfo hints, *res = NULL, *ai;
3005 int s = -1, on;
3006 char port[8];
3008 if (uri && !g_str_has_prefix(uri, "https://"))
3009 goto done;
3011 su = soup_uri_new(uri);
3012 if (su == NULL)
3013 goto done;
3014 if (!SOUP_URI_VALID_FOR_HTTP(su))
3015 goto done;
3017 snprintf(port, sizeof port, "%d", su->port);
3018 bzero(&hints, sizeof(struct addrinfo));
3019 hints.ai_flags = AI_CANONNAME;
3020 hints.ai_family = AF_UNSPEC;
3021 hints.ai_socktype = SOCK_STREAM;
3023 if (getaddrinfo(su->host, port, &hints, &res))
3024 goto done;
3026 for (ai = res; ai; ai = ai->ai_next) {
3027 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3028 continue;
3030 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3031 if (s < 0)
3032 goto done;
3033 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3034 sizeof(on)) == -1)
3035 goto done;
3037 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3038 goto done;
3041 if (domain)
3042 strlcpy(domain, su->host, domain_sz);
3043 done:
3044 if (su)
3045 soup_uri_free(su);
3046 if (res)
3047 freeaddrinfo(res);
3049 return (s);
3053 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3055 if (gsession)
3056 gnutls_deinit(gsession);
3057 if (xcred)
3058 gnutls_certificate_free_credentials(xcred);
3060 return (0);
3064 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3065 gnutls_certificate_credentials_t *xc)
3067 gnutls_certificate_credentials_t xcred;
3068 gnutls_session_t gsession;
3069 int rv = 1;
3071 if (gs == NULL || xc == NULL)
3072 goto done;
3074 *gs = NULL;
3075 *xc = NULL;
3077 gnutls_certificate_allocate_credentials(&xcred);
3078 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3079 GNUTLS_X509_FMT_PEM);
3080 gnutls_init(&gsession, GNUTLS_CLIENT);
3081 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3082 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3083 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3084 if ((rv = gnutls_handshake(gsession)) < 0) {
3085 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3087 gnutls_error_is_fatal(rv),
3088 gnutls_strerror_name(rv));
3089 stop_tls(gsession, xcred);
3090 goto done;
3093 gnutls_credentials_type_t cred;
3094 cred = gnutls_auth_get_type(gsession);
3095 if (cred != GNUTLS_CRD_CERTIFICATE) {
3096 stop_tls(gsession, xcred);
3097 goto done;
3100 *gs = gsession;
3101 *xc = xcred;
3102 rv = 0;
3103 done:
3104 return (rv);
3108 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3109 size_t *cert_count)
3111 unsigned int len;
3112 const gnutls_datum_t *cl;
3113 gnutls_x509_crt_t *all_certs;
3114 int i, rv = 1;
3116 if (certs == NULL || cert_count == NULL)
3117 goto done;
3118 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3119 goto done;
3120 cl = gnutls_certificate_get_peers(gsession, &len);
3121 if (len == 0)
3122 goto done;
3124 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3125 for (i = 0; i < len; i++) {
3126 gnutls_x509_crt_init(&all_certs[i]);
3127 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3128 GNUTLS_X509_FMT_PEM < 0)) {
3129 g_free(all_certs);
3130 goto done;
3134 *certs = all_certs;
3135 *cert_count = len;
3136 rv = 0;
3137 done:
3138 return (rv);
3141 void
3142 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3144 int i;
3146 for (i = 0; i < cert_count; i++)
3147 gnutls_x509_crt_deinit(certs[i]);
3148 g_free(certs);
3151 void
3152 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3153 size_t cert_count, char *domain)
3155 size_t cert_buf_sz;
3156 char cert_buf[64 * 1024], file[PATH_MAX];
3157 int i;
3158 FILE *f;
3159 GdkColor color;
3161 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3162 return;
3164 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3165 if ((f = fopen(file, "w")) == NULL) {
3166 show_oops(t, "Can't create cert file %s %s",
3167 file, strerror(errno));
3168 return;
3171 for (i = 0; i < cert_count; i++) {
3172 cert_buf_sz = sizeof cert_buf;
3173 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3174 cert_buf, &cert_buf_sz)) {
3175 show_oops(t, "gnutls_x509_crt_export failed");
3176 goto done;
3178 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3179 show_oops(t, "Can't write certs: %s", strerror(errno));
3180 goto done;
3184 /* not the best spot but oh well */
3185 gdk_color_parse("lightblue", &color);
3186 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3187 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
3188 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &color);
3189 gdk_color_parse(XT_COLOR_BLACK, &color);
3190 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
3191 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &color);
3192 done:
3193 fclose(f);
3197 load_compare_cert(struct tab *t, struct karg *args)
3199 const gchar *uri;
3200 char domain[8182], file[PATH_MAX];
3201 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3202 int s = -1, rv = 1, i;
3203 size_t cert_count;
3204 FILE *f = NULL;
3205 size_t cert_buf_sz;
3206 gnutls_session_t gsession;
3207 gnutls_x509_crt_t *certs;
3208 gnutls_certificate_credentials_t xcred;
3210 if (t == NULL)
3211 return (1);
3213 if ((uri = get_uri(t)) == NULL)
3214 return (1);
3216 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3217 return (1);
3219 /* go ssl/tls */
3220 if (start_tls(t, s, &gsession, &xcred)) {
3221 show_oops(t, "Start TLS failed");
3222 goto done;
3225 /* get certs */
3226 if (get_connection_certs(gsession, &certs, &cert_count)) {
3227 show_oops(t, "Can't get connection certificates");
3228 goto done;
3231 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3232 if ((f = fopen(file, "r")) == NULL)
3233 goto freeit;
3235 for (i = 0; i < cert_count; i++) {
3236 cert_buf_sz = sizeof cert_buf;
3237 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3238 cert_buf, &cert_buf_sz)) {
3239 goto freeit;
3241 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3242 rv = -1; /* critical */
3243 goto freeit;
3245 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3246 rv = -1; /* critical */
3247 goto freeit;
3251 rv = 0;
3252 freeit:
3253 if (f)
3254 fclose(f);
3255 free_connection_certs(certs, cert_count);
3256 done:
3257 /* we close the socket first for speed */
3258 if (s != -1)
3259 close(s);
3260 stop_tls(gsession, xcred);
3262 return (rv);
3266 cert_cmd(struct tab *t, struct karg *args)
3268 const gchar *uri;
3269 char domain[8182];
3270 int s = -1;
3271 size_t cert_count;
3272 gnutls_session_t gsession;
3273 gnutls_x509_crt_t *certs;
3274 gnutls_certificate_credentials_t xcred;
3276 if (t == NULL)
3277 return (1);
3279 if (ssl_ca_file == NULL) {
3280 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3281 return (1);
3284 if ((uri = get_uri(t)) == NULL) {
3285 show_oops(t, "Invalid URI");
3286 return (1);
3289 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3290 show_oops(t, "Invalid certificate URI: %s", uri);
3291 return (1);
3294 /* go ssl/tls */
3295 if (start_tls(t, s, &gsession, &xcred)) {
3296 show_oops(t, "Start TLS failed");
3297 goto done;
3300 /* get certs */
3301 if (get_connection_certs(gsession, &certs, &cert_count)) {
3302 show_oops(t, "get_connection_certs failed");
3303 goto done;
3306 if (args->i & XT_SHOW)
3307 show_certs(t, certs, cert_count, "Certificate Chain");
3308 else if (args->i & XT_SAVE)
3309 save_certs(t, certs, cert_count, domain);
3311 free_connection_certs(certs, cert_count);
3312 done:
3313 /* we close the socket first for speed */
3314 if (s != -1)
3315 close(s);
3316 stop_tls(gsession, xcred);
3318 return (0);
3322 remove_cookie(int index)
3324 int i, rv = 1;
3325 GSList *cf;
3326 SoupCookie *c;
3328 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3330 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3332 for (i = 1; cf; cf = cf->next, i++) {
3333 if (i != index)
3334 continue;
3335 c = cf->data;
3336 print_cookie("remove cookie", c);
3337 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3338 rv = 0;
3339 break;
3342 soup_cookies_free(cf);
3344 return (rv);
3348 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3350 struct domain *d;
3351 char *tmp, *body;
3353 body = g_strdup("");
3355 /* p list */
3356 if (args->i & XT_WL_PERSISTENT) {
3357 tmp = body;
3358 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3359 g_free(tmp);
3360 RB_FOREACH(d, domain_list, wl) {
3361 if (d->handy == 0)
3362 continue;
3363 tmp = body;
3364 body = g_strdup_printf("%s%s<br/>", body, d->d);
3365 g_free(tmp);
3369 /* s list */
3370 if (args->i & XT_WL_SESSION) {
3371 tmp = body;
3372 body = g_strdup_printf("%s<h2>Session</h2>", body);
3373 g_free(tmp);
3374 RB_FOREACH(d, domain_list, wl) {
3375 if (d->handy == 1)
3376 continue;
3377 tmp = body;
3378 body = g_strdup_printf("%s%s<br/>", body, d->d);
3379 g_free(tmp);
3383 tmp = get_html_page(title, body, "", 0);
3384 g_free(body);
3385 if (wl == &js_wl)
3386 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3387 else
3388 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3389 g_free(tmp);
3390 return (0);
3394 wl_save(struct tab *t, struct karg *args, int js)
3396 char file[PATH_MAX];
3397 FILE *f;
3398 char *line = NULL, *lt = NULL;
3399 size_t linelen;
3400 const gchar *uri;
3401 char *dom = NULL, *dom_save = NULL;
3402 struct karg a;
3403 struct domain *d;
3404 GSList *cf;
3405 SoupCookie *ci, *c;
3407 if (t == NULL || args == NULL)
3408 return (1);
3410 if (runtime_settings[0] == '\0')
3411 return (1);
3413 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3414 if ((f = fopen(file, "r+")) == NULL)
3415 return (1);
3417 uri = get_uri(t);
3418 dom = find_domain(uri, 1);
3419 if (uri == NULL || dom == NULL) {
3420 show_oops(t, "Can't add domain to %s white list",
3421 js ? "JavaScript" : "cookie");
3422 goto done;
3425 if (args->i & XT_WL_TOPLEVEL) {
3426 /* save domain */
3427 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3428 show_oops(t, "invalid domain: %s", dom);
3429 goto done;
3431 } else if (args->i & XT_WL_FQDN) {
3432 /* save fqdn */
3433 dom_save = dom;
3434 } else
3435 goto done;
3437 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3439 while (!feof(f)) {
3440 line = fparseln(f, &linelen, NULL, NULL, 0);
3441 if (line == NULL)
3442 continue;
3443 if (!strcmp(line, lt))
3444 goto done;
3445 free(line);
3446 line = NULL;
3449 fprintf(f, "%s\n", lt);
3451 a.i = XT_WL_ENABLE;
3452 a.i |= args->i;
3453 if (js) {
3454 d = wl_find(dom_save, &js_wl);
3455 if (!d) {
3456 settings_add("js_wl", dom_save);
3457 d = wl_find(dom_save, &js_wl);
3459 toggle_js(t, &a);
3460 } else {
3461 d = wl_find(dom_save, &c_wl);
3462 if (!d) {
3463 settings_add("cookie_wl", dom_save);
3464 d = wl_find(dom_save, &c_wl);
3466 toggle_cwl(t, &a);
3468 /* find and add to persistent jar */
3469 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3470 for (;cf; cf = cf->next) {
3471 ci = cf->data;
3472 if (!strcmp(dom_save, ci->domain) ||
3473 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3474 c = soup_cookie_copy(ci);
3475 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3478 soup_cookies_free(cf);
3480 if (d)
3481 d->handy = 1;
3483 done:
3484 if (line)
3485 free(line);
3486 if (dom)
3487 g_free(dom);
3488 if (lt)
3489 g_free(lt);
3490 fclose(f);
3492 return (0);
3496 js_show_wl(struct tab *t, struct karg *args)
3498 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3499 wl_show(t, args, "JavaScript White List", &js_wl);
3501 return (0);
3505 cookie_show_wl(struct tab *t, struct karg *args)
3507 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3508 wl_show(t, args, "Cookie White List", &c_wl);
3510 return (0);
3514 cookie_cmd(struct tab *t, struct karg *args)
3516 if (args->i & XT_SHOW)
3517 wl_show(t, args, "Cookie White List", &c_wl);
3518 else if (args->i & XT_WL_TOGGLE) {
3519 args->i |= XT_WL_RELOAD;
3520 toggle_cwl(t, args);
3521 } else if (args->i & XT_SAVE) {
3522 args->i |= XT_WL_RELOAD;
3523 wl_save(t, args, 0);
3524 } else if (args->i & XT_DELETE)
3525 show_oops(t, "'cookie delete' currently unimplemented");
3527 return (0);
3531 js_cmd(struct tab *t, struct karg *args)
3533 if (args->i & XT_SHOW)
3534 wl_show(t, args, "JavaScript White List", &js_wl);
3535 else if (args->i & XT_SAVE) {
3536 args->i |= XT_WL_RELOAD;
3537 wl_save(t, args, 1);
3538 } else if (args->i & XT_WL_TOGGLE) {
3539 args->i |= XT_WL_RELOAD;
3540 toggle_js(t, args);
3541 } else if (args->i & XT_DELETE)
3542 show_oops(t, "'js delete' currently unimplemented");
3544 return (0);
3548 toplevel_cmd(struct tab *t, struct karg *args)
3550 js_toggle_cb(t->js_toggle, t);
3552 return (0);
3556 add_favorite(struct tab *t, struct karg *args)
3558 char file[PATH_MAX];
3559 FILE *f;
3560 char *line = NULL;
3561 size_t urilen, linelen;
3562 const gchar *uri, *title;
3564 if (t == NULL)
3565 return (1);
3567 /* don't allow adding of xtp pages to favorites */
3568 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3569 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3570 return (1);
3573 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3574 if ((f = fopen(file, "r+")) == NULL) {
3575 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3576 return (1);
3579 title = get_title(t, FALSE);
3580 uri = get_uri(t);
3582 if (title == NULL || uri == NULL) {
3583 show_oops(t, "can't add page to favorites");
3584 goto done;
3587 urilen = strlen(uri);
3589 for (;;) {
3590 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3591 if (feof(f) || ferror(f))
3592 break;
3594 if (linelen == urilen && !strcmp(line, uri))
3595 goto done;
3597 free(line);
3598 line = NULL;
3601 fprintf(f, "\n%s\n%s", title, uri);
3602 done:
3603 if (line)
3604 free(line);
3605 fclose(f);
3607 update_favorite_tabs(NULL);
3609 return (0);
3613 navaction(struct tab *t, struct karg *args)
3615 WebKitWebHistoryItem *item;
3617 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3618 t->tab_id, args->i);
3620 if (t->item) {
3621 if (args->i == XT_NAV_BACK)
3622 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3623 else
3624 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3625 if (item == NULL)
3626 return (XT_CB_PASSTHROUGH);
3627 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3628 t->item = NULL;
3629 return (XT_CB_PASSTHROUGH);
3632 switch (args->i) {
3633 case XT_NAV_BACK:
3634 webkit_web_view_go_back(t->wv);
3635 break;
3636 case XT_NAV_FORWARD:
3637 webkit_web_view_go_forward(t->wv);
3638 break;
3639 case XT_NAV_RELOAD:
3640 webkit_web_view_reload(t->wv);
3641 break;
3642 case XT_NAV_RELOAD_CACHE:
3643 webkit_web_view_reload_bypass_cache(t->wv);
3644 break;
3646 return (XT_CB_PASSTHROUGH);
3650 move(struct tab *t, struct karg *args)
3652 GtkAdjustment *adjust;
3653 double pi, si, pos, ps, upper, lower, max;
3655 switch (args->i) {
3656 case XT_MOVE_DOWN:
3657 case XT_MOVE_UP:
3658 case XT_MOVE_BOTTOM:
3659 case XT_MOVE_TOP:
3660 case XT_MOVE_PAGEDOWN:
3661 case XT_MOVE_PAGEUP:
3662 case XT_MOVE_HALFDOWN:
3663 case XT_MOVE_HALFUP:
3664 adjust = t->adjust_v;
3665 break;
3666 default:
3667 adjust = t->adjust_h;
3668 break;
3671 pos = gtk_adjustment_get_value(adjust);
3672 ps = gtk_adjustment_get_page_size(adjust);
3673 upper = gtk_adjustment_get_upper(adjust);
3674 lower = gtk_adjustment_get_lower(adjust);
3675 si = gtk_adjustment_get_step_increment(adjust);
3676 pi = gtk_adjustment_get_page_increment(adjust);
3677 max = upper - ps;
3679 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3680 "max %f si %f pi %f\n",
3681 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3682 pos, ps, upper, lower, max, si, pi);
3684 switch (args->i) {
3685 case XT_MOVE_DOWN:
3686 case XT_MOVE_RIGHT:
3687 pos += si;
3688 gtk_adjustment_set_value(adjust, MIN(pos, max));
3689 break;
3690 case XT_MOVE_UP:
3691 case XT_MOVE_LEFT:
3692 pos -= si;
3693 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3694 break;
3695 case XT_MOVE_BOTTOM:
3696 case XT_MOVE_FARRIGHT:
3697 gtk_adjustment_set_value(adjust, max);
3698 break;
3699 case XT_MOVE_TOP:
3700 case XT_MOVE_FARLEFT:
3701 gtk_adjustment_set_value(adjust, lower);
3702 break;
3703 case XT_MOVE_PAGEDOWN:
3704 pos += pi;
3705 gtk_adjustment_set_value(adjust, MIN(pos, max));
3706 break;
3707 case XT_MOVE_PAGEUP:
3708 pos -= pi;
3709 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3710 break;
3711 case XT_MOVE_HALFDOWN:
3712 pos += pi / 2;
3713 gtk_adjustment_set_value(adjust, MIN(pos, max));
3714 break;
3715 case XT_MOVE_HALFUP:
3716 pos -= pi / 2;
3717 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3718 break;
3719 default:
3720 return (XT_CB_PASSTHROUGH);
3723 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3725 return (XT_CB_HANDLED);
3728 void
3729 url_set_visibility(void)
3731 struct tab *t;
3733 TAILQ_FOREACH(t, &tabs, entry)
3734 if (show_url == 0) {
3735 gtk_widget_hide(t->toolbar);
3736 focus_webview(t);
3737 } else
3738 gtk_widget_show(t->toolbar);
3741 void
3742 notebook_tab_set_visibility()
3744 if (show_tabs == 0) {
3745 gtk_widget_hide(tab_bar);
3746 gtk_notebook_set_show_tabs(notebook, FALSE);
3747 } else {
3748 if (tab_style == XT_TABS_NORMAL) {
3749 gtk_widget_hide(tab_bar);
3750 gtk_notebook_set_show_tabs(notebook, TRUE);
3751 } else if (tab_style == XT_TABS_COMPACT) {
3752 gtk_widget_show(tab_bar);
3753 gtk_notebook_set_show_tabs(notebook, FALSE);
3758 void
3759 statusbar_set_visibility(void)
3761 struct tab *t;
3763 TAILQ_FOREACH(t, &tabs, entry)
3764 if (show_statusbar == 0) {
3765 gtk_widget_hide(t->statusbar_box);
3766 focus_webview(t);
3767 } else
3768 gtk_widget_show(t->statusbar_box);
3771 void
3772 url_set(struct tab *t, int enable_url_entry)
3774 GdkPixbuf *pixbuf;
3775 int progress;
3777 show_url = enable_url_entry;
3779 if (enable_url_entry) {
3780 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3781 GTK_ENTRY_ICON_PRIMARY, NULL);
3782 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3783 } else {
3784 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3785 GTK_ENTRY_ICON_PRIMARY);
3786 progress =
3787 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3788 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
3789 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3790 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
3791 progress);
3796 fullscreen(struct tab *t, struct karg *args)
3798 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3800 if (t == NULL)
3801 return (XT_CB_PASSTHROUGH);
3803 if (show_url == 0) {
3804 url_set(t, 1);
3805 show_tabs = 1;
3806 } else {
3807 url_set(t, 0);
3808 show_tabs = 0;
3811 url_set_visibility();
3812 notebook_tab_set_visibility();
3814 return (XT_CB_HANDLED);
3818 statustoggle(struct tab *t, struct karg *args)
3820 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3822 if (show_statusbar == 1) {
3823 show_statusbar = 0;
3824 statusbar_set_visibility();
3825 } else if (show_statusbar == 0) {
3826 show_statusbar = 1;
3827 statusbar_set_visibility();
3829 return (XT_CB_HANDLED);
3833 urlaction(struct tab *t, struct karg *args)
3835 int rv = XT_CB_HANDLED;
3837 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3839 if (t == NULL)
3840 return (XT_CB_PASSTHROUGH);
3842 switch (args->i) {
3843 case XT_URL_SHOW:
3844 if (show_url == 0) {
3845 url_set(t, 1);
3846 url_set_visibility();
3848 break;
3849 case XT_URL_HIDE:
3850 if (show_url == 1) {
3851 url_set(t, 0);
3852 url_set_visibility();
3854 break;
3856 return (rv);
3860 tabaction(struct tab *t, struct karg *args)
3862 int rv = XT_CB_HANDLED;
3863 char *url = args->s;
3864 struct undo *u;
3865 struct tab *tt;
3867 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3869 if (t == NULL)
3870 return (XT_CB_PASSTHROUGH);
3872 switch (args->i) {
3873 case XT_TAB_NEW:
3874 if (strlen(url) > 0)
3875 create_new_tab(url, NULL, 1, args->p);
3876 else
3877 create_new_tab(NULL, NULL, 1, args->p);
3878 break;
3879 case XT_TAB_DELETE:
3880 if (args->p < 0)
3881 delete_tab(t);
3882 else
3883 TAILQ_FOREACH(tt, &tabs, entry)
3884 if (tt->tab_id == args->p - 1) {
3885 delete_tab(tt);
3886 break;
3888 break;
3889 case XT_TAB_DELQUIT:
3890 if (gtk_notebook_get_n_pages(notebook) > 1)
3891 delete_tab(t);
3892 else
3893 quit(t, args);
3894 break;
3895 case XT_TAB_OPEN:
3896 if (strlen(url) > 0)
3898 else {
3899 rv = XT_CB_PASSTHROUGH;
3900 goto done;
3902 load_uri(t, url);
3903 break;
3904 case XT_TAB_SHOW:
3905 if (show_tabs == 0) {
3906 show_tabs = 1;
3907 notebook_tab_set_visibility();
3909 break;
3910 case XT_TAB_HIDE:
3911 if (show_tabs == 1) {
3912 show_tabs = 0;
3913 notebook_tab_set_visibility();
3915 break;
3916 case XT_TAB_NEXTSTYLE:
3917 if (tab_style == XT_TABS_NORMAL) {
3918 tab_style = XT_TABS_COMPACT;
3919 recolor_compact_tabs();
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 { "statustoggle", CTRL, 0, GDK_n },
4827 { "command", 0, 0, GDK_colon },
4828 { "qa", CTRL, 0, GDK_q },
4829 { "restart", MOD1, 0, GDK_q },
4830 { "js toggle", CTRL, 0, GDK_j },
4831 { "cookie toggle", MOD1, 0, GDK_c },
4832 { "togglesrc", CTRL, 0, GDK_s },
4833 { "yankuri", 0, 0, GDK_y },
4834 { "pasteuricur", 0, 0, GDK_p },
4835 { "pasteurinew", 0, 0, GDK_P },
4836 { "toplevel toggle", 0, 0, GDK_F4 },
4837 { "help", 0, 0, GDK_F1 },
4839 /* search */
4840 { "searchnext", 0, 0, GDK_n },
4841 { "searchprevious", 0, 0, GDK_N },
4843 /* focus */
4844 { "focusaddress", 0, 0, GDK_F6 },
4845 { "focussearch", 0, 0, GDK_F7 },
4847 /* hinting */
4848 { "hinting", 0, 0, GDK_f },
4850 /* custom stylesheet */
4851 { "userstyle", 0, 0, GDK_i },
4853 /* navigation */
4854 { "goback", 0, 0, GDK_BackSpace },
4855 { "goback", MOD1, 0, GDK_Left },
4856 { "goforward", SHFT, 0, GDK_BackSpace },
4857 { "goforward", MOD1, 0, GDK_Right },
4858 { "reload", 0, 0, GDK_F5 },
4859 { "reload", CTRL, 0, GDK_r },
4860 { "reloadforce", CTRL, 0, GDK_R },
4861 { "reload", CTRL, 0, GDK_l },
4862 { "favorites", MOD1, 1, GDK_f },
4864 /* vertical movement */
4865 { "scrolldown", 0, 0, GDK_j },
4866 { "scrolldown", 0, 0, GDK_Down },
4867 { "scrollup", 0, 0, GDK_Up },
4868 { "scrollup", 0, 0, GDK_k },
4869 { "scrollbottom", 0, 0, GDK_G },
4870 { "scrollbottom", 0, 0, GDK_End },
4871 { "scrolltop", 0, 0, GDK_Home },
4872 { "scrolltop", 0, 0, GDK_g },
4873 { "scrollpagedown", 0, 0, GDK_space },
4874 { "scrollpagedown", CTRL, 0, GDK_f },
4875 { "scrollhalfdown", CTRL, 0, GDK_d },
4876 { "scrollpagedown", 0, 0, GDK_Page_Down },
4877 { "scrollpageup", 0, 0, GDK_Page_Up },
4878 { "scrollpageup", CTRL, 0, GDK_b },
4879 { "scrollhalfup", CTRL, 0, GDK_u },
4880 /* horizontal movement */
4881 { "scrollright", 0, 0, GDK_l },
4882 { "scrollright", 0, 0, GDK_Right },
4883 { "scrollleft", 0, 0, GDK_Left },
4884 { "scrollleft", 0, 0, GDK_h },
4885 { "scrollfarright", 0, 0, GDK_dollar },
4886 { "scrollfarleft", 0, 0, GDK_0 },
4888 /* tabs */
4889 { "tabnew", CTRL, 0, GDK_t },
4890 { "999tabnew", CTRL, 0, GDK_T },
4891 { "tabclose", CTRL, 1, GDK_w },
4892 { "tabundoclose", 0, 0, GDK_U },
4893 { "tabnext 1", CTRL, 0, GDK_1 },
4894 { "tabnext 2", CTRL, 0, GDK_2 },
4895 { "tabnext 3", CTRL, 0, GDK_3 },
4896 { "tabnext 4", CTRL, 0, GDK_4 },
4897 { "tabnext 5", CTRL, 0, GDK_5 },
4898 { "tabnext 6", CTRL, 0, GDK_6 },
4899 { "tabnext 7", CTRL, 0, GDK_7 },
4900 { "tabnext 8", CTRL, 0, GDK_8 },
4901 { "tabnext 9", CTRL, 0, GDK_9 },
4902 { "tabnext 10", CTRL, 0, GDK_0 },
4903 { "tabfirst", CTRL, 0, GDK_less },
4904 { "tablast", CTRL, 0, GDK_greater },
4905 { "tabprevious", CTRL, 0, GDK_Left },
4906 { "tabnext", CTRL, 0, GDK_Right },
4907 { "focusout", CTRL, 0, GDK_minus },
4908 { "focusin", CTRL, 0, GDK_plus },
4909 { "focusin", CTRL, 0, GDK_equal },
4911 /* command aliases (handy when -S flag is used) */
4912 { "promptopen", 0, 0, GDK_F9 },
4913 { "promptopencurrent", 0, 0, GDK_F10 },
4914 { "prompttabnew", 0, 0, GDK_F11 },
4915 { "prompttabnewcurrent",0, 0, GDK_F12 },
4917 TAILQ_HEAD(keybinding_list, key_binding);
4919 void
4920 walk_kb(struct settings *s,
4921 void (*cb)(struct settings *, char *, void *), void *cb_args)
4923 struct key_binding *k;
4924 char str[1024];
4926 if (s == NULL || cb == NULL) {
4927 show_oops(NULL, "walk_kb invalid parameters");
4928 return;
4931 TAILQ_FOREACH(k, &kbl, entry) {
4932 if (k->cmd == NULL)
4933 continue;
4934 str[0] = '\0';
4936 /* sanity */
4937 if (gdk_keyval_name(k->key) == NULL)
4938 continue;
4940 strlcat(str, k->cmd, sizeof str);
4941 strlcat(str, ",", sizeof str);
4943 if (k->mask & GDK_SHIFT_MASK)
4944 strlcat(str, "S-", sizeof str);
4945 if (k->mask & GDK_CONTROL_MASK)
4946 strlcat(str, "C-", sizeof str);
4947 if (k->mask & GDK_MOD1_MASK)
4948 strlcat(str, "M1-", sizeof str);
4949 if (k->mask & GDK_MOD2_MASK)
4950 strlcat(str, "M2-", sizeof str);
4951 if (k->mask & GDK_MOD3_MASK)
4952 strlcat(str, "M3-", sizeof str);
4953 if (k->mask & GDK_MOD4_MASK)
4954 strlcat(str, "M4-", sizeof str);
4955 if (k->mask & GDK_MOD5_MASK)
4956 strlcat(str, "M5-", sizeof str);
4958 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4959 cb(s, str, cb_args);
4963 void
4964 init_keybindings(void)
4966 int i;
4967 struct key_binding *k;
4969 for (i = 0; i < LENGTH(keys); i++) {
4970 k = g_malloc0(sizeof *k);
4971 k->cmd = keys[i].cmd;
4972 k->mask = keys[i].mask;
4973 k->use_in_entry = keys[i].use_in_entry;
4974 k->key = keys[i].key;
4975 TAILQ_INSERT_HEAD(&kbl, k, entry);
4977 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4978 k->cmd ? k->cmd : "unnamed key");
4982 void
4983 keybinding_clearall(void)
4985 struct key_binding *k, *next;
4987 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4988 next = TAILQ_NEXT(k, entry);
4989 if (k->cmd == NULL)
4990 continue;
4992 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4993 k->cmd ? k->cmd : "unnamed key");
4994 TAILQ_REMOVE(&kbl, k, entry);
4995 g_free(k);
5000 keybinding_add(char *cmd, char *key, int use_in_entry)
5002 struct key_binding *k;
5003 guint keyval, mask = 0;
5004 int i;
5006 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5008 /* Keys which are to be used in entry have been prefixed with an
5009 * exclamation mark. */
5010 if (use_in_entry)
5011 key++;
5013 /* find modifier keys */
5014 if (strstr(key, "S-"))
5015 mask |= GDK_SHIFT_MASK;
5016 if (strstr(key, "C-"))
5017 mask |= GDK_CONTROL_MASK;
5018 if (strstr(key, "M1-"))
5019 mask |= GDK_MOD1_MASK;
5020 if (strstr(key, "M2-"))
5021 mask |= GDK_MOD2_MASK;
5022 if (strstr(key, "M3-"))
5023 mask |= GDK_MOD3_MASK;
5024 if (strstr(key, "M4-"))
5025 mask |= GDK_MOD4_MASK;
5026 if (strstr(key, "M5-"))
5027 mask |= GDK_MOD5_MASK;
5029 /* find keyname */
5030 for (i = strlen(key) - 1; i > 0; i--)
5031 if (key[i] == '-')
5032 key = &key[i + 1];
5034 /* validate keyname */
5035 keyval = gdk_keyval_from_name(key);
5036 if (keyval == GDK_VoidSymbol) {
5037 warnx("invalid keybinding name %s", key);
5038 return (1);
5040 /* must run this test too, gtk+ doesn't handle 10 for example */
5041 if (gdk_keyval_name(keyval) == NULL) {
5042 warnx("invalid keybinding name %s", key);
5043 return (1);
5046 /* Remove eventual dupes. */
5047 TAILQ_FOREACH(k, &kbl, entry)
5048 if (k->key == keyval && k->mask == mask) {
5049 TAILQ_REMOVE(&kbl, k, entry);
5050 g_free(k);
5051 break;
5054 /* add keyname */
5055 k = g_malloc0(sizeof *k);
5056 k->cmd = g_strdup(cmd);
5057 k->mask = mask;
5058 k->use_in_entry = use_in_entry;
5059 k->key = keyval;
5061 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5062 k->cmd,
5063 k->mask,
5064 k->use_in_entry,
5065 k->key);
5066 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5067 k->cmd, gdk_keyval_name(keyval));
5069 TAILQ_INSERT_HEAD(&kbl, k, entry);
5071 return (0);
5075 add_kb(struct settings *s, char *entry)
5077 char *kb, *key;
5079 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5081 /* clearall is special */
5082 if (!strcmp(entry, "clearall")) {
5083 keybinding_clearall();
5084 return (0);
5087 kb = strstr(entry, ",");
5088 if (kb == NULL)
5089 return (1);
5090 *kb = '\0';
5091 key = kb + 1;
5093 return (keybinding_add(entry, key, key[0] == '!'));
5096 struct cmd {
5097 char *cmd;
5098 int level;
5099 int (*func)(struct tab *, struct karg *);
5100 int arg;
5101 int type;
5102 } cmds[] = {
5103 { "command", 0, command, ':', 0 },
5104 { "search", 0, command, '/', 0 },
5105 { "searchb", 0, command, '?', 0 },
5106 { "togglesrc", 0, toggle_src, 0, 0 },
5108 /* yanking and pasting */
5109 { "yankuri", 0, yank_uri, 0, 0 },
5110 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5111 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5112 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5114 /* search */
5115 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5116 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5118 /* focus */
5119 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5120 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5122 /* hinting */
5123 { "hinting", 0, hint, 0, 0 },
5125 /* custom stylesheet */
5126 { "userstyle", 0, userstyle, 0, 0 },
5128 /* navigation */
5129 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5130 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5131 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5132 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5134 /* vertical movement */
5135 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5136 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5137 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5138 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5139 { "1", 0, move, XT_MOVE_TOP, 0 },
5140 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5141 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5142 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5143 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5144 /* horizontal movement */
5145 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5146 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5147 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5148 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5151 { "favorites", 0, xtp_page_fl, 0, 0 },
5152 { "fav", 0, xtp_page_fl, 0, 0 },
5153 { "favadd", 0, add_favorite, 0, 0 },
5155 { "qall", 0, quit, 0, 0 },
5156 { "quitall", 0, quit, 0, 0 },
5157 { "w", 0, save_tabs, 0, 0 },
5158 { "wq", 0, save_tabs_and_quit, 0, 0 },
5159 { "help", 0, help, 0, 0 },
5160 { "about", 0, about, 0, 0 },
5161 { "stats", 0, stats, 0, 0 },
5162 { "version", 0, about, 0, 0 },
5164 /* js command */
5165 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5166 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5167 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5168 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5169 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5170 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5171 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5172 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5173 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5174 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5175 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5177 /* cookie command */
5178 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5179 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5180 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5181 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5182 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5183 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5184 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5185 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5186 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5187 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5188 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5190 /* toplevel (domain) command */
5191 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5192 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5194 /* cookie jar */
5195 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5197 /* cert command */
5198 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5199 { "save", 1, cert_cmd, XT_SAVE, 0 },
5200 { "show", 1, cert_cmd, XT_SHOW, 0 },
5202 { "ca", 0, ca_cmd, 0, 0 },
5203 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5204 { "dl", 0, xtp_page_dl, 0, 0 },
5205 { "h", 0, xtp_page_hl, 0, 0 },
5206 { "history", 0, xtp_page_hl, 0, 0 },
5207 { "home", 0, go_home, 0, 0 },
5208 { "restart", 0, restart, 0, 0 },
5209 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5210 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5211 { "statustoggle", 0, statustoggle, 0, 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_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5266 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5267 btn_down = 0;
5269 return (FALSE);
5272 gboolean
5273 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5275 struct karg a;
5277 hide_oops(t);
5278 hide_buffers(t);
5280 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5281 btn_down = 1;
5282 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5283 /* go backward */
5284 a.i = XT_NAV_BACK;
5285 navaction(t, &a);
5287 return (TRUE);
5288 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5289 /* go forward */
5290 a.i = XT_NAV_FORWARD;
5291 navaction(t, &a);
5293 return (TRUE);
5296 return (FALSE);
5299 gboolean
5300 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5302 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5304 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5305 delete_tab(t);
5307 return (FALSE);
5311 * cancel, remove, etc. downloads
5313 void
5314 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5316 struct download find, *d = NULL;
5318 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5320 /* some commands require a valid download id */
5321 if (cmd != XT_XTP_DL_LIST) {
5322 /* lookup download in question */
5323 find.id = id;
5324 d = RB_FIND(download_list, &downloads, &find);
5326 if (d == NULL) {
5327 show_oops(t, "%s: no such download", __func__);
5328 return;
5332 /* decide what to do */
5333 switch (cmd) {
5334 case XT_XTP_DL_CANCEL:
5335 webkit_download_cancel(d->download);
5336 break;
5337 case XT_XTP_DL_REMOVE:
5338 webkit_download_cancel(d->download); /* just incase */
5339 g_object_unref(d->download);
5340 RB_REMOVE(download_list, &downloads, d);
5341 break;
5342 case XT_XTP_DL_LIST:
5343 /* Nothing */
5344 break;
5345 default:
5346 show_oops(t, "%s: unknown command", __func__);
5347 break;
5349 xtp_page_dl(t, NULL);
5353 * Actions on history, only does one thing for now, but
5354 * we provide the function for future actions
5356 void
5357 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5359 struct history *h, *next;
5360 int i = 1;
5362 switch (cmd) {
5363 case XT_XTP_HL_REMOVE:
5364 /* walk backwards, as listed in reverse */
5365 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5366 next = RB_PREV(history_list, &hl, h);
5367 if (id == i) {
5368 RB_REMOVE(history_list, &hl, h);
5369 g_free((gpointer) h->title);
5370 g_free((gpointer) h->uri);
5371 g_free(h);
5372 break;
5374 i++;
5376 break;
5377 case XT_XTP_HL_LIST:
5378 /* Nothing - just xtp_page_hl() below */
5379 break;
5380 default:
5381 show_oops(t, "%s: unknown command", __func__);
5382 break;
5385 xtp_page_hl(t, NULL);
5388 /* remove a favorite */
5389 void
5390 remove_favorite(struct tab *t, int index)
5392 char file[PATH_MAX], *title, *uri = NULL;
5393 char *new_favs, *tmp;
5394 FILE *f;
5395 int i;
5396 size_t len, lineno;
5398 /* open favorites */
5399 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5401 if ((f = fopen(file, "r")) == NULL) {
5402 show_oops(t, "%s: can't open favorites: %s",
5403 __func__, strerror(errno));
5404 return;
5407 /* build a string which will become the new favroites file */
5408 new_favs = g_strdup("");
5410 for (i = 1;;) {
5411 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5412 if (feof(f) || ferror(f))
5413 break;
5414 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5415 if (len == 0) {
5416 free(title);
5417 title = NULL;
5418 continue;
5421 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5422 if (feof(f) || ferror(f)) {
5423 show_oops(t, "%s: can't parse favorites %s",
5424 __func__, strerror(errno));
5425 goto clean;
5429 /* as long as this isn't the one we are deleting add to file */
5430 if (i != index) {
5431 tmp = new_favs;
5432 new_favs = g_strdup_printf("%s%s\n%s\n",
5433 new_favs, title, uri);
5434 g_free(tmp);
5437 free(uri);
5438 uri = NULL;
5439 free(title);
5440 title = NULL;
5441 i++;
5443 fclose(f);
5445 /* write back new favorites file */
5446 if ((f = fopen(file, "w")) == NULL) {
5447 show_oops(t, "%s: can't open favorites: %s",
5448 __func__, strerror(errno));
5449 goto clean;
5452 fwrite(new_favs, strlen(new_favs), 1, f);
5453 fclose(f);
5455 clean:
5456 if (uri)
5457 free(uri);
5458 if (title)
5459 free(title);
5461 g_free(new_favs);
5464 void
5465 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5467 switch (cmd) {
5468 case XT_XTP_FL_LIST:
5469 /* nothing, just the below call to xtp_page_fl() */
5470 break;
5471 case XT_XTP_FL_REMOVE:
5472 remove_favorite(t, arg);
5473 break;
5474 default:
5475 show_oops(t, "%s: invalid favorites command", __func__);
5476 break;
5479 xtp_page_fl(t, NULL);
5482 void
5483 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5485 switch (cmd) {
5486 case XT_XTP_CL_LIST:
5487 /* nothing, just xtp_page_cl() */
5488 break;
5489 case XT_XTP_CL_REMOVE:
5490 remove_cookie(arg);
5491 break;
5492 default:
5493 show_oops(t, "%s: unknown cookie xtp command", __func__);
5494 break;
5497 xtp_page_cl(t, NULL);
5500 /* link an XTP class to it's session key and handler function */
5501 struct xtp_despatch {
5502 uint8_t xtp_class;
5503 char **session_key;
5504 void (*handle_func)(struct tab *, uint8_t, int);
5507 struct xtp_despatch xtp_despatches[] = {
5508 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5509 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5510 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5511 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5512 { XT_XTP_INVALID, NULL, NULL }
5516 * is the url xtp protocol? (xxxt://)
5517 * if so, parse and despatch correct bahvior
5520 parse_xtp_url(struct tab *t, const char *url)
5522 char *dup = NULL, *p, *last;
5523 uint8_t n_tokens = 0;
5524 char *tokens[4] = {NULL, NULL, NULL, ""};
5525 struct xtp_despatch *dsp, *dsp_match = NULL;
5526 uint8_t req_class;
5527 int ret = FALSE;
5530 * tokens array meaning:
5531 * tokens[0] = class
5532 * tokens[1] = session key
5533 * tokens[2] = action
5534 * tokens[3] = optional argument
5537 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5539 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5540 goto clean;
5542 dup = g_strdup(url + strlen(XT_XTP_STR));
5544 /* split out the url */
5545 for ((p = strtok_r(dup, "/", &last)); p;
5546 (p = strtok_r(NULL, "/", &last))) {
5547 if (n_tokens < 4)
5548 tokens[n_tokens++] = p;
5551 /* should be atleast three fields 'class/seskey/command/arg' */
5552 if (n_tokens < 3)
5553 goto clean;
5555 dsp = xtp_despatches;
5556 req_class = atoi(tokens[0]);
5557 while (dsp->xtp_class) {
5558 if (dsp->xtp_class == req_class) {
5559 dsp_match = dsp;
5560 break;
5562 dsp++;
5565 /* did we find one atall? */
5566 if (dsp_match == NULL) {
5567 show_oops(t, "%s: no matching xtp despatch found", __func__);
5568 goto clean;
5571 /* check session key and call despatch function */
5572 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5573 ret = TRUE; /* all is well, this was a valid xtp request */
5574 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5577 clean:
5578 if (dup)
5579 g_free(dup);
5581 return (ret);
5586 void
5587 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5589 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5591 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5593 if (t == NULL) {
5594 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5595 return;
5598 if (uri == NULL) {
5599 show_oops(t, "activate_uri_entry_cb no uri");
5600 return;
5603 uri += strspn(uri, "\t ");
5605 /* if xxxt:// treat specially */
5606 if (parse_xtp_url(t, uri))
5607 return;
5609 /* otherwise continue to load page normally */
5610 load_uri(t, (gchar *)uri);
5611 focus_webview(t);
5614 void
5615 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5617 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5618 char *newuri = NULL;
5619 gchar *enc_search;
5621 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5623 if (t == NULL) {
5624 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5625 return;
5628 if (search_string == NULL) {
5629 show_oops(t, "no search_string");
5630 return;
5633 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5635 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5636 newuri = g_strdup_printf(search_string, enc_search);
5637 g_free(enc_search);
5639 webkit_web_view_load_uri(t->wv, newuri);
5640 focus_webview(t);
5642 if (newuri)
5643 g_free(newuri);
5646 void
5647 check_and_set_js(const gchar *uri, struct tab *t)
5649 struct domain *d = NULL;
5650 int es = 0;
5652 if (uri == NULL || t == NULL)
5653 return;
5655 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5656 es = 0;
5657 else
5658 es = 1;
5660 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5661 es ? "enable" : "disable", uri);
5663 g_object_set(G_OBJECT(t->settings),
5664 "enable-scripts", es, (char *)NULL);
5665 g_object_set(G_OBJECT(t->settings),
5666 "javascript-can-open-windows-automatically", es, (char *)NULL);
5667 webkit_web_view_set_settings(t->wv, t->settings);
5669 button_set_stockid(t->js_toggle,
5670 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5673 void
5674 show_ca_status(struct tab *t, const char *uri)
5676 WebKitWebFrame *frame;
5677 WebKitWebDataSource *source;
5678 WebKitNetworkRequest *request;
5679 SoupMessage *message;
5680 GdkColor color;
5681 gchar *col_str = XT_COLOR_WHITE;
5682 int r;
5684 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5685 ssl_strict_certs, ssl_ca_file, uri);
5687 if (uri == NULL)
5688 goto done;
5689 if (ssl_ca_file == NULL) {
5690 if (g_str_has_prefix(uri, "http://"))
5691 goto done;
5692 if (g_str_has_prefix(uri, "https://")) {
5693 col_str = XT_COLOR_RED;
5694 goto done;
5696 return;
5698 if (g_str_has_prefix(uri, "http://") ||
5699 !g_str_has_prefix(uri, "https://"))
5700 goto done;
5702 frame = webkit_web_view_get_main_frame(t->wv);
5703 source = webkit_web_frame_get_data_source(frame);
5704 request = webkit_web_data_source_get_request(source);
5705 message = webkit_network_request_get_message(request);
5707 if (message && (soup_message_get_flags(message) &
5708 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5709 col_str = XT_COLOR_GREEN;
5710 goto done;
5711 } else {
5712 r = load_compare_cert(t, NULL);
5713 if (r == 0)
5714 col_str = XT_COLOR_BLUE;
5715 else if (r == 1)
5716 col_str = XT_COLOR_YELLOW;
5717 else
5718 col_str = XT_COLOR_RED;
5719 goto done;
5721 done:
5722 if (col_str) {
5723 gdk_color_parse(col_str, &color);
5724 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5726 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5727 gtk_widget_modify_text(t->sbe.statusbar,
5728 GTK_STATE_NORMAL, &color);
5729 gtk_widget_modify_text(t->sbe.position,
5730 GTK_STATE_NORMAL, &color);
5731 gdk_color_parse(XT_COLOR_BLACK, &color);
5732 gtk_widget_modify_base(t->sbe.statusbar,
5733 GTK_STATE_NORMAL, &color);
5734 gtk_widget_modify_base(t->sbe.position,
5735 GTK_STATE_NORMAL, &color);
5736 } else {
5737 gtk_widget_modify_base(t->sbe.statusbar,
5738 GTK_STATE_NORMAL, &color);
5739 gtk_widget_modify_base(t->sbe.position,
5740 GTK_STATE_NORMAL, &color);
5741 gdk_color_parse(XT_COLOR_BLACK, &color);
5742 gtk_widget_modify_text(t->sbe.statusbar,
5743 GTK_STATE_NORMAL, &color);
5744 gtk_widget_modify_text(t->sbe.position,
5745 GTK_STATE_NORMAL, &color);
5750 void
5751 free_favicon(struct tab *t)
5753 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5754 __func__, t->icon_download, t->icon_request);
5756 if (t->icon_request)
5757 g_object_unref(t->icon_request);
5758 if (t->icon_dest_uri)
5759 g_free(t->icon_dest_uri);
5761 t->icon_request = NULL;
5762 t->icon_dest_uri = NULL;
5765 void
5766 xt_icon_from_name(struct tab *t, gchar *name)
5768 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5769 GTK_ENTRY_ICON_PRIMARY, "text-html");
5770 if (show_url == 0)
5771 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5772 GTK_ENTRY_ICON_PRIMARY, "text-html");
5773 else
5774 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5775 GTK_ENTRY_ICON_PRIMARY, NULL);
5778 void
5779 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5781 GdkPixbuf *pb_scaled;
5783 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5784 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5785 GDK_INTERP_BILINEAR);
5786 else
5787 pb_scaled = pb;
5789 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5790 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5791 if (show_url == 0)
5792 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5793 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5794 else
5795 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5796 GTK_ENTRY_ICON_PRIMARY, NULL);
5798 if (pb_scaled != pb)
5799 g_object_unref(pb_scaled);
5802 void
5803 xt_icon_from_file(struct tab *t, char *file)
5805 GdkPixbuf *pb;
5807 if (g_str_has_prefix(file, "file://"))
5808 file += strlen("file://");
5810 pb = gdk_pixbuf_new_from_file(file, NULL);
5811 if (pb) {
5812 xt_icon_from_pixbuf(t, pb);
5813 g_object_unref(pb);
5814 } else
5815 xt_icon_from_name(t, "text-html");
5818 gboolean
5819 is_valid_icon(char *file)
5821 gboolean valid = 0;
5822 const char *mime_type;
5823 GFileInfo *fi;
5824 GFile *gf;
5826 gf = g_file_new_for_path(file);
5827 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5828 NULL, NULL);
5829 mime_type = g_file_info_get_content_type(fi);
5830 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5831 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5832 g_strcmp0(mime_type, "image/png") == 0 ||
5833 g_strcmp0(mime_type, "image/gif") == 0 ||
5834 g_strcmp0(mime_type, "application/octet-stream") == 0;
5835 g_object_unref(fi);
5836 g_object_unref(gf);
5838 return (valid);
5841 void
5842 set_favicon_from_file(struct tab *t, char *file)
5844 struct stat sb;
5846 if (t == NULL || file == NULL)
5847 return;
5849 if (g_str_has_prefix(file, "file://"))
5850 file += strlen("file://");
5851 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5853 if (!stat(file, &sb)) {
5854 if (sb.st_size == 0 || !is_valid_icon(file)) {
5855 /* corrupt icon so trash it */
5856 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5857 __func__, file);
5858 unlink(file);
5859 /* no need to set icon to default here */
5860 return;
5863 xt_icon_from_file(t, file);
5866 void
5867 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5868 WebKitWebView *wv)
5870 WebKitDownloadStatus status = webkit_download_get_status(download);
5871 struct tab *tt = NULL, *t = NULL;
5874 * find the webview instead of passing in the tab as it could have been
5875 * deleted from underneath us.
5877 TAILQ_FOREACH(tt, &tabs, entry) {
5878 if (tt->wv == wv) {
5879 t = tt;
5880 break;
5883 if (t == NULL)
5884 return;
5886 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5887 __func__, t->tab_id, status);
5889 switch (status) {
5890 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5891 /* -1 */
5892 t->icon_download = NULL;
5893 free_favicon(t);
5894 break;
5895 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5896 /* 0 */
5897 break;
5898 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5899 /* 1 */
5900 break;
5901 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5902 /* 2 */
5903 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5904 __func__, t->tab_id);
5905 t->icon_download = NULL;
5906 free_favicon(t);
5907 break;
5908 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5909 /* 3 */
5911 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5912 __func__, t->icon_dest_uri);
5913 set_favicon_from_file(t, t->icon_dest_uri);
5914 /* these will be freed post callback */
5915 t->icon_request = NULL;
5916 t->icon_download = NULL;
5917 break;
5918 default:
5919 break;
5923 void
5924 abort_favicon_download(struct tab *t)
5926 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5928 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
5929 if (t->icon_download) {
5930 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5931 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5932 webkit_download_cancel(t->icon_download);
5933 t->icon_download = NULL;
5934 } else
5935 free_favicon(t);
5936 #endif
5938 xt_icon_from_name(t, "text-html");
5941 void
5942 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5944 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5946 if (uri == NULL || t == NULL)
5947 return;
5949 #if WEBKIT_CHECK_VERSION(1, 4, 0)
5950 /* take icon from WebKitIconDatabase */
5951 GdkPixbuf *pb;
5953 pb = webkit_web_view_get_icon_pixbuf(wv);
5954 if (pb) {
5955 xt_icon_from_pixbuf(t, pb);
5956 g_object_unref(pb);
5957 } else
5958 xt_icon_from_name(t, "text-html");
5959 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
5960 /* download icon to cache dir */
5961 gchar *name_hash, file[PATH_MAX];
5962 struct stat sb;
5964 if (t->icon_request) {
5965 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5966 return;
5969 /* check to see if we got the icon in cache */
5970 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5971 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5972 g_free(name_hash);
5974 if (!stat(file, &sb)) {
5975 if (sb.st_size > 0) {
5976 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5977 __func__, file);
5978 set_favicon_from_file(t, file);
5979 return;
5982 /* corrupt icon so trash it */
5983 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5984 __func__, file);
5985 unlink(file);
5988 /* create download for icon */
5989 t->icon_request = webkit_network_request_new(uri);
5990 if (t->icon_request == NULL) {
5991 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5992 __func__, uri);
5993 return;
5996 t->icon_download = webkit_download_new(t->icon_request);
5997 if (t->icon_download == NULL)
5998 return;
6000 /* we have to free icon_dest_uri later */
6001 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6002 webkit_download_set_destination_uri(t->icon_download,
6003 t->icon_dest_uri);
6005 if (webkit_download_get_status(t->icon_download) ==
6006 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6007 g_object_unref(t->icon_request);
6008 g_free(t->icon_dest_uri);
6009 t->icon_request = NULL;
6010 t->icon_dest_uri = NULL;
6011 return;
6014 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6015 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6017 webkit_download_start(t->icon_download);
6018 #endif
6021 void
6022 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6024 const gchar *uri = NULL, *title = NULL;
6025 struct history *h, find;
6026 const gchar *s_loading;
6027 struct karg a;
6029 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6030 webkit_web_view_get_load_status(wview), get_uri(t) ? get_uri(t) : "NOTHING");
6032 if (t == NULL) {
6033 show_oops(NULL, "notify_load_status_cb invalid parameters");
6034 return;
6037 switch (webkit_web_view_get_load_status(wview)) {
6038 case WEBKIT_LOAD_PROVISIONAL:
6039 /* 0 */
6040 abort_favicon_download(t);
6041 #if GTK_CHECK_VERSION(2, 20, 0)
6042 gtk_widget_show(t->spinner);
6043 gtk_spinner_start(GTK_SPINNER(t->spinner));
6044 #endif
6045 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6047 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6049 /* take focus if we are visible */
6050 focus_webview(t);
6051 t->focus_wv = 1;
6053 break;
6055 case WEBKIT_LOAD_COMMITTED:
6056 /* 1 */
6057 if ((uri = get_uri(t)) != NULL) {
6058 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6060 if (t->status) {
6061 g_free(t->status);
6062 t->status = NULL;
6064 set_status(t, (char *)uri, XT_STATUS_LOADING);
6067 /* check if js white listing is enabled */
6068 if (enable_js_whitelist) {
6069 uri = get_uri(t);
6070 check_and_set_js(uri, t);
6073 if (t->styled)
6074 apply_style(t);
6076 show_ca_status(t, uri);
6078 /* we know enough to autosave the session */
6079 if (session_autosave) {
6080 a.s = NULL;
6081 save_tabs(t, &a);
6083 break;
6085 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6086 /* 3 */
6087 break;
6089 case WEBKIT_LOAD_FINISHED:
6090 /* 2 */
6091 uri = get_uri(t);
6092 if (uri == NULL)
6093 return;
6095 if (!strncmp(uri, "http://", strlen("http://")) ||
6096 !strncmp(uri, "https://", strlen("https://")) ||
6097 !strncmp(uri, "file://", strlen("file://"))) {
6098 find.uri = uri;
6099 h = RB_FIND(history_list, &hl, &find);
6100 if (!h) {
6101 title = get_title(t, FALSE);
6102 h = g_malloc(sizeof *h);
6103 h->uri = g_strdup(uri);
6104 h->title = g_strdup(title);
6105 RB_INSERT(history_list, &hl, h);
6106 completion_add_uri(h->uri);
6107 update_history_tabs(NULL);
6111 set_status(t, (char *)uri, XT_STATUS_URI);
6112 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6113 case WEBKIT_LOAD_FAILED:
6114 /* 4 */
6115 #endif
6116 #if GTK_CHECK_VERSION(2, 20, 0)
6117 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6118 gtk_widget_hide(t->spinner);
6119 #endif
6120 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
6121 if (s_loading && !strcmp(s_loading, "Loading"))
6122 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6123 default:
6124 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6125 break;
6128 if (t->item)
6129 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6130 else
6131 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6132 webkit_web_view_can_go_back(wview));
6134 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6135 webkit_web_view_can_go_forward(wview));
6138 void
6139 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6141 const gchar *title = NULL, *win_title = NULL;
6143 title = get_title(t, FALSE);
6144 win_title = get_title(t, TRUE);
6145 gtk_label_set_text(GTK_LABEL(t->label), title);
6146 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6147 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6148 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6151 void
6152 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6154 run_script(t, JS_HINTING);
6157 void
6158 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6160 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6161 progress == 100 ? 0 : (double)progress / 100);
6162 if (show_url == 0) {
6163 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6164 progress == 100 ? 0 : (double)progress / 100);
6167 update_statusbar_position(NULL, NULL);
6171 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6172 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6173 WebKitWebPolicyDecision *pd, struct tab *t)
6175 char *uri;
6176 WebKitWebNavigationReason reason;
6177 struct domain *d = NULL;
6179 if (t == NULL) {
6180 show_oops(NULL, "webview_npd_cb invalid parameters");
6181 return (FALSE);
6184 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6185 t->ctrl_click,
6186 webkit_network_request_get_uri(request));
6188 uri = (char *)webkit_network_request_get_uri(request);
6190 /* if this is an xtp url, we don't load anything else */
6191 if (parse_xtp_url(t, uri))
6192 return (TRUE);
6194 if (t->ctrl_click) {
6195 t->ctrl_click = 0;
6196 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6197 webkit_web_policy_decision_ignore(pd);
6198 return (TRUE); /* we made the decission */
6202 * This is a little hairy but it comes down to this:
6203 * when we run in whitelist mode we have to assist the browser in
6204 * opening the URL that it would have opened in a new tab.
6206 reason = webkit_web_navigation_action_get_reason(na);
6207 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6208 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6209 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6210 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6211 load_uri(t, uri);
6212 webkit_web_policy_decision_use(pd);
6213 return (TRUE); /* we made the decision */
6216 return (FALSE);
6219 WebKitWebView *
6220 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6222 struct tab *tt;
6223 struct domain *d = NULL;
6224 const gchar *uri;
6225 WebKitWebView *webview = NULL;
6227 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6228 webkit_web_view_get_uri(wv));
6230 if (tabless) {
6231 /* open in current tab */
6232 webview = t->wv;
6233 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6234 uri = webkit_web_view_get_uri(wv);
6235 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6236 return (NULL);
6238 tt = create_new_tab(NULL, NULL, 1, -1);
6239 webview = tt->wv;
6240 } else if (enable_scripts == 1) {
6241 tt = create_new_tab(NULL, NULL, 1, -1);
6242 webview = tt->wv;
6245 return (webview);
6248 gboolean
6249 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6251 const gchar *uri;
6252 struct domain *d = NULL;
6254 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6256 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6257 uri = webkit_web_view_get_uri(wv);
6258 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6259 return (FALSE);
6261 delete_tab(t);
6262 } else if (enable_scripts == 1)
6263 delete_tab(t);
6265 return (TRUE);
6269 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6271 /* we can not eat the event without throwing gtk off so defer it */
6273 /* catch middle click */
6274 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6275 t->ctrl_click = 1;
6276 goto done;
6279 /* catch ctrl click */
6280 if (e->type == GDK_BUTTON_RELEASE &&
6281 CLEAN(e->state) == GDK_CONTROL_MASK)
6282 t->ctrl_click = 1;
6283 else
6284 t->ctrl_click = 0;
6285 done:
6286 return (XT_CB_PASSTHROUGH);
6290 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6292 struct mime_type *m;
6294 m = find_mime_type(mime_type);
6295 if (m == NULL)
6296 return (1);
6297 if (m->mt_download)
6298 return (1);
6300 switch (fork()) {
6301 case -1:
6302 show_oops(t, "can't fork mime handler");
6303 /* NOTREACHED */
6304 case 0:
6305 break;
6306 default:
6307 return (0);
6310 /* child */
6311 execlp(m->mt_action, m->mt_action,
6312 webkit_network_request_get_uri(request), (void *)NULL);
6314 _exit(0);
6316 /* NOTREACHED */
6317 return (0);
6320 const gchar *
6321 get_mime_type(char *file)
6323 const char *mime_type;
6324 GFileInfo *fi;
6325 GFile *gf;
6327 if (g_str_has_prefix(file, "file://"))
6328 file += strlen("file://");
6330 gf = g_file_new_for_path(file);
6331 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6332 NULL, NULL);
6333 mime_type = g_file_info_get_content_type(fi);
6334 g_object_unref(fi);
6335 g_object_unref(gf);
6337 return (mime_type);
6341 run_download_mimehandler(char *mime_type, char *file)
6343 struct mime_type *m;
6345 m = find_mime_type(mime_type);
6346 if (m == NULL)
6347 return (1);
6349 switch (fork()) {
6350 case -1:
6351 show_oops(NULL, "can't fork download mime handler");
6352 return (1);
6353 /* NOTREACHED */
6354 case 0:
6355 break;
6356 default:
6357 return (0);
6360 /* child */
6361 if (g_str_has_prefix(file, "file://"))
6362 file += strlen("file://");
6363 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6365 _exit(0);
6367 /* NOTREACHED */
6368 return (0);
6371 void
6372 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6373 WebKitWebView *wv)
6375 WebKitDownloadStatus status;
6376 const gchar *file = NULL, *mime = NULL;
6378 if (download == NULL)
6379 return;
6380 status = webkit_download_get_status(download);
6381 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6382 return;
6384 file = webkit_download_get_destination_uri(download);
6385 if (file == NULL)
6386 return;
6387 mime = get_mime_type((char *)file);
6388 if (mime == NULL)
6389 return;
6391 run_download_mimehandler((char *)mime, (char *)file);
6395 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6396 WebKitNetworkRequest *request, char *mime_type,
6397 WebKitWebPolicyDecision *decision, struct tab *t)
6399 if (t == NULL) {
6400 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6401 return (FALSE);
6404 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6405 t->tab_id, mime_type);
6407 if (run_mimehandler(t, mime_type, request) == 0) {
6408 webkit_web_policy_decision_ignore(decision);
6409 focus_webview(t);
6410 return (TRUE);
6413 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6414 webkit_web_policy_decision_download(decision);
6415 return (TRUE);
6418 return (FALSE);
6422 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6423 struct tab *t)
6425 struct stat sb;
6426 const gchar *suggested_name;
6427 gchar *filename = NULL;
6428 char *uri = NULL;
6429 struct download *download_entry;
6430 int i, ret = TRUE;
6432 if (wk_download == NULL || t == NULL) {
6433 show_oops(NULL, "%s invalid parameters", __func__);
6434 return (FALSE);
6437 suggested_name = webkit_download_get_suggested_filename(wk_download);
6438 if (suggested_name == NULL)
6439 return (FALSE); /* abort download */
6441 i = 0;
6442 do {
6443 if (filename) {
6444 g_free(filename);
6445 filename = NULL;
6447 if (i) {
6448 g_free(uri);
6449 uri = NULL;
6450 filename = g_strdup_printf("%d%s", i, suggested_name);
6452 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6453 filename : suggested_name);
6454 i++;
6455 } while (!stat(uri + strlen("file://"), &sb));
6457 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6458 "local %s\n", __func__, t->tab_id, filename, uri);
6460 webkit_download_set_destination_uri(wk_download, uri);
6462 if (webkit_download_get_status(wk_download) ==
6463 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6464 show_oops(t, "%s: download failed to start", __func__);
6465 ret = FALSE;
6466 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6467 } else {
6468 /* connect "download first" mime handler */
6469 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6470 G_CALLBACK(download_status_changed_cb), NULL);
6472 download_entry = g_malloc(sizeof(struct download));
6473 download_entry->download = wk_download;
6474 download_entry->tab = t;
6475 download_entry->id = next_download_id++;
6476 RB_INSERT(download_list, &downloads, download_entry);
6477 /* get from history */
6478 g_object_ref(wk_download);
6479 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6480 show_oops(t, "Download of '%s' started...",
6481 basename((char *)webkit_download_get_destination_uri(wk_download)));
6484 if (uri)
6485 g_free(uri);
6487 if (filename)
6488 g_free(filename);
6490 /* sync other download manager tabs */
6491 update_download_tabs(NULL);
6494 * NOTE: never redirect/render the current tab before this
6495 * function returns. This will cause the download to never start.
6497 return (ret); /* start download */
6500 void
6501 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6503 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6505 if (t == NULL) {
6506 show_oops(NULL, "webview_hover_cb");
6507 return;
6510 if (uri)
6511 set_status(t, uri, XT_STATUS_LINK);
6512 else {
6513 if (t->status)
6514 set_status(t, t->status, XT_STATUS_NOTHING);
6518 gboolean
6519 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6521 struct key_binding *k;
6523 TAILQ_FOREACH(k, &kbl, entry)
6524 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6525 if (k->mask == 0) {
6526 if ((e->state & (CTRL | MOD1)) == 0)
6527 return (cmd_execute(t, k->cmd));
6528 } else if ((e->state & k->mask) == k->mask) {
6529 return (cmd_execute(t, k->cmd));
6533 return (XT_CB_PASSTHROUGH);
6537 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6539 char s[2], buf[128];
6540 const char *errstr = NULL;
6542 /* don't use w directly; use t->whatever instead */
6544 if (t == NULL) {
6545 show_oops(NULL, "wv_keypress_after_cb");
6546 return (XT_CB_PASSTHROUGH);
6549 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6550 e->keyval, e->state, t);
6552 if (t->hints_on) {
6553 /* ESC */
6554 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6555 disable_hints(t);
6556 return (XT_CB_HANDLED);
6559 /* RETURN */
6560 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6561 if (errstr) {
6562 /* we have a string */
6563 } else {
6564 /* we have a number */
6565 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6566 t->hint_num);
6567 run_script(t, buf);
6569 disable_hints(t);
6572 /* BACKSPACE */
6573 /* XXX unfuck this */
6574 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6575 if (t->hint_mode == XT_HINT_NUMERICAL) {
6576 /* last input was numerical */
6577 int l;
6578 l = strlen(t->hint_num);
6579 if (l > 0) {
6580 l--;
6581 if (l == 0) {
6582 disable_hints(t);
6583 enable_hints(t);
6584 } else {
6585 t->hint_num[l] = '\0';
6586 goto num;
6589 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6590 /* last input was alphanumerical */
6591 int l;
6592 l = strlen(t->hint_buf);
6593 if (l > 0) {
6594 l--;
6595 if (l == 0) {
6596 disable_hints(t);
6597 enable_hints(t);
6598 } else {
6599 t->hint_buf[l] = '\0';
6600 goto anum;
6603 } else {
6604 /* bogus */
6605 disable_hints(t);
6609 /* numerical input */
6610 if (CLEAN(e->state) == 0 &&
6611 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6612 snprintf(s, sizeof s, "%c", e->keyval);
6613 strlcat(t->hint_num, s, sizeof t->hint_num);
6614 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6615 t->hint_num);
6616 num:
6617 if (errstr) {
6618 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6619 disable_hints(t);
6620 } else {
6621 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6622 t->hint_num);
6623 t->hint_mode = XT_HINT_NUMERICAL;
6624 run_script(t, buf);
6627 /* empty the counter buffer */
6628 bzero(t->hint_buf, sizeof t->hint_buf);
6629 return (XT_CB_HANDLED);
6632 /* alphanumerical input */
6633 if (
6634 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6635 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6636 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6637 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6638 snprintf(s, sizeof s, "%c", e->keyval);
6639 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6640 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6641 t->hint_buf);
6642 anum:
6643 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6644 run_script(t, buf);
6646 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6647 t->hint_buf);
6648 t->hint_mode = XT_HINT_ALPHANUM;
6649 run_script(t, buf);
6651 /* empty the counter buffer */
6652 bzero(t->hint_num, sizeof t->hint_num);
6653 return (XT_CB_HANDLED);
6656 return (XT_CB_HANDLED);
6657 } else {
6658 /* prefix input*/
6659 snprintf(s, sizeof s, "%c", e->keyval);
6660 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6661 cmd_prefix = 10 * cmd_prefix + atoi(s);
6665 return (handle_keypress(t, e, 0));
6669 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6671 hide_oops(t);
6673 /* Hide buffers, if they are visible, with escape. */
6674 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6675 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6676 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6677 hide_buffers(t);
6678 return (XT_CB_HANDLED);
6681 return (XT_CB_PASSTHROUGH);
6685 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6687 const gchar *c = gtk_entry_get_text(w);
6688 GdkColor color;
6689 int forward = TRUE;
6691 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6692 e->keyval, e->state, t);
6694 if (t == NULL) {
6695 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6696 return (XT_CB_PASSTHROUGH);
6699 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6700 e->keyval, e->state, t);
6702 if (c[0] == ':')
6703 goto done;
6704 if (strlen(c) == 1) {
6705 webkit_web_view_unmark_text_matches(t->wv);
6706 goto done;
6709 if (c[0] == '/')
6710 forward = TRUE;
6711 else if (c[0] == '?')
6712 forward = FALSE;
6713 else
6714 goto done;
6716 /* search */
6717 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6718 FALSE) {
6719 /* not found, mark red */
6720 gdk_color_parse(XT_COLOR_RED, &color);
6721 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6722 /* unmark and remove selection */
6723 webkit_web_view_unmark_text_matches(t->wv);
6724 /* my kingdom for a way to unselect text in webview */
6725 } else {
6726 /* found, highlight all */
6727 webkit_web_view_unmark_text_matches(t->wv);
6728 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6729 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6730 gdk_color_parse(XT_COLOR_WHITE, &color);
6731 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6733 done:
6734 return (XT_CB_PASSTHROUGH);
6737 gboolean
6738 match_uri(const gchar *uri, const gchar *key) {
6739 gchar *voffset;
6740 size_t len;
6741 gboolean match = FALSE;
6743 len = strlen(key);
6745 if (!strncmp(key, uri, len))
6746 match = TRUE;
6747 else {
6748 voffset = strstr(uri, "/") + 2;
6749 if (!strncmp(key, voffset, len))
6750 match = TRUE;
6751 else if (g_str_has_prefix(voffset, "www.")) {
6752 voffset = voffset + strlen("www.");
6753 if (!strncmp(key, voffset, len))
6754 match = TRUE;
6758 return (match);
6761 void
6762 cmd_getlist(int id, char *key)
6764 int i, dep, c = 0;
6765 struct history *h;
6767 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6768 RB_FOREACH_REVERSE(h, history_list, &hl)
6769 if (match_uri(h->uri, key)) {
6770 cmd_status.list[c] = (char *)h->uri;
6771 if (++c > 255)
6772 break;
6775 cmd_status.len = c;
6776 return;
6779 dep = (id == -1) ? 0 : cmds[id].level + 1;
6781 for (i = id + 1; i < LENGTH(cmds); i++) {
6782 if (cmds[i].level < dep)
6783 break;
6784 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6785 cmd_status.list[c++] = cmds[i].cmd;
6789 cmd_status.len = c;
6792 char *
6793 cmd_getnext(int dir)
6795 cmd_status.index += dir;
6797 if (cmd_status.index < 0)
6798 cmd_status.index = cmd_status.len - 1;
6799 else if (cmd_status.index >= cmd_status.len)
6800 cmd_status.index = 0;
6802 return cmd_status.list[cmd_status.index];
6806 cmd_tokenize(char *s, char *tokens[])
6808 int i = 0;
6809 char *tok, *last;
6810 size_t len = strlen(s);
6811 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6813 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6814 tokens[i] = tok;
6816 if (blank && i < 3)
6817 tokens[i++] = "";
6819 return (i);
6822 void
6823 cmd_complete(struct tab *t, char *str, int dir)
6825 GtkEntry *w = GTK_ENTRY(t->cmd);
6826 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6827 char *tok, *match, *s = g_strdup(str);
6828 char *tokens[3];
6829 char res[XT_MAX_URL_LENGTH + 32] = ":";
6830 char *sc = s;
6832 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6834 /* copy prefix*/
6835 for (i = 0; isdigit(s[i]); i++)
6836 res[i + 1] = s[i];
6838 for (; isspace(s[i]); i++)
6839 res[i + 1] = s[i];
6841 s += i;
6843 levels = cmd_tokenize(s, tokens);
6845 for (i = 0; i < levels - 1; i++) {
6846 tok = tokens[i];
6847 matchcount = 0;
6848 for (j = c; j < LENGTH(cmds); j++) {
6849 if (cmds[j].level < dep)
6850 break;
6851 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6852 matchcount++;
6853 c = j + 1;
6854 if (strlen(tok) == strlen(cmds[j].cmd)) {
6855 matchcount = 1;
6856 break;
6861 if (matchcount == 1) {
6862 strlcat(res, tok, sizeof res);
6863 strlcat(res, " ", sizeof res);
6864 dep++;
6865 } else {
6866 g_free(sc);
6867 return;
6870 parent = c - 1;
6873 if (cmd_status.index == -1)
6874 cmd_getlist(parent, tokens[i]);
6876 if (cmd_status.len > 0) {
6877 match = cmd_getnext(dir);
6878 strlcat(res, match, sizeof res);
6879 gtk_entry_set_text(w, res);
6880 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6883 g_free(sc);
6886 gboolean
6887 cmd_execute(struct tab *t, char *str)
6889 struct cmd *cmd = NULL;
6890 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6891 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6892 struct karg arg = {0, NULL, -1};
6893 int rv = XT_CB_PASSTHROUGH;
6895 sc = s;
6897 /* copy prefix*/
6898 for (j = 0; j<3 && isdigit(s[j]); j++)
6899 prefixstr[j]=s[j];
6901 prefixstr[j]='\0';
6903 s += j;
6904 while (isspace(s[0]))
6905 s++;
6907 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6908 prefix = atoi(prefixstr);
6909 else
6910 s = sc;
6912 for (tok = strtok_r(s, " ", &last); tok;
6913 tok = strtok_r(NULL, " ", &last)) {
6914 matchcount = 0;
6915 for (j = c; j < LENGTH(cmds); j++) {
6916 if (cmds[j].level < dep)
6917 break;
6918 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6919 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6920 matchcount++;
6921 c = j + 1;
6922 cmd = &cmds[j];
6923 if (len == strlen(cmds[j].cmd)) {
6924 matchcount = 1;
6925 break;
6929 if (matchcount == 1) {
6930 if (cmd->type > 0)
6931 goto execute_cmd;
6932 dep++;
6933 } else {
6934 show_oops(t, "Invalid command: %s", str);
6935 goto done;
6938 execute_cmd:
6939 arg.i = cmd->arg;
6941 if (prefix != -1)
6942 arg.p = prefix;
6943 else if (cmd_prefix > 0)
6944 arg.p = cmd_prefix;
6946 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6947 show_oops(t, "No prefix allowed: %s", str);
6948 goto done;
6950 if (cmd->type > 1)
6951 arg.s = last ? g_strdup(last) : g_strdup("");
6952 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6953 arg.p = atoi(arg.s);
6954 if (arg.p <= 0) {
6955 if (arg.s[0]=='0')
6956 show_oops(t, "Zero count");
6957 else
6958 show_oops(t, "Trailing characters");
6959 goto done;
6963 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6965 cmd->func(t, &arg);
6967 rv = XT_CB_HANDLED;
6968 done:
6969 if (j > 0)
6970 cmd_prefix = 0;
6971 g_free(sc);
6972 if (arg.s)
6973 g_free(arg.s);
6975 return (rv);
6979 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6981 if (t == NULL) {
6982 show_oops(NULL, "entry_key_cb invalid parameters");
6983 return (XT_CB_PASSTHROUGH);
6986 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6987 e->keyval, e->state, t);
6989 hide_oops(t);
6991 if (e->keyval == GDK_Escape) {
6992 /* don't use focus_webview(t) because we want to type :cmds */
6993 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6996 return (handle_keypress(t, e, 1));
7000 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7002 int rv = XT_CB_HANDLED;
7003 const gchar *c = gtk_entry_get_text(w);
7005 if (t == NULL) {
7006 show_oops(NULL, "cmd_keypress_cb parameters");
7007 return (XT_CB_PASSTHROUGH);
7010 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7011 e->keyval, e->state, t);
7013 /* sanity */
7014 if (c == NULL)
7015 e->keyval = GDK_Escape;
7016 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7017 e->keyval = GDK_Escape;
7019 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
7020 cmd_status.index = -1;
7022 switch (e->keyval) {
7023 case GDK_Tab:
7024 if (c[0] == ':')
7025 cmd_complete(t, (char *)&c[1], 1);
7026 goto done;
7027 case GDK_ISO_Left_Tab:
7028 if (c[0] == ':')
7029 cmd_complete(t, (char *)&c[1], -1);
7031 goto done;
7032 case GDK_BackSpace:
7033 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7034 break;
7035 /* FALLTHROUGH */
7036 case GDK_Escape:
7037 hide_cmd(t);
7038 focus_webview(t);
7040 /* cancel search */
7041 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7042 webkit_web_view_unmark_text_matches(t->wv);
7043 goto done;
7046 rv = XT_CB_PASSTHROUGH;
7047 done:
7048 return (rv);
7052 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7054 if (t == NULL) {
7055 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7056 return (XT_CB_PASSTHROUGH);
7058 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7060 hide_cmd(t);
7061 hide_oops(t);
7063 if (show_url == 0 || t->focus_wv)
7064 focus_webview(t);
7065 else
7066 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7068 return (XT_CB_PASSTHROUGH);
7071 void
7072 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7074 char *s;
7075 const gchar *c = gtk_entry_get_text(entry);
7077 if (t == NULL) {
7078 show_oops(NULL, "cmd_activate_cb invalid parameters");
7079 return;
7082 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7084 hide_cmd(t);
7086 /* sanity */
7087 if (c == NULL)
7088 goto done;
7089 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7090 goto done;
7091 if (strlen(c) < 2)
7092 goto done;
7093 s = (char *)&c[1];
7095 if (c[0] == '/' || c[0] == '?') {
7096 if (t->search_text) {
7097 g_free(t->search_text);
7098 t->search_text = NULL;
7101 t->search_text = g_strdup(s);
7102 if (global_search)
7103 g_free(global_search);
7104 global_search = g_strdup(s);
7105 t->search_forward = c[0] == '/';
7107 goto done;
7110 cmd_execute(t, s);
7112 done:
7113 return;
7116 void
7117 backward_cb(GtkWidget *w, struct tab *t)
7119 struct karg a;
7121 if (t == NULL) {
7122 show_oops(NULL, "backward_cb invalid parameters");
7123 return;
7126 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7128 a.i = XT_NAV_BACK;
7129 navaction(t, &a);
7132 void
7133 forward_cb(GtkWidget *w, struct tab *t)
7135 struct karg a;
7137 if (t == NULL) {
7138 show_oops(NULL, "forward_cb invalid parameters");
7139 return;
7142 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7144 a.i = XT_NAV_FORWARD;
7145 navaction(t, &a);
7148 void
7149 home_cb(GtkWidget *w, struct tab *t)
7151 if (t == NULL) {
7152 show_oops(NULL, "home_cb invalid parameters");
7153 return;
7156 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7158 load_uri(t, home);
7161 void
7162 stop_cb(GtkWidget *w, struct tab *t)
7164 WebKitWebFrame *frame;
7166 if (t == NULL) {
7167 show_oops(NULL, "stop_cb invalid parameters");
7168 return;
7171 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7173 frame = webkit_web_view_get_main_frame(t->wv);
7174 if (frame == NULL) {
7175 show_oops(t, "stop_cb: no frame");
7176 return;
7179 webkit_web_frame_stop_loading(frame);
7180 abort_favicon_download(t);
7183 void
7184 setup_webkit(struct tab *t)
7186 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7187 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7188 FALSE, (char *)NULL);
7189 else
7190 warnx("webkit does not have \"enable-dns-prefetching\" property");
7191 g_object_set(G_OBJECT(t->settings),
7192 "user-agent", t->user_agent, (char *)NULL);
7193 g_object_set(G_OBJECT(t->settings),
7194 "enable-scripts", enable_scripts, (char *)NULL);
7195 g_object_set(G_OBJECT(t->settings),
7196 "enable-plugins", enable_plugins, (char *)NULL);
7197 g_object_set(G_OBJECT(t->settings),
7198 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7199 g_object_set(G_OBJECT(t->settings),
7200 "enable-html5-database", FALSE, (char *)NULL);
7201 g_object_set(G_OBJECT(t->settings),
7202 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7203 g_object_set(G_OBJECT(t->settings),
7204 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7205 g_object_set(G_OBJECT(t->settings),
7206 "spell_checking_languages", spell_check_languages, (char *)NULL);
7207 g_object_set(G_OBJECT(t->wv),
7208 "full-content-zoom", TRUE, (char *)NULL);
7209 adjustfont_webkit(t, XT_FONT_SET);
7211 webkit_web_view_set_settings(t->wv, t->settings);
7214 gboolean
7215 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7217 struct tab *ti, *t = NULL;
7218 gdouble view_size, value, max;
7219 gchar *position;
7221 TAILQ_FOREACH(ti, &tabs, entry)
7222 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7223 t = ti;
7224 break;
7227 if (t == NULL)
7228 return FALSE;
7230 if (adjustment == NULL)
7231 adjustment = gtk_scrolled_window_get_vadjustment(
7232 GTK_SCROLLED_WINDOW(t->browser_win));
7234 view_size = gtk_adjustment_get_page_size(adjustment);
7235 value = gtk_adjustment_get_value(adjustment);
7236 max = gtk_adjustment_get_upper(adjustment) - view_size;
7238 if (max == 0)
7239 position = g_strdup("All");
7240 else if (value == max)
7241 position = g_strdup("Bot");
7242 else if (value == 0)
7243 position = g_strdup("Top");
7244 else
7245 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7247 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7248 g_free(position);
7250 return (TRUE);
7253 GtkWidget *
7254 create_browser(struct tab *t)
7256 GtkWidget *w;
7257 gchar *strval;
7258 GtkAdjustment *adjustment;
7260 if (t == NULL) {
7261 show_oops(NULL, "create_browser invalid parameters");
7262 return (NULL);
7265 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7266 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7267 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7268 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7270 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7271 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7272 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7274 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7275 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7277 /* set defaults */
7278 t->settings = webkit_web_settings_new();
7280 if (user_agent == NULL) {
7281 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7282 (char *)NULL);
7283 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7284 g_free(strval);
7285 } else
7286 t->user_agent = g_strdup(user_agent);
7288 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7290 adjustment =
7291 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7292 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7293 G_CALLBACK(update_statusbar_position), NULL);
7295 setup_webkit(t);
7297 return (w);
7300 GtkWidget *
7301 create_window(void)
7303 GtkWidget *w;
7305 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7306 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7307 gtk_widget_set_name(w, "xxxterm");
7308 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7309 g_signal_connect(G_OBJECT(w), "delete_event",
7310 G_CALLBACK (gtk_main_quit), NULL);
7312 return (w);
7315 GtkWidget *
7316 create_kiosk_toolbar(struct tab *t)
7318 GtkWidget *toolbar = NULL, *b;
7320 b = gtk_hbox_new(FALSE, 0);
7321 toolbar = b;
7322 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7324 /* backward button */
7325 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7326 gtk_widget_set_sensitive(t->backward, FALSE);
7327 g_signal_connect(G_OBJECT(t->backward), "clicked",
7328 G_CALLBACK(backward_cb), t);
7329 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7331 /* forward button */
7332 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7333 gtk_widget_set_sensitive(t->forward, FALSE);
7334 g_signal_connect(G_OBJECT(t->forward), "clicked",
7335 G_CALLBACK(forward_cb), t);
7336 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7338 /* home button */
7339 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7340 gtk_widget_set_sensitive(t->gohome, true);
7341 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7342 G_CALLBACK(home_cb), t);
7343 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7345 /* create widgets but don't use them */
7346 t->uri_entry = gtk_entry_new();
7347 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7348 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7349 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7351 return (toolbar);
7354 GtkWidget *
7355 create_toolbar(struct tab *t)
7357 GtkWidget *toolbar = NULL, *b, *eb1;
7359 b = gtk_hbox_new(FALSE, 0);
7360 toolbar = b;
7361 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7363 if (fancy_bar) {
7364 /* backward button */
7365 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7366 gtk_widget_set_sensitive(t->backward, FALSE);
7367 g_signal_connect(G_OBJECT(t->backward), "clicked",
7368 G_CALLBACK(backward_cb), t);
7369 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7371 /* forward button */
7372 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7373 gtk_widget_set_sensitive(t->forward, FALSE);
7374 g_signal_connect(G_OBJECT(t->forward), "clicked",
7375 G_CALLBACK(forward_cb), t);
7376 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7377 FALSE, 0);
7379 /* stop button */
7380 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7381 gtk_widget_set_sensitive(t->stop, FALSE);
7382 g_signal_connect(G_OBJECT(t->stop), "clicked",
7383 G_CALLBACK(stop_cb), t);
7384 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7385 FALSE, 0);
7387 /* JS button */
7388 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7389 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7390 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7391 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7392 G_CALLBACK(js_toggle_cb), t);
7393 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7396 t->uri_entry = gtk_entry_new();
7397 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7398 G_CALLBACK(activate_uri_entry_cb), t);
7399 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7400 G_CALLBACK(entry_key_cb), t);
7401 completion_add(t);
7402 eb1 = gtk_hbox_new(FALSE, 0);
7403 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7404 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7405 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7407 /* search entry */
7408 if (fancy_bar && search_string) {
7409 GtkWidget *eb2;
7410 t->search_entry = gtk_entry_new();
7411 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7412 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7413 G_CALLBACK(activate_search_entry_cb), t);
7414 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7415 G_CALLBACK(entry_key_cb), t);
7416 gtk_widget_set_size_request(t->search_entry, -1, -1);
7417 eb2 = gtk_hbox_new(FALSE, 0);
7418 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7419 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7421 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7423 return (toolbar);
7426 GtkWidget *
7427 create_buffers(struct tab *t)
7429 GtkCellRenderer *renderer;
7430 GtkWidget *view;
7432 view = gtk_tree_view_new();
7434 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7436 renderer = gtk_cell_renderer_text_new();
7437 gtk_tree_view_insert_column_with_attributes
7438 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7440 renderer = gtk_cell_renderer_text_new();
7441 gtk_tree_view_insert_column_with_attributes
7442 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE, NULL);
7444 gtk_tree_view_set_model
7445 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7447 return view;
7450 void
7451 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7452 GtkTreeViewColumn *col, struct tab *t)
7454 GtkTreeIter iter;
7455 guint id;
7457 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7459 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path)) {
7460 gtk_tree_model_get
7461 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7462 set_current_tab(id - 1);
7465 hide_buffers(t);
7468 /* after tab reordering/creation/removal */
7469 void
7470 recalc_tabs(void)
7472 struct tab *t;
7473 int maxid = 0;
7475 TAILQ_FOREACH(t, &tabs, entry) {
7476 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7477 if (t->tab_id > maxid)
7478 maxid = t->tab_id;
7480 gtk_widget_show(t->tab_elems.sep);
7483 TAILQ_FOREACH(t, &tabs, entry) {
7484 if (t->tab_id == maxid) {
7485 gtk_widget_hide(t->tab_elems.sep);
7486 break;
7491 /* after active tab change */
7492 void
7493 recolor_compact_tabs(void)
7495 struct tab *t;
7496 int curid = 0;
7497 GdkColor color;
7499 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7500 TAILQ_FOREACH(t, &tabs, entry)
7501 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7503 curid = gtk_notebook_get_current_page(notebook);
7504 TAILQ_FOREACH(t, &tabs, entry)
7505 if (t->tab_id == curid) {
7506 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7507 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7508 break;
7512 void
7513 set_current_tab(int page_num)
7515 gtk_notebook_set_current_page(notebook, page_num);
7516 recolor_compact_tabs();
7520 undo_close_tab_save(struct tab *t)
7522 int m, n;
7523 const gchar *uri;
7524 struct undo *u1, *u2;
7525 GList *items;
7526 WebKitWebHistoryItem *item;
7528 if ((uri = get_uri(t)) == NULL)
7529 return (1);
7531 u1 = g_malloc0(sizeof(struct undo));
7532 u1->uri = g_strdup(uri);
7534 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7536 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7537 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7538 u1->back = n;
7540 /* forward history */
7541 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7543 while (items) {
7544 item = items->data;
7545 u1->history = g_list_prepend(u1->history,
7546 webkit_web_history_item_copy(item));
7547 items = g_list_next(items);
7550 /* current item */
7551 if (m) {
7552 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7553 u1->history = g_list_prepend(u1->history,
7554 webkit_web_history_item_copy(item));
7557 /* back history */
7558 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7560 while (items) {
7561 item = items->data;
7562 u1->history = g_list_prepend(u1->history,
7563 webkit_web_history_item_copy(item));
7564 items = g_list_next(items);
7567 TAILQ_INSERT_HEAD(&undos, u1, entry);
7569 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7570 u2 = TAILQ_LAST(&undos, undo_tailq);
7571 TAILQ_REMOVE(&undos, u2, entry);
7572 g_free(u2->uri);
7573 g_list_free(u2->history);
7574 g_free(u2);
7575 } else
7576 undo_count++;
7578 return (0);
7581 void
7582 delete_tab(struct tab *t)
7584 struct karg a;
7586 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7588 if (t == NULL)
7589 return;
7591 TAILQ_REMOVE(&tabs, t, entry);
7593 /* Halt all webkit activity. */
7594 abort_favicon_download(t);
7595 webkit_web_view_stop_loading(t->wv);
7597 /* Save the tab, so we can undo the close. */
7598 undo_close_tab_save(t);
7600 if (browser_mode == XT_BM_KIOSK) {
7601 gtk_widget_destroy(t->uri_entry);
7602 gtk_widget_destroy(t->stop);
7603 gtk_widget_destroy(t->js_toggle);
7606 gtk_widget_destroy(t->tab_elems.eventbox);
7607 gtk_widget_destroy(t->vbox);
7609 g_free(t->user_agent);
7610 g_free(t->stylesheet);
7611 g_free(t);
7613 if (TAILQ_EMPTY(&tabs)) {
7614 if (browser_mode == XT_BM_KIOSK)
7615 create_new_tab(home, NULL, 1, -1);
7616 else
7617 create_new_tab(NULL, NULL, 1, -1);
7620 /* recreate session */
7621 if (session_autosave) {
7622 a.s = NULL;
7623 save_tabs(t, &a);
7626 recalc_tabs();
7627 recolor_compact_tabs();
7630 void
7631 adjustfont_webkit(struct tab *t, int adjust)
7633 gfloat zoom;
7635 if (t == NULL) {
7636 show_oops(NULL, "adjustfont_webkit invalid parameters");
7637 return;
7640 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7641 if (adjust == XT_FONT_SET) {
7642 t->font_size = default_font_size;
7643 zoom = default_zoom_level;
7644 t->font_size += adjust;
7645 g_object_set(G_OBJECT(t->settings), "default-font-size",
7646 t->font_size, (char *)NULL);
7647 g_object_get(G_OBJECT(t->settings), "default-font-size",
7648 &t->font_size, (char *)NULL);
7649 } else {
7650 t->font_size += adjust;
7651 zoom += adjust/25.0;
7652 if (zoom < 0.0) {
7653 zoom = 0.04;
7656 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7657 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7660 gboolean
7661 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
7663 struct tab *t = (struct tab *) data;
7665 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
7667 switch (event->button) {
7668 case 1:
7669 set_current_tab(t->tab_id);
7670 break;
7671 case 2:
7672 delete_tab(t);
7673 break;
7676 return TRUE;
7679 void
7680 append_tab(struct tab *t)
7682 if (t == NULL)
7683 return;
7685 TAILQ_INSERT_TAIL(&tabs, t, entry);
7686 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7689 struct tab *
7690 create_new_tab(char *title, struct undo *u, int focus, int position)
7692 struct tab *t;
7693 int load = 1, id;
7694 GtkWidget *b, *bb;
7695 WebKitWebHistoryItem *item;
7696 GList *items;
7697 GdkColor color;
7699 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7701 if (tabless && !TAILQ_EMPTY(&tabs)) {
7702 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7703 return (NULL);
7706 t = g_malloc0(sizeof *t);
7708 if (title == NULL) {
7709 title = "(untitled)";
7710 load = 0;
7713 t->vbox = gtk_vbox_new(FALSE, 0);
7715 /* label + button for tab */
7716 b = gtk_hbox_new(FALSE, 0);
7717 t->tab_content = b;
7719 #if GTK_CHECK_VERSION(2, 20, 0)
7720 t->spinner = gtk_spinner_new();
7721 #endif
7722 t->label = gtk_label_new(title);
7723 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7724 gtk_widget_set_size_request(t->label, 100, 0);
7725 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7726 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7727 gtk_widget_set_size_request(b, 130, 0);
7729 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7730 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7731 #if GTK_CHECK_VERSION(2, 20, 0)
7732 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7733 #endif
7735 /* toolbar */
7736 if (browser_mode == XT_BM_KIOSK)
7737 t->toolbar = create_kiosk_toolbar(t);
7738 else
7739 t->toolbar = create_toolbar(t);
7741 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7743 /* browser */
7744 t->browser_win = create_browser(t);
7745 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7747 /* oops message for user feedback */
7748 t->oops = gtk_entry_new();
7749 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7750 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7751 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7752 gdk_color_parse(XT_COLOR_RED, &color);
7753 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7754 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7755 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
7757 /* command entry */
7758 t->cmd = gtk_entry_new();
7759 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7760 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7761 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7762 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7764 /* status bar */
7765 t->statusbar_box = gtk_hbox_new(FALSE, 0);
7767 t->sbe.statusbar = gtk_entry_new();
7768 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
7769 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
7770 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
7771 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
7773 t->sbe.position = gtk_entry_new();
7774 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.position), NULL);
7775 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.position), FALSE);
7776 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.position), FALSE);
7777 gtk_widget_modify_font(GTK_WIDGET(t->sbe.position), statusbar_font);
7778 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.position), 1.0);
7779 gtk_widget_set_size_request(t->sbe.position, 40, -1);
7781 gdk_color_parse(XT_COLOR_BLACK, &color);
7782 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
7783 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &color);
7784 gdk_color_parse(XT_COLOR_WHITE, &color);
7785 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &color);
7786 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &color);
7788 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
7789 TRUE, FALSE);
7790 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.position, FALSE,
7791 FALSE, FALSE);
7793 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
7795 /* buffer list */
7796 t->buffers = create_buffers(t);
7797 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
7799 /* xtp meaning is normal by default */
7800 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7802 /* set empty favicon */
7803 xt_icon_from_name(t, "text-html");
7805 /* and show it all */
7806 gtk_widget_show_all(b);
7807 gtk_widget_show_all(t->vbox);
7809 /* compact tab bar */
7810 t->tab_elems.label = gtk_label_new(title);
7811 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
7812 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
7813 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
7814 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
7816 t->tab_elems.eventbox = gtk_event_box_new();
7817 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
7818 t->tab_elems.sep = gtk_vseparator_new();
7820 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
7821 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
7822 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7823 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7824 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
7825 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
7827 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
7828 TRUE, 0);
7829 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
7830 FALSE, 0);
7831 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
7832 t->tab_elems.box);
7834 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
7835 TRUE, 0);
7836 gtk_widget_show_all(t->tab_elems.eventbox);
7838 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7839 append_tab(t);
7840 else {
7841 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7842 if (id > gtk_notebook_get_n_pages(notebook))
7843 append_tab(t);
7844 else {
7845 TAILQ_INSERT_TAIL(&tabs, t, entry);
7846 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7847 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, id);
7848 recalc_tabs();
7852 #if GTK_CHECK_VERSION(2, 20, 0)
7853 /* turn spinner off if we are a new tab without uri */
7854 if (!load) {
7855 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7856 gtk_widget_hide(t->spinner);
7858 #endif
7859 /* make notebook tabs reorderable */
7860 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7862 /* compact tabs clickable */
7863 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
7864 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
7866 g_object_connect(G_OBJECT(t->cmd),
7867 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7868 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7869 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7870 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7871 (char *)NULL);
7873 /* reuse wv_button_cb to hide oops */
7874 g_object_connect(G_OBJECT(t->oops),
7875 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7876 (char *)NULL);
7878 g_signal_connect(t->buffers,
7879 "row-activated", G_CALLBACK(row_activated_cb), t);
7880 g_object_connect(G_OBJECT(t->buffers),
7881 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
7883 g_object_connect(G_OBJECT(t->wv),
7884 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7885 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7886 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7887 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7888 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7889 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7890 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7891 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7892 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7893 "signal::event", G_CALLBACK(webview_event_cb), t,
7894 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7895 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7896 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7897 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7898 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
7899 (char *)NULL);
7900 g_signal_connect(t->wv,
7901 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7902 g_signal_connect(t->wv,
7903 "notify::title", G_CALLBACK(notify_title_cb), t);
7905 /* hijack the unused keys as if we were the browser */
7906 g_object_connect(G_OBJECT(t->toolbar),
7907 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7908 (char *)NULL);
7910 g_signal_connect(G_OBJECT(bb), "button_press_event",
7911 G_CALLBACK(tab_close_cb), t);
7913 /* hide stuff */
7914 hide_cmd(t);
7915 hide_oops(t);
7916 hide_buffers(t);
7917 url_set_visibility();
7918 statusbar_set_visibility();
7920 if (focus) {
7921 set_current_tab(t->tab_id);
7922 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7923 t->tab_id);
7926 if (load) {
7927 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7928 load_uri(t, title);
7929 } else {
7930 if (show_url == 1)
7931 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7932 else
7933 focus_webview(t);
7936 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7937 /* restore the tab's history */
7938 if (u && u->history) {
7939 items = u->history;
7940 while (items) {
7941 item = items->data;
7942 webkit_web_back_forward_list_add_item(t->bfl, item);
7943 items = g_list_next(items);
7946 item = g_list_nth_data(u->history, u->back);
7947 if (item)
7948 webkit_web_view_go_to_back_forward_item(t->wv, item);
7950 g_list_free(items);
7951 g_list_free(u->history);
7952 } else
7953 webkit_web_back_forward_list_clear(t->bfl);
7955 recolor_compact_tabs();
7956 return (t);
7959 void
7960 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7961 gpointer *udata)
7963 struct tab *t;
7964 const gchar *uri;
7966 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7968 if (gtk_notebook_get_current_page(notebook) == -1)
7969 recalc_tabs();
7971 TAILQ_FOREACH(t, &tabs, entry) {
7972 if (t->tab_id == pn) {
7973 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7974 "%d\n", pn);
7976 uri = get_title(t, TRUE);
7977 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7979 hide_cmd(t);
7980 hide_oops(t);
7982 if (t->focus_wv) {
7983 /* can't use focus_webview here */
7984 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7990 void
7991 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7992 gpointer *udata)
7994 struct tab *t = NULL, *tt;
7996 recalc_tabs();
7998 TAILQ_FOREACH(tt, &tabs, entry)
7999 if (tt->tab_id == pn) {
8000 t = tt;
8001 break;
8004 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8006 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8007 t->tab_id);
8010 void
8011 menuitem_response(struct tab *t)
8013 gtk_notebook_set_current_page(notebook, t->tab_id);
8016 gboolean
8017 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8019 GtkWidget *menu, *menu_items;
8020 GdkEventButton *bevent;
8021 const gchar *uri;
8022 struct tab *ti;
8024 if (event->type == GDK_BUTTON_PRESS) {
8025 bevent = (GdkEventButton *) event;
8026 menu = gtk_menu_new();
8028 TAILQ_FOREACH(ti, &tabs, entry) {
8029 if ((uri = get_uri(ti)) == NULL)
8030 /* XXX make sure there is something to print */
8031 /* XXX add gui pages in here to look purdy */
8032 uri = "(untitled)";
8033 menu_items = gtk_menu_item_new_with_label(uri);
8034 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8035 gtk_widget_show(menu_items);
8037 g_signal_connect_swapped((menu_items),
8038 "activate", G_CALLBACK(menuitem_response),
8039 (gpointer)ti);
8042 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8043 bevent->button, bevent->time);
8045 /* unref object so it'll free itself when popped down */
8046 #if !GTK_CHECK_VERSION(3, 0, 0)
8047 /* XXX does not need unref with gtk+3? */
8048 g_object_ref_sink(menu);
8049 g_object_unref(menu);
8050 #endif
8052 return (TRUE /* eat event */);
8055 return (FALSE /* propagate */);
8059 icon_size_map(int icon_size)
8061 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8062 icon_size > GTK_ICON_SIZE_DIALOG)
8063 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8065 return (icon_size);
8068 GtkWidget *
8069 create_button(char *name, char *stockid, int size)
8071 GtkWidget *button, *image;
8072 gchar *rcstring;
8073 int gtk_icon_size;
8075 rcstring = g_strdup_printf(
8076 "style \"%s-style\"\n"
8077 "{\n"
8078 " GtkWidget::focus-padding = 0\n"
8079 " GtkWidget::focus-line-width = 0\n"
8080 " xthickness = 0\n"
8081 " ythickness = 0\n"
8082 "}\n"
8083 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8084 gtk_rc_parse_string(rcstring);
8085 g_free(rcstring);
8086 button = gtk_button_new();
8087 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8088 gtk_icon_size = icon_size_map(size ? size : icon_size);
8090 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8091 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8092 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8093 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8094 gtk_widget_set_name(button, name);
8095 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8097 return (button);
8100 void
8101 button_set_stockid(GtkWidget *button, char *stockid)
8103 GtkWidget *image;
8105 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8106 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8107 gtk_button_set_image(GTK_BUTTON(button), image);
8110 void
8111 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8113 GtkClipboard *clipboard;
8114 gchar *p = NULL, *s = NULL;
8117 * This code is very aggressive!
8118 * It basically ensures that the primary and regular clipboard are
8119 * always set the same. This obviously messes with standard X protocol
8120 * but those clowns should have come up with something better.
8123 if (btn_down)
8124 return;
8126 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8127 p = gtk_clipboard_wait_for_text(primary);
8128 if (p == NULL) {
8129 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
8130 p = gtk_clipboard_wait_for_text(clipboard);
8131 if (p)
8132 gtk_clipboard_set_text(primary, p, -1);
8133 } else {
8134 DNPRINTF(XT_D_CLIP, "primary got selection\n");
8135 s = gtk_clipboard_wait_for_text(clipboard);
8136 if (s) {
8138 * if s and p are the same the string was set by
8139 * clipb_clipboard_cb so do nothing in that case
8140 * to prevent endless loop
8142 if (!strcmp(s, p))
8143 goto done;
8145 gtk_clipboard_set_text(clipboard, p, -1);
8147 done:
8148 if (p)
8149 g_free(p);
8150 if (s)
8151 g_free(s);
8154 void
8155 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
8157 GtkClipboard *primary;
8158 gchar *p = NULL, *s = NULL;
8160 if (btn_down)
8161 return;
8163 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
8165 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8166 p = gtk_clipboard_wait_for_text(clipboard);
8167 if (p) {
8168 s = gtk_clipboard_wait_for_text(primary);
8169 if (s) {
8171 * if s and p are the same the string was set by
8172 * clipb_primary_cb so do nothing in that case
8173 * to prevent endless loop and deselection of text
8175 if (!strcmp(s, p))
8176 goto done;
8178 gtk_clipboard_set_text(primary, p, -1);
8180 done:
8181 if (p)
8182 g_free(p);
8183 if (s)
8184 g_free(s);
8187 void
8188 create_canvas(void)
8190 GtkWidget *vbox;
8191 GList *l = NULL;
8192 GdkPixbuf *pb;
8193 char file[PATH_MAX];
8194 int i;
8196 vbox = gtk_vbox_new(FALSE, 0);
8197 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8198 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8199 #if !GTK_CHECK_VERSION(3, 0, 0)
8200 /* XXX seems to be needed with gtk+2 */
8201 gtk_notebook_set_tab_hborder(notebook, 0);
8202 gtk_notebook_set_tab_vborder(notebook, 0);
8203 #endif
8204 gtk_notebook_set_scrollable(notebook, TRUE);
8205 gtk_notebook_set_show_border(notebook, FALSE);
8206 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8208 abtn = gtk_button_new();
8209 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8210 gtk_widget_set_size_request(arrow, -1, -1);
8211 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8212 gtk_widget_set_size_request(abtn, -1, 20);
8214 #if GTK_CHECK_VERSION(2, 20, 0)
8215 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8216 #endif
8217 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8219 /* compact tab bar */
8220 tab_bar = gtk_hbox_new(TRUE, 0);
8222 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8223 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8224 gtk_widget_set_size_request(vbox, -1, -1);
8226 g_object_connect(G_OBJECT(notebook),
8227 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8228 (char *)NULL);
8229 g_object_connect(G_OBJECT(notebook),
8230 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
8231 (char *)NULL);
8232 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8233 G_CALLBACK(arrow_cb), NULL);
8235 main_window = create_window();
8236 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8238 /* icons */
8239 for (i = 0; i < LENGTH(icons); i++) {
8240 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8241 pb = gdk_pixbuf_new_from_file(file, NULL);
8242 l = g_list_append(l, pb);
8244 gtk_window_set_default_icon_list(l);
8246 /* clipboard */
8247 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8248 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8249 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
8250 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
8252 gtk_widget_show_all(abtn);
8253 gtk_widget_show_all(main_window);
8254 notebook_tab_set_visibility();
8257 void
8258 set_hook(void **hook, char *name)
8260 if (hook == NULL)
8261 errx(1, "set_hook");
8263 if (*hook == NULL) {
8264 *hook = dlsym(RTLD_NEXT, name);
8265 if (*hook == NULL)
8266 errx(1, "can't hook %s", name);
8270 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8271 gboolean
8272 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8274 g_return_val_if_fail(cookie1, FALSE);
8275 g_return_val_if_fail(cookie2, FALSE);
8277 return (!strcmp (cookie1->name, cookie2->name) &&
8278 !strcmp (cookie1->value, cookie2->value) &&
8279 !strcmp (cookie1->path, cookie2->path) &&
8280 !strcmp (cookie1->domain, cookie2->domain));
8283 void
8284 transfer_cookies(void)
8286 GSList *cf;
8287 SoupCookie *sc, *pc;
8289 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8291 for (;cf; cf = cf->next) {
8292 pc = cf->data;
8293 sc = soup_cookie_copy(pc);
8294 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8297 soup_cookies_free(cf);
8300 void
8301 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8303 GSList *cf;
8304 SoupCookie *ci;
8306 print_cookie("soup_cookie_jar_delete_cookie", c);
8308 if (cookies_enabled == 0)
8309 return;
8311 if (jar == NULL || c == NULL)
8312 return;
8314 /* find and remove from persistent jar */
8315 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8317 for (;cf; cf = cf->next) {
8318 ci = cf->data;
8319 if (soup_cookie_equal(ci, c)) {
8320 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8321 break;
8325 soup_cookies_free(cf);
8327 /* delete from session jar */
8328 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8331 void
8332 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8334 struct domain *d = NULL;
8335 SoupCookie *c;
8336 FILE *r_cookie_f;
8338 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8339 jar, p_cookiejar, s_cookiejar);
8341 if (cookies_enabled == 0)
8342 return;
8344 /* see if we are up and running */
8345 if (p_cookiejar == NULL) {
8346 _soup_cookie_jar_add_cookie(jar, cookie);
8347 return;
8349 /* disallow p_cookiejar adds, shouldn't happen */
8350 if (jar == p_cookiejar)
8351 return;
8353 /* sanity */
8354 if (jar == NULL || cookie == NULL)
8355 return;
8357 if (enable_cookie_whitelist &&
8358 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8359 blocked_cookies++;
8360 DNPRINTF(XT_D_COOKIE,
8361 "soup_cookie_jar_add_cookie: reject %s\n",
8362 cookie->domain);
8363 if (save_rejected_cookies) {
8364 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8365 show_oops(NULL, "can't open reject cookie file");
8366 return;
8368 fseek(r_cookie_f, 0, SEEK_END);
8369 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8370 cookie->http_only ? "#HttpOnly_" : "",
8371 cookie->domain,
8372 *cookie->domain == '.' ? "TRUE" : "FALSE",
8373 cookie->path,
8374 cookie->secure ? "TRUE" : "FALSE",
8375 cookie->expires ?
8376 (gulong)soup_date_to_time_t(cookie->expires) :
8378 cookie->name,
8379 cookie->value);
8380 fflush(r_cookie_f);
8381 fclose(r_cookie_f);
8383 if (!allow_volatile_cookies)
8384 return;
8387 if (cookie->expires == NULL && session_timeout) {
8388 soup_cookie_set_expires(cookie,
8389 soup_date_new_from_now(session_timeout));
8390 print_cookie("modified add cookie", cookie);
8393 /* see if we are white listed for persistence */
8394 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8395 /* add to persistent jar */
8396 c = soup_cookie_copy(cookie);
8397 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8398 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8401 /* add to session jar */
8402 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8403 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8406 void
8407 setup_cookies(void)
8409 char file[PATH_MAX];
8411 set_hook((void *)&_soup_cookie_jar_add_cookie,
8412 "soup_cookie_jar_add_cookie");
8413 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8414 "soup_cookie_jar_delete_cookie");
8416 if (cookies_enabled == 0)
8417 return;
8420 * the following code is intricate due to overriding several libsoup
8421 * functions.
8422 * do not alter order of these operations.
8425 /* rejected cookies */
8426 if (save_rejected_cookies)
8427 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8429 /* persistent cookies */
8430 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8431 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8433 /* session cookies */
8434 s_cookiejar = soup_cookie_jar_new();
8435 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8436 cookie_policy, (void *)NULL);
8437 transfer_cookies();
8439 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8442 void
8443 setup_proxy(char *uri)
8445 if (proxy_uri) {
8446 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8447 soup_uri_free(proxy_uri);
8448 proxy_uri = NULL;
8450 if (http_proxy) {
8451 if (http_proxy != uri) {
8452 g_free(http_proxy);
8453 http_proxy = NULL;
8457 if (uri) {
8458 http_proxy = g_strdup(uri);
8459 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8460 proxy_uri = soup_uri_new(http_proxy);
8461 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8466 send_cmd_to_socket(char *cmd)
8468 int s, len, rv = 1;
8469 struct sockaddr_un sa;
8471 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8472 warnx("%s: socket", __func__);
8473 return (rv);
8476 sa.sun_family = AF_UNIX;
8477 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8478 work_dir, XT_SOCKET_FILE);
8479 len = SUN_LEN(&sa);
8481 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8482 warnx("%s: connect", __func__);
8483 goto done;
8486 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8487 warnx("%s: send", __func__);
8488 goto done;
8491 rv = 0;
8492 done:
8493 close(s);
8494 return (rv);
8497 gboolean
8498 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8500 int s, n;
8501 char str[XT_MAX_URL_LENGTH];
8502 socklen_t t = sizeof(struct sockaddr_un);
8503 struct sockaddr_un sa;
8504 struct passwd *p;
8505 uid_t uid;
8506 gid_t gid;
8507 struct tab *tt;
8508 gint fd = g_io_channel_unix_get_fd(source);
8510 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8511 warn("accept");
8512 return (FALSE);
8515 if (getpeereid(s, &uid, &gid) == -1) {
8516 warn("getpeereid");
8517 return (FALSE);
8519 if (uid != getuid() || gid != getgid()) {
8520 warnx("unauthorized user");
8521 return (FALSE);
8524 p = getpwuid(uid);
8525 if (p == NULL) {
8526 warnx("not a valid user");
8527 return (FALSE);
8530 n = recv(s, str, sizeof(str), 0);
8531 if (n <= 0)
8532 return (FALSE);
8534 tt = TAILQ_LAST(&tabs, tab_list);
8535 cmd_execute(tt, str);
8536 return (TRUE);
8540 is_running(void)
8542 int s, len, rv = 1;
8543 struct sockaddr_un sa;
8545 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8546 warn("is_running: socket");
8547 return (-1);
8550 sa.sun_family = AF_UNIX;
8551 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8552 work_dir, XT_SOCKET_FILE);
8553 len = SUN_LEN(&sa);
8555 /* connect to see if there is a listener */
8556 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8557 rv = 0; /* not running */
8558 else
8559 rv = 1; /* already running */
8561 close(s);
8563 return (rv);
8567 build_socket(void)
8569 int s, len;
8570 struct sockaddr_un sa;
8572 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8573 warn("build_socket: socket");
8574 return (-1);
8577 sa.sun_family = AF_UNIX;
8578 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8579 work_dir, XT_SOCKET_FILE);
8580 len = SUN_LEN(&sa);
8582 /* connect to see if there is a listener */
8583 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8584 /* no listener so we will */
8585 unlink(sa.sun_path);
8587 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8588 warn("build_socket: bind");
8589 goto done;
8592 if (listen(s, 1) == -1) {
8593 warn("build_socket: listen");
8594 goto done;
8597 return (s);
8600 done:
8601 close(s);
8602 return (-1);
8605 gboolean
8606 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8607 GtkTreeIter *iter, struct tab *t)
8609 gchar *value;
8611 gtk_tree_model_get(model, iter, 0, &value, -1);
8612 load_uri(t, value);
8613 g_free(value);
8615 return (FALSE);
8618 gboolean
8619 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8620 GtkTreeIter *iter, struct tab *t)
8622 gchar *value;
8624 gtk_tree_model_get(model, iter, 0, &value, -1);
8625 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8626 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8627 g_free(value);
8629 return (TRUE);
8632 void
8633 completion_add_uri(const gchar *uri)
8635 GtkTreeIter iter;
8637 /* add uri to list_store */
8638 gtk_list_store_append(completion_model, &iter);
8639 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8642 gboolean
8643 completion_match(GtkEntryCompletion *completion, const gchar *key,
8644 GtkTreeIter *iter, gpointer user_data)
8646 gchar *value;
8647 gboolean match = FALSE;
8649 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8650 -1);
8652 if (value == NULL)
8653 return FALSE;
8655 match = match_uri(value, key);
8657 g_free(value);
8658 return (match);
8661 void
8662 completion_add(struct tab *t)
8664 /* enable completion for tab */
8665 t->completion = gtk_entry_completion_new();
8666 gtk_entry_completion_set_text_column(t->completion, 0);
8667 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8668 gtk_entry_completion_set_model(t->completion,
8669 GTK_TREE_MODEL(completion_model));
8670 gtk_entry_completion_set_match_func(t->completion, completion_match,
8671 NULL, NULL);
8672 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8673 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8674 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8675 G_CALLBACK(completion_select_cb), t);
8676 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8677 G_CALLBACK(completion_hover_cb), t);
8680 void
8681 xxx_dir(char *dir)
8683 struct stat sb;
8685 if (stat(dir, &sb)) {
8686 if (mkdir(dir, S_IRWXU) == -1)
8687 err(1, "mkdir %s", dir);
8688 if (stat(dir, &sb))
8689 err(1, "stat %s", dir);
8691 if (S_ISDIR(sb.st_mode) == 0)
8692 errx(1, "%s not a dir", dir);
8693 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8694 warnx("fixing invalid permissions on %s", dir);
8695 if (chmod(dir, S_IRWXU) == -1)
8696 err(1, "chmod %s", dir);
8700 void
8701 usage(void)
8703 fprintf(stderr,
8704 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8705 exit(0);
8710 main(int argc, char *argv[])
8712 struct stat sb;
8713 int c, s, optn = 0, opte = 0, focus = 1;
8714 char conf[PATH_MAX] = { '\0' };
8715 char file[PATH_MAX];
8716 char *env_proxy = NULL;
8717 FILE *f = NULL;
8718 struct karg a;
8719 struct sigaction sact;
8720 GIOChannel *channel;
8721 struct rlimit rlp;
8723 start_argv = argv;
8725 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8727 /* fiddle with ulimits */
8728 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8729 warn("getrlimit");
8730 else {
8731 /* just use them all */
8732 rlp.rlim_cur = rlp.rlim_max;
8733 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8734 warn("setrlimit");
8735 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8736 warn("getrlimit");
8737 else if (rlp.rlim_cur <= 256)
8738 warnx("%s requires at least 256 file descriptors",
8739 __progname);
8742 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8743 switch (c) {
8744 case 'S':
8745 show_url = 0;
8746 break;
8747 case 'T':
8748 show_tabs = 0;
8749 break;
8750 case 'V':
8751 errx(0 , "Version: %s", version);
8752 break;
8753 case 'f':
8754 strlcpy(conf, optarg, sizeof(conf));
8755 break;
8756 case 's':
8757 strlcpy(named_session, optarg, sizeof(named_session));
8758 break;
8759 case 't':
8760 tabless = 1;
8761 break;
8762 case 'n':
8763 optn = 1;
8764 break;
8765 case 'e':
8766 opte = 1;
8767 break;
8768 default:
8769 usage();
8770 /* NOTREACHED */
8773 argc -= optind;
8774 argv += optind;
8776 RB_INIT(&hl);
8777 RB_INIT(&js_wl);
8778 RB_INIT(&downloads);
8780 TAILQ_INIT(&tabs);
8781 TAILQ_INIT(&mtl);
8782 TAILQ_INIT(&aliases);
8783 TAILQ_INIT(&undos);
8784 TAILQ_INIT(&kbl);
8786 init_keybindings();
8788 gnutls_global_init();
8790 /* generate session keys for xtp pages */
8791 generate_xtp_session_key(&dl_session_key);
8792 generate_xtp_session_key(&hl_session_key);
8793 generate_xtp_session_key(&cl_session_key);
8794 generate_xtp_session_key(&fl_session_key);
8796 /* prepare gtk */
8797 gtk_init(&argc, &argv);
8798 if (!g_thread_supported())
8799 g_thread_init(NULL);
8801 /* signals */
8802 bzero(&sact, sizeof(sact));
8803 sigemptyset(&sact.sa_mask);
8804 sact.sa_handler = sigchild;
8805 sact.sa_flags = SA_NOCLDSTOP;
8806 sigaction(SIGCHLD, &sact, NULL);
8808 /* set download dir */
8809 pwd = getpwuid(getuid());
8810 if (pwd == NULL)
8811 errx(1, "invalid user %d", getuid());
8812 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8814 /* set default string settings */
8815 home = g_strdup("https://www.cyphertite.com");
8816 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8817 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8818 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
8819 cmd_font_name = g_strdup("monospace normal 9");
8820 oops_font_name = g_strdup("monospace normal 9");
8821 statusbar_font_name = g_strdup("monospace normal 9");
8822 tabbar_font_name = g_strdup("monospace normal 9");
8825 /* read config file */
8826 if (strlen(conf) == 0)
8827 snprintf(conf, sizeof conf, "%s/.%s",
8828 pwd->pw_dir, XT_CONF_FILE);
8829 config_parse(conf, 0);
8831 /* init fonts */
8832 cmd_font = pango_font_description_from_string(cmd_font_name);
8833 oops_font = pango_font_description_from_string(oops_font_name);
8834 statusbar_font = pango_font_description_from_string(statusbar_font_name);
8835 tabbar_font = pango_font_description_from_string(tabbar_font_name);
8837 /* working directory */
8838 if (strlen(work_dir) == 0)
8839 snprintf(work_dir, sizeof work_dir, "%s/%s",
8840 pwd->pw_dir, XT_DIR);
8841 xxx_dir(work_dir);
8843 /* icon cache dir */
8844 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8845 xxx_dir(cache_dir);
8847 /* certs dir */
8848 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8849 xxx_dir(certs_dir);
8851 /* sessions dir */
8852 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8853 work_dir, XT_SESSIONS_DIR);
8854 xxx_dir(sessions_dir);
8856 /* runtime settings that can override config file */
8857 if (runtime_settings[0] != '\0')
8858 config_parse(runtime_settings, 1);
8860 /* download dir */
8861 if (!strcmp(download_dir, pwd->pw_dir))
8862 strlcat(download_dir, "/downloads", sizeof download_dir);
8863 xxx_dir(download_dir);
8865 /* favorites file */
8866 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8867 if (stat(file, &sb)) {
8868 warnx("favorites file doesn't exist, creating it");
8869 if ((f = fopen(file, "w")) == NULL)
8870 err(1, "favorites");
8871 fclose(f);
8874 /* cookies */
8875 session = webkit_get_default_session();
8876 setup_cookies();
8878 /* certs */
8879 if (ssl_ca_file) {
8880 if (stat(ssl_ca_file, &sb)) {
8881 warnx("no CA file: %s", ssl_ca_file);
8882 g_free(ssl_ca_file);
8883 ssl_ca_file = NULL;
8884 } else
8885 g_object_set(session,
8886 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8887 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8888 (void *)NULL);
8891 /* proxy */
8892 env_proxy = getenv("http_proxy");
8893 if (env_proxy)
8894 setup_proxy(env_proxy);
8895 else
8896 setup_proxy(http_proxy);
8898 if (opte) {
8899 send_cmd_to_socket(argv[0]);
8900 exit(0);
8903 /* set some connection parameters */
8904 /* XXX webkit 1.4.X overwrites these values! */
8905 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
8906 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8907 g_object_set(session, "max-conns-per-host", max_host_connections,
8908 (char *)NULL);
8910 /* see if there is already an xxxterm running */
8911 if (single_instance && is_running()) {
8912 optn = 1;
8913 warnx("already running");
8916 char *cmd = NULL;
8917 if (optn) {
8918 while (argc) {
8919 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8920 send_cmd_to_socket(cmd);
8921 if (cmd)
8922 g_free(cmd);
8924 argc--;
8925 argv++;
8927 exit(0);
8930 /* uri completion */
8931 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8933 /* buffers */
8934 buffers_store = gtk_list_store_new
8935 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
8937 /* go graphical */
8938 create_canvas();
8939 notebook_tab_set_visibility();
8941 if (save_global_history)
8942 restore_global_history();
8944 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8945 restore_saved_tabs();
8946 else {
8947 a.s = named_session;
8948 a.i = XT_SES_DONOTHING;
8949 open_tabs(NULL, &a);
8952 while (argc) {
8953 create_new_tab(argv[0], NULL, focus, -1);
8954 focus = 0;
8956 argc--;
8957 argv++;
8960 if (TAILQ_EMPTY(&tabs))
8961 create_new_tab(home, NULL, 1, -1);
8963 if (enable_socket)
8964 if ((s = build_socket()) != -1) {
8965 channel = g_io_channel_unix_new(s);
8966 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8969 gtk_main();
8971 gnutls_global_deinit();
8973 return (0);