add plugin whitelist
[xxxterm.git] / xxxterm.c
blobaa574cd3a033db0152e4b25b58a1de1162498937
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 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pwd.h>
35 #include <regex.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <dirent.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #include <bsd/stdlib.h>
49 # if !defined(sane_libbsd_headers)
50 void arc4random_buf(void *, size_t);
51 # endif
52 #elif defined(__FreeBSD__)
53 #include <libutil.h>
54 #include "freebsd/util.h"
55 #include <sys/tree.h>
56 #else /* OpenBSD */
57 #include <util.h>
58 #include <sys/tree.h>
59 #endif
60 #include <sys/queue.h>
61 #include <sys/resource.h>
62 #include <sys/socket.h>
63 #include <sys/stat.h>
64 #include <sys/time.h>
65 #include <sys/un.h>
67 #include <gtk/gtk.h>
68 #include <gdk/gdkkeysyms.h>
70 #if GTK_CHECK_VERSION(3,0,0)
71 /* we still use GDK_* instead of GDK_KEY_* */
72 #include <gdk/gdkkeysyms-compat.h>
73 #endif
75 #include <webkit/webkit.h>
76 #include <libsoup/soup.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/gnutls.h>
79 #include <gnutls/x509.h>
81 #include "version.h"
82 #include "javascript.h"
84 /* comment if you don't want to use threads */
85 #define USE_THREADS
87 #ifdef USE_THREADS
88 #include <gcrypt.h>
89 #include <pthread.h>
90 GCRY_THREAD_OPTION_PTHREAD_IMPL;
91 #endif
95 javascript.h borrowed from vimprobable2 under the following license:
97 Copyright (c) 2009 Leon Winter
98 Copyright (c) 2009 Hannes Schueller
99 Copyright (c) 2009 Matto Fransen
101 Permission is hereby granted, free of charge, to any person obtaining a copy
102 of this software and associated documentation files (the "Software"), to deal
103 in the Software without restriction, including without limitation the rights
104 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
105 copies of the Software, and to permit persons to whom the Software is
106 furnished to do so, subject to the following conditions:
108 The above copyright notice and this permission notice shall be included in
109 all copies or substantial portions of the Software.
111 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
112 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
113 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
117 THE SOFTWARE.
120 static char *version = XXXTERM_VERSION;
122 /* hooked functions */
123 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
124 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
125 SoupCookie *);
127 /*#define XT_DEBUG*/
128 #ifdef XT_DEBUG
129 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
130 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
131 #define XT_D_MOVE 0x0001
132 #define XT_D_KEY 0x0002
133 #define XT_D_TAB 0x0004
134 #define XT_D_URL 0x0008
135 #define XT_D_CMD 0x0010
136 #define XT_D_NAV 0x0020
137 #define XT_D_DOWNLOAD 0x0040
138 #define XT_D_CONFIG 0x0080
139 #define XT_D_JS 0x0100
140 #define XT_D_FAVORITE 0x0200
141 #define XT_D_PRINTING 0x0400
142 #define XT_D_COOKIE 0x0800
143 #define XT_D_KEYBINDING 0x1000
144 #define XT_D_CLIP 0x2000
145 #define XT_D_BUFFERCMD 0x4000
146 u_int32_t swm_debug = 0
147 | XT_D_MOVE
148 | XT_D_KEY
149 | XT_D_TAB
150 | XT_D_URL
151 | XT_D_CMD
152 | XT_D_NAV
153 | XT_D_DOWNLOAD
154 | XT_D_CONFIG
155 | XT_D_JS
156 | XT_D_FAVORITE
157 | XT_D_PRINTING
158 | XT_D_COOKIE
159 | XT_D_KEYBINDING
160 | XT_D_CLIP
161 | XT_D_BUFFERCMD
163 #else
164 #define DPRINTF(x...)
165 #define DNPRINTF(n,x...)
166 #endif
168 #define LENGTH(x) (sizeof x / sizeof x[0])
169 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
170 ~(GDK_BUTTON1_MASK) & \
171 ~(GDK_BUTTON2_MASK) & \
172 ~(GDK_BUTTON3_MASK) & \
173 ~(GDK_BUTTON4_MASK) & \
174 ~(GDK_BUTTON5_MASK))
176 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
178 char *icons[] = {
179 "xxxtermicon16.png",
180 "xxxtermicon32.png",
181 "xxxtermicon48.png",
182 "xxxtermicon64.png",
183 "xxxtermicon128.png"
186 struct tab {
187 TAILQ_ENTRY(tab) entry;
188 GtkWidget *vbox;
189 GtkWidget *tab_content;
190 struct {
191 GtkWidget *label;
192 GtkWidget *eventbox;
193 GtkWidget *box;
194 GtkWidget *sep;
195 } tab_elems;
196 GtkWidget *label;
197 GtkWidget *spinner;
198 GtkWidget *uri_entry;
199 GtkWidget *search_entry;
200 GtkWidget *toolbar;
201 GtkWidget *browser_win;
202 GtkWidget *statusbar_box;
203 struct {
204 GtkWidget *statusbar;
205 GtkWidget *buffercmd;
206 GtkWidget *zoom;
207 GtkWidget *position;
208 } sbe;
209 GtkWidget *cmd;
210 GtkWidget *buffers;
211 GtkWidget *oops;
212 GtkWidget *backward;
213 GtkWidget *forward;
214 GtkWidget *stop;
215 GtkWidget *gohome;
216 GtkWidget *js_toggle;
217 GtkEntryCompletion *completion;
218 guint tab_id;
219 WebKitWebView *wv;
221 WebKitWebHistoryItem *item;
222 WebKitWebBackForwardList *bfl;
224 /* favicon */
225 WebKitNetworkRequest *icon_request;
226 WebKitDownload *icon_download;
227 gchar *icon_dest_uri;
229 /* adjustments for browser */
230 GtkScrollbar *sb_h;
231 GtkScrollbar *sb_v;
232 GtkAdjustment *adjust_h;
233 GtkAdjustment *adjust_v;
235 /* flags */
236 int focus_wv;
237 int ctrl_click;
238 gchar *status;
239 int xtp_meaning; /* identifies dls/favorites */
240 gchar *tmp_uri;
241 int popup; /* 1 if cmd_entry has popup visible */
242 #ifdef USE_THREADS
243 /* https thread stuff */
244 GThread *thread;
245 #endif
246 /* hints */
247 int hints_on;
248 int hint_mode;
249 #define XT_HINT_NONE (0)
250 #define XT_HINT_NUMERICAL (1)
251 #define XT_HINT_ALPHANUM (2)
252 char hint_buf[128];
253 char hint_num[128];
255 /* custom stylesheet */
256 int styled;
257 char *stylesheet;
259 /* search */
260 char *search_text;
261 int search_forward;
262 guint search_id;
264 /* settings */
265 WebKitWebSettings *settings;
266 gchar *user_agent;
268 /* marks */
269 double mark[XT_NOMARKS];
271 TAILQ_HEAD(tab_list, tab);
273 struct history {
274 RB_ENTRY(history) entry;
275 const gchar *uri;
276 const gchar *title;
278 RB_HEAD(history_list, history);
280 struct session {
281 TAILQ_ENTRY(session) entry;
282 const gchar *name;
284 TAILQ_HEAD(session_list, session);
286 struct download {
287 RB_ENTRY(download) entry;
288 int id;
289 WebKitDownload *download;
290 struct tab *tab;
292 RB_HEAD(download_list, download);
294 struct domain {
295 RB_ENTRY(domain) entry;
296 gchar *d;
297 int handy; /* app use */
299 RB_HEAD(domain_list, domain);
301 struct undo {
302 TAILQ_ENTRY(undo) entry;
303 gchar *uri;
304 GList *history;
305 int back; /* Keeps track of how many back
306 * history items there are. */
308 TAILQ_HEAD(undo_tailq, undo);
310 struct sp {
311 char *line;
312 TAILQ_ENTRY(sp) entry;
314 TAILQ_HEAD(sp_list, sp);
316 struct command_entry {
317 char *line;
318 TAILQ_ENTRY(command_entry) entry;
320 TAILQ_HEAD(command_list, command_entry);
322 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
323 int next_download_id = 1;
325 struct karg {
326 int i;
327 char *s;
328 int precount;
331 /* defines */
332 #define XT_NAME ("XXXTerm")
333 #define XT_DIR (".xxxterm")
334 #define XT_CACHE_DIR ("cache")
335 #define XT_CERT_DIR ("certs/")
336 #define XT_SESSIONS_DIR ("sessions/")
337 #define XT_CONF_FILE ("xxxterm.conf")
338 #define XT_FAVS_FILE ("favorites")
339 #define XT_QMARKS_FILE ("quickmarks")
340 #define XT_SAVED_TABS_FILE ("main_session")
341 #define XT_RESTART_TABS_FILE ("restart_tabs")
342 #define XT_SOCKET_FILE ("socket")
343 #define XT_HISTORY_FILE ("history")
344 #define XT_REJECT_FILE ("rejected.txt")
345 #define XT_COOKIE_FILE ("cookies.txt")
346 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
347 #define XT_SEARCH_FILE ("search_history")
348 #define XT_COMMAND_FILE ("command_history")
349 #define XT_CB_HANDLED (TRUE)
350 #define XT_CB_PASSTHROUGH (FALSE)
351 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
352 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
353 #define XT_DLMAN_REFRESH "10"
354 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
355 "td{overflow: hidden;" \
356 " padding: 2px 2px 2px 2px;" \
357 " border: 1px solid black;" \
358 " vertical-align:top;" \
359 " word-wrap: break-word}\n" \
360 "tr:hover{background: #ffff99}\n" \
361 "th{background-color: #cccccc;" \
362 " border: 1px solid black}\n" \
363 "table{width: 100%%;" \
364 " border: 1px black solid;" \
365 " border-collapse:collapse}\n" \
366 ".progress-outer{" \
367 "border: 1px solid black;" \
368 " height: 8px;" \
369 " width: 90%%}\n" \
370 ".progress-inner{float: left;" \
371 " height: 8px;" \
372 " background: green}\n" \
373 ".dlstatus{font-size: small;" \
374 " text-align: center}\n" \
375 "</style>\n"
376 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
377 #define XT_MAX_UNDO_CLOSE_TAB (32)
378 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
379 #define XT_PRINT_EXTRA_MARGIN 10
380 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
381 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
383 /* colors */
384 #define XT_COLOR_RED "#cc0000"
385 #define XT_COLOR_YELLOW "#ffff66"
386 #define XT_COLOR_BLUE "lightblue"
387 #define XT_COLOR_GREEN "#99ff66"
388 #define XT_COLOR_WHITE "white"
389 #define XT_COLOR_BLACK "black"
391 #define XT_COLOR_CT_BACKGROUND "#000000"
392 #define XT_COLOR_CT_INACTIVE "#dddddd"
393 #define XT_COLOR_CT_ACTIVE "#bbbb00"
394 #define XT_COLOR_CT_SEPARATOR "#555555"
396 #define XT_COLOR_SB_SEPARATOR "#555555"
398 #define XT_PROTO_DELIM "://"
401 * xxxterm "protocol" (xtp)
402 * We use this for managing stuff like downloads and favorites. They
403 * make magical HTML pages in memory which have xxxt:// links in order
404 * to communicate with xxxterm's internals. These links take the format:
405 * xxxt://class/session_key/action/arg
407 * Don't begin xtp class/actions as 0. atoi returns that on error.
409 * Typically we have not put addition of items in this framework, as
410 * adding items is either done via an ex-command or via a keybinding instead.
413 #define XT_XTP_STR "xxxt://"
415 /* XTP classes (xxxt://<class>) */
416 #define XT_XTP_INVALID 0 /* invalid */
417 #define XT_XTP_DL 1 /* downloads */
418 #define XT_XTP_HL 2 /* history */
419 #define XT_XTP_CL 3 /* cookies */
420 #define XT_XTP_FL 4 /* favorites */
422 /* XTP download actions */
423 #define XT_XTP_DL_LIST 1
424 #define XT_XTP_DL_CANCEL 2
425 #define XT_XTP_DL_REMOVE 3
427 /* XTP history actions */
428 #define XT_XTP_HL_LIST 1
429 #define XT_XTP_HL_REMOVE 2
431 /* XTP cookie actions */
432 #define XT_XTP_CL_LIST 1
433 #define XT_XTP_CL_REMOVE 2
435 /* XTP cookie actions */
436 #define XT_XTP_FL_LIST 1
437 #define XT_XTP_FL_REMOVE 2
439 /* actions */
440 #define XT_MOVE_INVALID (0)
441 #define XT_MOVE_DOWN (1)
442 #define XT_MOVE_UP (2)
443 #define XT_MOVE_BOTTOM (3)
444 #define XT_MOVE_TOP (4)
445 #define XT_MOVE_PAGEDOWN (5)
446 #define XT_MOVE_PAGEUP (6)
447 #define XT_MOVE_HALFDOWN (7)
448 #define XT_MOVE_HALFUP (8)
449 #define XT_MOVE_LEFT (9)
450 #define XT_MOVE_FARLEFT (10)
451 #define XT_MOVE_RIGHT (11)
452 #define XT_MOVE_FARRIGHT (12)
453 #define XT_MOVE_PERCENT (13)
455 #define XT_QMARK_SET (0)
456 #define XT_QMARK_OPEN (1)
457 #define XT_QMARK_TAB (2)
459 #define XT_MARK_SET (0)
460 #define XT_MARK_GOTO (1)
462 #define XT_TAB_LAST (-4)
463 #define XT_TAB_FIRST (-3)
464 #define XT_TAB_PREV (-2)
465 #define XT_TAB_NEXT (-1)
466 #define XT_TAB_INVALID (0)
467 #define XT_TAB_NEW (1)
468 #define XT_TAB_DELETE (2)
469 #define XT_TAB_DELQUIT (3)
470 #define XT_TAB_OPEN (4)
471 #define XT_TAB_UNDO_CLOSE (5)
472 #define XT_TAB_SHOW (6)
473 #define XT_TAB_HIDE (7)
474 #define XT_TAB_NEXTSTYLE (8)
476 #define XT_NAV_INVALID (0)
477 #define XT_NAV_BACK (1)
478 #define XT_NAV_FORWARD (2)
479 #define XT_NAV_RELOAD (3)
481 #define XT_FOCUS_INVALID (0)
482 #define XT_FOCUS_URI (1)
483 #define XT_FOCUS_SEARCH (2)
485 #define XT_SEARCH_INVALID (0)
486 #define XT_SEARCH_NEXT (1)
487 #define XT_SEARCH_PREV (2)
489 #define XT_PASTE_CURRENT_TAB (0)
490 #define XT_PASTE_NEW_TAB (1)
492 #define XT_ZOOM_IN (-1)
493 #define XT_ZOOM_OUT (-2)
494 #define XT_ZOOM_NORMAL (100)
496 #define XT_URL_SHOW (1)
497 #define XT_URL_HIDE (2)
499 #define XT_WL_TOGGLE (1<<0)
500 #define XT_WL_ENABLE (1<<1)
501 #define XT_WL_DISABLE (1<<2)
502 #define XT_WL_FQDN (1<<3) /* default */
503 #define XT_WL_TOPLEVEL (1<<4)
504 #define XT_WL_PERSISTENT (1<<5)
505 #define XT_WL_SESSION (1<<6)
506 #define XT_WL_RELOAD (1<<7)
508 #define XT_SHOW (1<<7)
509 #define XT_DELETE (1<<8)
510 #define XT_SAVE (1<<9)
511 #define XT_OPEN (1<<10)
513 #define XT_CMD_OPEN (0)
514 #define XT_CMD_OPEN_CURRENT (1)
515 #define XT_CMD_TABNEW (2)
516 #define XT_CMD_TABNEW_CURRENT (3)
518 #define XT_STATUS_NOTHING (0)
519 #define XT_STATUS_LINK (1)
520 #define XT_STATUS_URI (2)
521 #define XT_STATUS_LOADING (3)
523 #define XT_SES_DONOTHING (0)
524 #define XT_SES_CLOSETABS (1)
526 #define XT_BM_NORMAL (0)
527 #define XT_BM_WHITELIST (1)
528 #define XT_BM_KIOSK (2)
530 #define XT_PREFIX (1<<0)
531 #define XT_USERARG (1<<1)
532 #define XT_URLARG (1<<2)
533 #define XT_INTARG (1<<3)
534 #define XT_SESSARG (1<<4)
535 #define XT_SETARG (1<<5)
537 #define XT_TABS_NORMAL 0
538 #define XT_TABS_COMPACT 1
540 #define XT_BUFCMD_SZ (8)
542 /* mime types */
543 struct mime_type {
544 char *mt_type;
545 char *mt_action;
546 int mt_default;
547 int mt_download;
548 TAILQ_ENTRY(mime_type) entry;
550 TAILQ_HEAD(mime_type_list, mime_type);
552 /* uri aliases */
553 struct alias {
554 char *a_name;
555 char *a_uri;
556 TAILQ_ENTRY(alias) entry;
558 TAILQ_HEAD(alias_list, alias);
560 /* settings that require restart */
561 int tabless = 0; /* allow only 1 tab */
562 int enable_socket = 0;
563 int single_instance = 0; /* only allow one xxxterm to run */
564 int fancy_bar = 1; /* fancy toolbar */
565 int browser_mode = XT_BM_NORMAL;
566 int enable_localstorage = 1;
567 char *statusbar_elems = NULL;
569 /* runtime settings */
570 int show_tabs = 1; /* show tabs on notebook */
571 int tab_style = XT_TABS_NORMAL; /* tab bar style */
572 int show_url = 1; /* show url toolbar on notebook */
573 int show_statusbar = 0; /* vimperator style status bar */
574 int ctrl_click_focus = 0; /* ctrl click gets focus */
575 int cookies_enabled = 1; /* enable cookies */
576 int read_only_cookies = 0; /* enable to not write cookies */
577 int enable_scripts = 1;
578 int enable_plugins = 1;
579 gfloat default_zoom_level = 1.0;
580 char default_script[PATH_MAX];
581 int window_height = 768;
582 int window_width = 1024;
583 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
584 int refresh_interval = 10; /* download refresh interval */
585 int enable_plugin_whitelist = 0;
586 int enable_cookie_whitelist = 0;
587 int enable_js_whitelist = 0;
588 int session_timeout = 3600; /* cookie session timeout */
589 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
590 char *ssl_ca_file = NULL;
591 char *resource_dir = NULL;
592 gboolean ssl_strict_certs = FALSE;
593 int append_next = 1; /* append tab after current tab */
594 char *home = NULL;
595 char *search_string = NULL;
596 char *http_proxy = NULL;
597 char download_dir[PATH_MAX];
598 char runtime_settings[PATH_MAX]; /* override of settings */
599 int allow_volatile_cookies = 0;
600 int save_global_history = 0; /* save global history to disk */
601 char *user_agent = NULL;
602 int save_rejected_cookies = 0;
603 int session_autosave = 0;
604 int guess_search = 0;
605 int dns_prefetch = FALSE;
606 gint max_connections = 25;
607 gint max_host_connections = 5;
608 gint enable_spell_checking = 0;
609 char *spell_check_languages = NULL;
610 int xterm_workaround = 0;
611 char *url_regex = NULL;
612 int history_autosave = 0;
613 char search_file[PATH_MAX];
614 char command_file[PATH_MAX];
615 char *encoding = NULL;
617 char *cmd_font_name = NULL;
618 char *oops_font_name = NULL;
619 char *statusbar_font_name = NULL;
620 char *tabbar_font_name = NULL;
621 PangoFontDescription *cmd_font;
622 PangoFontDescription *oops_font;
623 PangoFontDescription *statusbar_font;
624 PangoFontDescription *tabbar_font;
625 char *qmarks[XT_NOMARKS];
627 int btn_down; /* M1 down in any wv */
628 regex_t url_re; /* guess_search regex */
630 struct settings;
631 struct key_binding;
632 int set_browser_mode(struct settings *, char *);
633 int set_cookie_policy(struct settings *, char *);
634 int set_download_dir(struct settings *, char *);
635 int set_default_script(struct settings *, char *);
636 int set_runtime_dir(struct settings *, char *);
637 int set_tab_style(struct settings *, char *);
638 int set_work_dir(struct settings *, char *);
639 int add_alias(struct settings *, char *);
640 int add_mime_type(struct settings *, char *);
641 int add_cookie_wl(struct settings *, char *);
642 int add_js_wl(struct settings *, char *);
643 int add_pl_wl(struct settings *, char *);
644 int add_kb(struct settings *, char *);
645 void button_set_stockid(GtkWidget *, char *);
646 GtkWidget * create_button(char *, char *, int);
648 char *get_browser_mode(struct settings *);
649 char *get_cookie_policy(struct settings *);
650 char *get_download_dir(struct settings *);
651 char *get_default_script(struct settings *);
652 char *get_runtime_dir(struct settings *);
653 char *get_tab_style(struct settings *);
654 char *get_work_dir(struct settings *);
655 void startpage_add(const char *, ...);
657 void walk_alias(struct settings *, void (*)(struct settings *,
658 char *, void *), void *);
659 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
660 char *, void *), void *);
661 void walk_js_wl(struct settings *, void (*)(struct settings *,
662 char *, void *), void *);
663 void walk_pl_wl(struct settings *, void (*)(struct settings *,
664 char *, void *), void *);
665 void walk_kb(struct settings *, void (*)(struct settings *, char *,
666 void *), void *);
667 void walk_mime_type(struct settings *, void (*)(struct settings *,
668 char *, void *), void *);
670 void recalc_tabs(void);
671 void recolor_compact_tabs(void);
672 void set_current_tab(int page_num);
673 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
674 void marks_clear(struct tab *t);
676 int set_http_proxy(char *);
678 struct special {
679 int (*set)(struct settings *, char *);
680 char *(*get)(struct settings *);
681 void (*walk)(struct settings *,
682 void (*cb)(struct settings *, char *, void *),
683 void *);
686 struct special s_browser_mode = {
687 set_browser_mode,
688 get_browser_mode,
689 NULL
692 struct special s_cookie = {
693 set_cookie_policy,
694 get_cookie_policy,
695 NULL
698 struct special s_alias = {
699 add_alias,
700 NULL,
701 walk_alias
704 struct special s_mime = {
705 add_mime_type,
706 NULL,
707 walk_mime_type
710 struct special s_js = {
711 add_js_wl,
712 NULL,
713 walk_js_wl
716 struct special s_pl = {
717 add_pl_wl,
718 NULL,
719 walk_pl_wl
722 struct special s_kb = {
723 add_kb,
724 NULL,
725 walk_kb
728 struct special s_cookie_wl = {
729 add_cookie_wl,
730 NULL,
731 walk_cookie_wl
734 struct special s_default_script = {
735 set_default_script,
736 get_default_script,
737 NULL
740 struct special s_download_dir = {
741 set_download_dir,
742 get_download_dir,
743 NULL
746 struct special s_work_dir = {
747 set_work_dir,
748 get_work_dir,
749 NULL
752 struct special s_tab_style = {
753 set_tab_style,
754 get_tab_style,
755 NULL
758 struct settings {
759 char *name;
760 int type;
761 #define XT_S_INVALID (0)
762 #define XT_S_INT (1)
763 #define XT_S_STR (2)
764 #define XT_S_FLOAT (3)
765 uint32_t flags;
766 #define XT_SF_RESTART (1<<0)
767 #define XT_SF_RUNTIME (1<<1)
768 int *ival;
769 char **sval;
770 struct special *s;
771 gfloat *fval;
772 int (*activate)(char *);
773 } rs[] = {
774 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
775 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
776 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
777 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
778 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
779 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
780 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
781 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
782 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
783 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
784 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
785 { "enable_plugin_whitelist", XT_S_INT, 0, &enable_plugin_whitelist, NULL, NULL },
786 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
787 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
788 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
789 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
790 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
791 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
792 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
793 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
794 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
795 { "home", XT_S_STR, 0, NULL, &home, NULL },
796 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
797 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
798 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
799 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
800 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
801 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
802 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
803 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
804 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
805 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
806 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
807 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
808 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
809 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
810 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
811 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
812 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
813 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
814 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
815 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
816 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
817 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
818 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
819 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
820 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
821 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
822 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
824 /* font settings */
825 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
826 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
827 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
828 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
830 /* runtime settings */
831 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
832 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
833 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
834 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
835 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
836 { "pl_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_pl },
839 int about(struct tab *, struct karg *);
840 int blank(struct tab *, struct karg *);
841 int ca_cmd(struct tab *, struct karg *);
842 int cookie_show_wl(struct tab *, struct karg *);
843 int js_show_wl(struct tab *, struct karg *);
844 int pl_show_wl(struct tab *, struct karg *);
845 int help(struct tab *, struct karg *);
846 int set(struct tab *, struct karg *);
847 int stats(struct tab *, struct karg *);
848 int marco(struct tab *, struct karg *);
849 int startpage(struct tab *, struct karg *);
850 const char * marco_message(int *);
851 int xtp_page_cl(struct tab *, struct karg *);
852 int xtp_page_dl(struct tab *, struct karg *);
853 int xtp_page_fl(struct tab *, struct karg *);
854 int xtp_page_hl(struct tab *, struct karg *);
855 void xt_icon_from_file(struct tab *, char *);
856 const gchar *get_uri(struct tab *);
857 const gchar *get_title(struct tab *, bool);
859 #define XT_URI_ABOUT ("about:")
860 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
861 #define XT_URI_ABOUT_ABOUT ("about")
862 #define XT_URI_ABOUT_BLANK ("blank")
863 #define XT_URI_ABOUT_CERTS ("certs")
864 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
865 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
866 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
867 #define XT_URI_ABOUT_FAVORITES ("favorites")
868 #define XT_URI_ABOUT_HELP ("help")
869 #define XT_URI_ABOUT_HISTORY ("history")
870 #define XT_URI_ABOUT_JSWL ("jswl")
871 #define XT_URI_ABOUT_PLUGINWL ("plwl")
872 #define XT_URI_ABOUT_SET ("set")
873 #define XT_URI_ABOUT_STATS ("stats")
874 #define XT_URI_ABOUT_MARCO ("marco")
875 #define XT_URI_ABOUT_STARTPAGE ("startpage")
877 struct about_type {
878 char *name;
879 int (*func)(struct tab *, struct karg *);
880 } about_list[] = {
881 { XT_URI_ABOUT_ABOUT, about },
882 { XT_URI_ABOUT_BLANK, blank },
883 { XT_URI_ABOUT_CERTS, ca_cmd },
884 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
885 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
886 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
887 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
888 { XT_URI_ABOUT_HELP, help },
889 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
890 { XT_URI_ABOUT_JSWL, js_show_wl },
891 { XT_URI_ABOUT_SET, set },
892 { XT_URI_ABOUT_STATS, stats },
893 { XT_URI_ABOUT_MARCO, marco },
894 { XT_URI_ABOUT_STARTPAGE, startpage },
895 { XT_URI_ABOUT_PLUGINWL, pl_show_wl },
898 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
899 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
900 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
901 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
902 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
903 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
904 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
906 /* globals */
907 extern char *__progname;
908 char **start_argv;
909 struct passwd *pwd;
910 GtkWidget *main_window;
911 GtkNotebook *notebook;
912 GtkWidget *tab_bar;
913 GtkWidget *arrow, *abtn;
914 struct tab_list tabs;
915 struct history_list hl;
916 struct session_list sessions;
917 struct download_list downloads;
918 struct domain_list c_wl;
919 struct domain_list js_wl;
920 struct domain_list pl_wl;
921 struct undo_tailq undos;
922 struct keybinding_list kbl;
923 struct sp_list spl;
924 struct command_list chl;
925 struct command_list shl;
926 struct command_entry *history_at;
927 struct command_entry *search_at;
928 int undo_count;
929 int updating_dl_tabs = 0;
930 int updating_hl_tabs = 0;
931 int updating_cl_tabs = 0;
932 int updating_fl_tabs = 0;
933 int cmd_history_count = 0;
934 int search_history_count = 0;
935 char *global_search;
936 long long unsigned int blocked_cookies = 0;
937 char named_session[PATH_MAX];
938 GtkListStore *completion_model;
939 GtkListStore *buffers_store;
941 void xxx_dir(char *);
942 int icon_size_map(int);
943 void completion_add(struct tab *);
944 void completion_add_uri(const gchar *);
945 void show_oops(struct tab *, const char *, ...);
947 void
948 history_delete(struct command_list *l, int *counter)
950 struct command_entry *c;
952 if (l == NULL || counter == NULL)
953 return;
955 c = TAILQ_LAST(l, command_list);
956 if (c == NULL)
957 return;
959 TAILQ_REMOVE(l, c, entry);
960 *counter -= 1;
961 g_free(c->line);
962 g_free(c);
965 void
966 history_add(struct command_list *list, char *file, char *l, int *counter)
968 struct command_entry *c;
969 FILE *f;
971 if (list == NULL || l == NULL || counter == NULL)
972 return;
974 /* don't add the same line */
975 c = TAILQ_FIRST(list);
976 if (c)
977 if (!strcmp(c->line + 1 /* skip space */, l))
978 return;
980 c = g_malloc0(sizeof *c);
981 c->line = g_strdup_printf(" %s", l);
983 *counter += 1;
984 TAILQ_INSERT_HEAD(list, c, entry);
986 if (*counter > 1000)
987 history_delete(list, counter);
989 if (history_autosave && file) {
990 f = fopen(file, "w");
991 if (f == NULL) {
992 show_oops(NULL, "couldn't write history %s", file);
993 return;
996 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
997 c->line[0] = ' ';
998 fprintf(f, "%s\n", c->line);
1001 fclose(f);
1006 history_read(struct command_list *list, char *file, int *counter)
1008 FILE *f;
1009 char *s, line[65536];
1011 if (list == NULL || file == NULL)
1012 return (1);
1014 f = fopen(file, "r");
1015 if (f == NULL) {
1016 startpage_add("couldn't open history file %s", file);
1017 return (1);
1020 for (;;) {
1021 s = fgets(line, sizeof line, f);
1022 if (s == NULL || feof(f) || ferror(f))
1023 break;
1024 if ((s = strchr(line, '\n')) == NULL) {
1025 startpage_add("invalid history file %s", file);
1026 fclose(f);
1027 return (1);
1029 *s = '\0';
1031 history_add(list, NULL, line + 1, counter);
1034 fclose(f);
1036 return (0);
1039 /* marks and quickmarks array storage.
1040 * first a-z, then A-Z, then 0-9 */
1041 char
1042 indextomark(int i)
1044 if (i < 0)
1045 return (0);
1047 if (i >= 0 && i <= 'z' - 'a')
1048 return 'a' + i;
1050 i -= 'z' - 'a' + 1;
1051 if (i >= 0 && i <= 'Z' - 'A')
1052 return 'A' + i;
1054 i -= 'Z' - 'A' + 1;
1055 if (i >= 10)
1056 return (0);
1058 return i + '0';
1062 marktoindex(char m)
1064 int ret = 0;
1066 if (m >= 'a' && m <= 'z')
1067 return ret + m - 'a';
1069 ret += 'z' - 'a' + 1;
1070 if (m >= 'A' && m <= 'Z')
1071 return ret + m - 'A';
1073 ret += 'Z' - 'A' + 1;
1074 if (m >= '0' && m <= '9')
1075 return ret + m - '0';
1077 return (-1);
1081 void
1082 sigchild(int sig)
1084 int saved_errno, status;
1085 pid_t pid;
1087 saved_errno = errno;
1089 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1090 if (pid == -1) {
1091 if (errno == EINTR)
1092 continue;
1093 if (errno != ECHILD) {
1095 clog_warn("sigchild: waitpid:");
1098 break;
1101 if (WIFEXITED(status)) {
1102 if (WEXITSTATUS(status) != 0) {
1104 clog_warnx("sigchild: child exit status: %d",
1105 WEXITSTATUS(status));
1108 } else {
1110 clog_warnx("sigchild: child is terminated abnormally");
1115 errno = saved_errno;
1119 is_g_object_setting(GObject *o, char *str)
1121 guint n_props = 0, i;
1122 GParamSpec **proplist;
1124 if (! G_IS_OBJECT(o))
1125 return (0);
1127 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1128 &n_props);
1130 for (i=0; i < n_props; i++) {
1131 if (! strcmp(proplist[i]->name, str))
1132 return (1);
1134 return (0);
1137 gchar *
1138 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1140 gchar *r;
1142 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1143 "<head>\n"
1144 "<title>%s</title>\n"
1145 "%s"
1146 "%s"
1147 "</head>\n"
1148 "<body>\n"
1149 "<h1>%s</h1>\n"
1150 "%s\n</body>\n"
1151 "</html>",
1152 title,
1153 addstyles ? XT_PAGE_STYLE : "",
1154 head,
1155 title,
1156 body);
1158 return r;
1162 * Display a web page from a HTML string in memory, rather than from a URL
1164 void
1165 load_webkit_string(struct tab *t, const char *str, gchar *title)
1167 char file[PATH_MAX];
1168 int i;
1170 /* we set this to indicate we want to manually do navaction */
1171 if (t->bfl)
1172 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1174 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1175 if (title) {
1176 /* set t->xtp_meaning */
1177 for (i = 0; i < LENGTH(about_list); i++)
1178 if (!strcmp(title, about_list[i].name)) {
1179 t->xtp_meaning = i;
1180 break;
1183 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1184 "file://");
1185 #if GTK_CHECK_VERSION(2, 20, 0)
1186 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1187 gtk_widget_hide(t->spinner);
1188 #endif
1189 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1190 xt_icon_from_file(t, file);
1194 struct tab *
1195 get_current_tab(void)
1197 struct tab *t;
1199 TAILQ_FOREACH(t, &tabs, entry) {
1200 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1201 return (t);
1204 warnx("%s: no current tab", __func__);
1206 return (NULL);
1209 void
1210 set_status(struct tab *t, gchar *s, int status)
1212 gchar *type = NULL;
1214 if (s == NULL)
1215 return;
1217 switch (status) {
1218 case XT_STATUS_LOADING:
1219 type = g_strdup_printf("Loading: %s", s);
1220 s = type;
1221 break;
1222 case XT_STATUS_LINK:
1223 type = g_strdup_printf("Link: %s", s);
1224 if (!t->status)
1225 t->status = g_strdup(gtk_entry_get_text(
1226 GTK_ENTRY(t->sbe.statusbar)));
1227 s = type;
1228 break;
1229 case XT_STATUS_URI:
1230 type = g_strdup_printf("%s", s);
1231 if (!t->status) {
1232 t->status = g_strdup(type);
1234 s = type;
1235 if (!t->status)
1236 t->status = g_strdup(s);
1237 break;
1238 case XT_STATUS_NOTHING:
1239 /* FALL THROUGH */
1240 default:
1241 break;
1243 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1244 if (type)
1245 g_free(type);
1248 void
1249 hide_cmd(struct tab *t)
1251 history_at = NULL; /* just in case */
1252 search_at = NULL; /* just in case */
1253 gtk_widget_hide(t->cmd);
1256 void
1257 show_cmd(struct tab *t)
1259 history_at = NULL;
1260 search_at = NULL;
1261 gtk_widget_hide(t->oops);
1262 gtk_widget_show(t->cmd);
1265 void
1266 hide_buffers(struct tab *t)
1268 gtk_widget_hide(t->buffers);
1269 gtk_list_store_clear(buffers_store);
1272 enum {
1273 COL_ID = 0,
1274 COL_TITLE,
1275 NUM_COLS
1279 sort_tabs_by_page_num(struct tab ***stabs)
1281 int num_tabs = 0;
1282 struct tab *t;
1284 num_tabs = gtk_notebook_get_n_pages(notebook);
1286 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1288 TAILQ_FOREACH(t, &tabs, entry)
1289 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1291 return (num_tabs);
1294 void
1295 buffers_make_list(void)
1297 int i, num_tabs;
1298 const gchar *title = NULL;
1299 GtkTreeIter iter;
1300 struct tab **stabs = NULL;
1302 num_tabs = sort_tabs_by_page_num(&stabs);
1304 for (i = 0; i < num_tabs; i++)
1305 if (stabs[i]) {
1306 gtk_list_store_append(buffers_store, &iter);
1307 title = get_title(stabs[i], FALSE);
1308 gtk_list_store_set(buffers_store, &iter,
1309 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1310 * rather than 0. */
1311 COL_TITLE, title,
1312 -1);
1315 g_free(stabs);
1318 void
1319 show_buffers(struct tab *t)
1321 buffers_make_list();
1322 gtk_widget_show(t->buffers);
1323 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1326 void
1327 toggle_buffers(struct tab *t)
1329 if (gtk_widget_get_visible(t->buffers))
1330 hide_buffers(t);
1331 else
1332 show_buffers(t);
1336 buffers(struct tab *t, struct karg *args)
1338 show_buffers(t);
1340 return (0);
1343 void
1344 hide_oops(struct tab *t)
1346 gtk_widget_hide(t->oops);
1349 void
1350 show_oops(struct tab *at, const char *fmt, ...)
1352 va_list ap;
1353 char *msg = NULL;
1354 struct tab *t = NULL;
1356 if (fmt == NULL)
1357 return;
1359 if (at == NULL) {
1360 if ((t = get_current_tab()) == NULL)
1361 return;
1362 } else
1363 t = at;
1365 va_start(ap, fmt);
1366 if (vasprintf(&msg, fmt, ap) == -1)
1367 errx(1, "show_oops failed");
1368 va_end(ap);
1370 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1371 gtk_widget_hide(t->cmd);
1372 gtk_widget_show(t->oops);
1374 if (msg)
1375 free(msg);
1378 char *
1379 get_as_string(struct settings *s)
1381 char *r = NULL;
1383 if (s == NULL)
1384 return (NULL);
1386 if (s->s) {
1387 if (s->s->get)
1388 r = s->s->get(s);
1389 else
1390 warnx("get_as_string skip %s\n", s->name);
1391 } else if (s->type == XT_S_INT)
1392 r = g_strdup_printf("%d", *s->ival);
1393 else if (s->type == XT_S_STR)
1394 r = g_strdup(*s->sval);
1395 else if (s->type == XT_S_FLOAT)
1396 r = g_strdup_printf("%f", *s->fval);
1397 else
1398 r = g_strdup_printf("INVALID TYPE");
1400 return (r);
1403 void
1404 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1406 int i;
1407 char *s;
1409 for (i = 0; i < LENGTH(rs); i++) {
1410 if (rs[i].s && rs[i].s->walk)
1411 rs[i].s->walk(&rs[i], cb, cb_args);
1412 else {
1413 s = get_as_string(&rs[i]);
1414 cb(&rs[i], s, cb_args);
1415 g_free(s);
1421 set_browser_mode(struct settings *s, char *val)
1423 if (!strcmp(val, "whitelist")) {
1424 browser_mode = XT_BM_WHITELIST;
1425 allow_volatile_cookies = 0;
1426 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1427 cookies_enabled = 1;
1428 enable_cookie_whitelist = 1;
1429 enable_plugin_whitelist = 1;
1430 enable_plugins = 0;
1431 read_only_cookies = 0;
1432 save_rejected_cookies = 0;
1433 session_timeout = 3600;
1434 enable_scripts = 0;
1435 enable_js_whitelist = 1;
1436 enable_localstorage = 0;
1437 } else if (!strcmp(val, "normal")) {
1438 browser_mode = XT_BM_NORMAL;
1439 allow_volatile_cookies = 0;
1440 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1441 cookies_enabled = 1;
1442 enable_cookie_whitelist = 0;
1443 enable_plugin_whitelist = 0;
1444 enable_plugins = 1;
1445 read_only_cookies = 0;
1446 save_rejected_cookies = 0;
1447 session_timeout = 3600;
1448 enable_scripts = 1;
1449 enable_js_whitelist = 0;
1450 enable_localstorage = 1;
1451 } else if (!strcmp(val, "kiosk")) {
1452 browser_mode = XT_BM_KIOSK;
1453 allow_volatile_cookies = 0;
1454 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1455 cookies_enabled = 1;
1456 enable_cookie_whitelist = 0;
1457 enable_plugin_whitelist = 0;
1458 enable_plugins = 1;
1459 read_only_cookies = 0;
1460 save_rejected_cookies = 0;
1461 session_timeout = 3600;
1462 enable_scripts = 1;
1463 enable_js_whitelist = 0;
1464 enable_localstorage = 1;
1465 show_tabs = 0;
1466 tabless = 1;
1467 } else
1468 return (1);
1470 return (0);
1473 char *
1474 get_browser_mode(struct settings *s)
1476 char *r = NULL;
1478 if (browser_mode == XT_BM_WHITELIST)
1479 r = g_strdup("whitelist");
1480 else if (browser_mode == XT_BM_NORMAL)
1481 r = g_strdup("normal");
1482 else if (browser_mode == XT_BM_KIOSK)
1483 r = g_strdup("kiosk");
1484 else
1485 return (NULL);
1487 return (r);
1491 set_cookie_policy(struct settings *s, char *val)
1493 if (!strcmp(val, "no3rdparty"))
1494 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1495 else if (!strcmp(val, "accept"))
1496 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1497 else if (!strcmp(val, "reject"))
1498 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1499 else
1500 return (1);
1502 return (0);
1505 char *
1506 get_cookie_policy(struct settings *s)
1508 char *r = NULL;
1510 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1511 r = g_strdup("no3rdparty");
1512 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1513 r = g_strdup("accept");
1514 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1515 r = g_strdup("reject");
1516 else
1517 return (NULL);
1519 return (r);
1522 char *
1523 get_default_script(struct settings *s)
1525 if (default_script[0] == '\0')
1526 return (0);
1527 return (g_strdup(default_script));
1531 set_default_script(struct settings *s, char *val)
1533 if (val[0] == '~')
1534 snprintf(default_script, sizeof default_script, "%s/%s",
1535 pwd->pw_dir, &val[1]);
1536 else
1537 strlcpy(default_script, val, sizeof default_script);
1539 return (0);
1542 char *
1543 get_download_dir(struct settings *s)
1545 if (download_dir[0] == '\0')
1546 return (0);
1547 return (g_strdup(download_dir));
1551 set_download_dir(struct settings *s, char *val)
1553 if (val[0] == '~')
1554 snprintf(download_dir, sizeof download_dir, "%s/%s",
1555 pwd->pw_dir, &val[1]);
1556 else
1557 strlcpy(download_dir, val, sizeof download_dir);
1559 return (0);
1563 * Session IDs.
1564 * We use these to prevent people putting xxxt:// URLs on
1565 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1567 #define XT_XTP_SES_KEY_SZ 8
1568 #define XT_XTP_SES_KEY_HEX_FMT \
1569 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1570 char *dl_session_key; /* downloads */
1571 char *hl_session_key; /* history list */
1572 char *cl_session_key; /* cookie list */
1573 char *fl_session_key; /* favorites list */
1575 char work_dir[PATH_MAX];
1576 char certs_dir[PATH_MAX];
1577 char cache_dir[PATH_MAX];
1578 char sessions_dir[PATH_MAX];
1579 char cookie_file[PATH_MAX];
1580 SoupURI *proxy_uri = NULL;
1581 SoupSession *session;
1582 SoupCookieJar *s_cookiejar;
1583 SoupCookieJar *p_cookiejar;
1584 char rc_fname[PATH_MAX];
1586 struct mime_type_list mtl;
1587 struct alias_list aliases;
1589 /* protos */
1590 struct tab *create_new_tab(char *, struct undo *, int, int);
1591 void delete_tab(struct tab *);
1592 void setzoom_webkit(struct tab *, int);
1593 int run_script(struct tab *, char *);
1594 int download_rb_cmp(struct download *, struct download *);
1595 gboolean cmd_execute(struct tab *t, char *str);
1598 history_rb_cmp(struct history *h1, struct history *h2)
1600 return (strcmp(h1->uri, h2->uri));
1602 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1605 domain_rb_cmp(struct domain *d1, struct domain *d2)
1607 return (strcmp(d1->d, d2->d));
1609 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1611 char *
1612 get_work_dir(struct settings *s)
1614 if (work_dir[0] == '\0')
1615 return (0);
1616 return (g_strdup(work_dir));
1620 set_work_dir(struct settings *s, char *val)
1622 if (val[0] == '~')
1623 snprintf(work_dir, sizeof work_dir, "%s/%s",
1624 pwd->pw_dir, &val[1]);
1625 else
1626 strlcpy(work_dir, val, sizeof work_dir);
1628 return (0);
1631 char *
1632 get_tab_style(struct settings *s)
1634 if (tab_style == XT_TABS_NORMAL)
1635 return (g_strdup("normal"));
1636 else
1637 return (g_strdup("compact"));
1641 set_tab_style(struct settings *s, char *val)
1643 if (!strcmp(val, "normal"))
1644 tab_style = XT_TABS_NORMAL;
1645 else if (!strcmp(val, "compact"))
1646 tab_style = XT_TABS_COMPACT;
1647 else
1648 return (1);
1650 return (0);
1654 * generate a session key to secure xtp commands.
1655 * pass in a ptr to the key in question and it will
1656 * be modified in place.
1658 void
1659 generate_xtp_session_key(char **key)
1661 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1663 /* free old key */
1664 if (*key)
1665 g_free(*key);
1667 /* make a new one */
1668 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1669 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1670 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1671 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1673 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1677 * validate a xtp session key.
1678 * return (1) if OK
1681 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1683 if (strcmp(trusted, untrusted) != 0) {
1684 show_oops(t, "%s: xtp session key mismatch possible spoof",
1685 __func__);
1686 return (0);
1689 return (1);
1693 download_rb_cmp(struct download *e1, struct download *e2)
1695 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1697 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1699 struct valid_url_types {
1700 char *type;
1701 } vut[] = {
1702 { "http://" },
1703 { "https://" },
1704 { "ftp://" },
1705 { "file://" },
1706 { XT_XTP_STR },
1710 valid_url_type(char *url)
1712 int i;
1714 for (i = 0; i < LENGTH(vut); i++)
1715 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1716 return (0);
1718 return (1);
1721 void
1722 print_cookie(char *msg, SoupCookie *c)
1724 if (c == NULL)
1725 return;
1727 if (msg)
1728 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1729 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1730 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1731 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1732 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1733 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1734 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1735 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1736 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1737 DNPRINTF(XT_D_COOKIE, "====================================\n");
1740 void
1741 walk_alias(struct settings *s,
1742 void (*cb)(struct settings *, char *, void *), void *cb_args)
1744 struct alias *a;
1745 char *str;
1747 if (s == NULL || cb == NULL) {
1748 show_oops(NULL, "walk_alias invalid parameters");
1749 return;
1752 TAILQ_FOREACH(a, &aliases, entry) {
1753 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1754 cb(s, str, cb_args);
1755 g_free(str);
1759 char *
1760 match_alias(char *url_in)
1762 struct alias *a;
1763 char *arg;
1764 char *url_out = NULL, *search, *enc_arg;
1766 search = g_strdup(url_in);
1767 arg = search;
1768 if (strsep(&arg, " \t") == NULL) {
1769 show_oops(NULL, "match_alias: NULL URL");
1770 goto done;
1773 TAILQ_FOREACH(a, &aliases, entry) {
1774 if (!strcmp(search, a->a_name))
1775 break;
1778 if (a != NULL) {
1779 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1780 a->a_name);
1781 if (arg != NULL) {
1782 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1783 url_out = g_strdup_printf(a->a_uri, enc_arg);
1784 g_free(enc_arg);
1785 } else
1786 url_out = g_strdup_printf(a->a_uri, "");
1788 done:
1789 g_free(search);
1790 return (url_out);
1793 char *
1794 guess_url_type(char *url_in)
1796 struct stat sb;
1797 char *url_out = NULL, *enc_search = NULL;
1798 int i;
1800 /* substitute aliases */
1801 url_out = match_alias(url_in);
1802 if (url_out != NULL)
1803 return (url_out);
1805 /* see if we are an about page */
1806 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1807 for (i = 0; i < LENGTH(about_list); i++)
1808 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1809 about_list[i].name)) {
1810 url_out = g_strdup(url_in);
1811 goto done;
1814 if (guess_search && url_regex &&
1815 !(g_str_has_prefix(url_in, "http://") ||
1816 g_str_has_prefix(url_in, "https://"))) {
1817 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1818 /* invalid URI so search instead */
1819 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1820 url_out = g_strdup_printf(search_string, enc_search);
1821 g_free(enc_search);
1822 goto done;
1826 /* XXX not sure about this heuristic */
1827 if (stat(url_in, &sb) == 0)
1828 url_out = g_strdup_printf("file://%s", url_in);
1829 else
1830 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1831 done:
1832 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1834 return (url_out);
1837 void
1838 load_uri(struct tab *t, gchar *uri)
1840 struct karg args;
1841 gchar *newuri = NULL;
1842 int i;
1844 if (uri == NULL)
1845 return;
1847 /* Strip leading spaces. */
1848 while (*uri && isspace(*uri))
1849 uri++;
1851 if (strlen(uri) == 0) {
1852 blank(t, NULL);
1853 return;
1856 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1858 if (valid_url_type(uri)) {
1859 newuri = guess_url_type(uri);
1860 uri = newuri;
1863 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1864 for (i = 0; i < LENGTH(about_list); i++)
1865 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1866 bzero(&args, sizeof args);
1867 about_list[i].func(t, &args);
1868 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1869 FALSE);
1870 goto done;
1872 show_oops(t, "invalid about page");
1873 goto done;
1876 set_status(t, (char *)uri, XT_STATUS_LOADING);
1877 marks_clear(t);
1878 webkit_web_view_load_uri(t->wv, uri);
1879 done:
1880 if (newuri)
1881 g_free(newuri);
1884 const gchar *
1885 get_uri(struct tab *t)
1887 const gchar *uri = NULL;
1889 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1890 return NULL;
1891 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1892 uri = webkit_web_view_get_uri(t->wv);
1893 } else {
1894 /* use tmp_uri to make sure it is g_freed */
1895 if (t->tmp_uri)
1896 g_free(t->tmp_uri);
1897 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1898 about_list[t->xtp_meaning].name);
1899 uri = t->tmp_uri;
1901 return uri;
1904 const gchar *
1905 get_title(struct tab *t, bool window)
1907 const gchar *set = NULL, *title = NULL;
1908 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1910 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1911 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1912 goto notitle;
1914 title = webkit_web_view_get_title(t->wv);
1915 if ((set = title ? title : get_uri(t)))
1916 return set;
1918 notitle:
1919 set = window ? XT_NAME : "(untitled)";
1921 return set;
1925 add_alias(struct settings *s, char *line)
1927 char *l, *alias;
1928 struct alias *a = NULL;
1930 if (s == NULL || line == NULL) {
1931 show_oops(NULL, "add_alias invalid parameters");
1932 return (1);
1935 l = line;
1936 a = g_malloc(sizeof(*a));
1938 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1939 show_oops(NULL, "add_alias: incomplete alias definition");
1940 goto bad;
1942 if (strlen(alias) == 0 || strlen(l) == 0) {
1943 show_oops(NULL, "add_alias: invalid alias definition");
1944 goto bad;
1947 a->a_name = g_strdup(alias);
1948 a->a_uri = g_strdup(l);
1950 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1952 TAILQ_INSERT_TAIL(&aliases, a, entry);
1954 return (0);
1955 bad:
1956 if (a)
1957 g_free(a);
1958 return (1);
1962 add_mime_type(struct settings *s, char *line)
1964 char *mime_type;
1965 char *l;
1966 struct mime_type *m = NULL;
1967 int downloadfirst = 0;
1969 /* XXX this could be smarter */
1971 if (line == NULL || strlen(line) == 0) {
1972 show_oops(NULL, "add_mime_type invalid parameters");
1973 return (1);
1976 l = line;
1977 if (*l == '@') {
1978 downloadfirst = 1;
1979 l++;
1981 m = g_malloc(sizeof(*m));
1983 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1984 show_oops(NULL, "add_mime_type: invalid mime_type");
1985 goto bad;
1987 if (mime_type[strlen(mime_type) - 1] == '*') {
1988 mime_type[strlen(mime_type) - 1] = '\0';
1989 m->mt_default = 1;
1990 } else
1991 m->mt_default = 0;
1993 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1994 show_oops(NULL, "add_mime_type: invalid mime_type");
1995 goto bad;
1998 m->mt_type = g_strdup(mime_type);
1999 m->mt_action = g_strdup(l);
2000 m->mt_download = downloadfirst;
2002 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
2003 m->mt_type, m->mt_action, m->mt_default);
2005 TAILQ_INSERT_TAIL(&mtl, m, entry);
2007 return (0);
2008 bad:
2009 if (m)
2010 g_free(m);
2011 return (1);
2014 struct mime_type *
2015 find_mime_type(char *mime_type)
2017 struct mime_type *m, *def = NULL, *rv = NULL;
2019 TAILQ_FOREACH(m, &mtl, entry) {
2020 if (m->mt_default &&
2021 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
2022 def = m;
2024 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
2025 rv = m;
2026 break;
2030 if (rv == NULL)
2031 rv = def;
2033 return (rv);
2036 void
2037 walk_mime_type(struct settings *s,
2038 void (*cb)(struct settings *, char *, void *), void *cb_args)
2040 struct mime_type *m;
2041 char *str;
2043 if (s == NULL || cb == NULL) {
2044 show_oops(NULL, "walk_mime_type invalid parameters");
2045 return;
2048 TAILQ_FOREACH(m, &mtl, entry) {
2049 str = g_strdup_printf("%s%s --> %s",
2050 m->mt_type,
2051 m->mt_default ? "*" : "",
2052 m->mt_action);
2053 cb(s, str, cb_args);
2054 g_free(str);
2058 void
2059 wl_add(char *str, struct domain_list *wl, int handy)
2061 struct domain *d;
2062 int add_dot = 0;
2063 char *p;
2065 if (str == NULL || wl == NULL || strlen(str) < 2)
2066 return;
2068 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2070 /* treat *.moo.com the same as .moo.com */
2071 if (str[0] == '*' && str[1] == '.')
2072 str = &str[1];
2073 else if (str[0] == '.')
2074 str = &str[0];
2075 else
2076 add_dot = 1;
2078 /* slice off port number */
2079 p = g_strrstr(str, ":");
2080 if (p)
2081 *p = '\0';
2083 d = g_malloc(sizeof *d);
2084 if (add_dot)
2085 d->d = g_strdup_printf(".%s", str);
2086 else
2087 d->d = g_strdup(str);
2088 d->handy = handy;
2090 if (RB_INSERT(domain_list, wl, d))
2091 goto unwind;
2093 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2094 return;
2095 unwind:
2096 if (d) {
2097 if (d->d)
2098 g_free(d->d);
2099 g_free(d);
2104 add_cookie_wl(struct settings *s, char *entry)
2106 wl_add(entry, &c_wl, 1);
2107 return (0);
2110 void
2111 walk_cookie_wl(struct settings *s,
2112 void (*cb)(struct settings *, char *, void *), void *cb_args)
2114 struct domain *d;
2116 if (s == NULL || cb == NULL) {
2117 show_oops(NULL, "walk_cookie_wl invalid parameters");
2118 return;
2121 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2122 cb(s, d->d, cb_args);
2125 void
2126 walk_js_wl(struct settings *s,
2127 void (*cb)(struct settings *, char *, void *), void *cb_args)
2129 struct domain *d;
2131 if (s == NULL || cb == NULL) {
2132 show_oops(NULL, "walk_js_wl invalid parameters");
2133 return;
2136 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2137 cb(s, d->d, cb_args);
2141 add_js_wl(struct settings *s, char *entry)
2143 wl_add(entry, &js_wl, 1 /* persistent */);
2144 return (0);
2147 void
2148 walk_pl_wl(struct settings *s,
2149 void (*cb)(struct settings *, char *, void *), void *cb_args)
2151 struct domain *d;
2153 if (s == NULL || cb == NULL) {
2154 show_oops(NULL, "walk_pl_wl invalid parameters");
2155 return;
2158 RB_FOREACH_REVERSE(d, domain_list, &pl_wl)
2159 cb(s, d->d, cb_args);
2163 add_pl_wl(struct settings *s, char *entry)
2165 wl_add(entry, &pl_wl, 1 /* persistent */);
2166 return (0);
2169 struct domain *
2170 wl_find(const gchar *search, struct domain_list *wl)
2172 int i;
2173 struct domain *d = NULL, dfind;
2174 gchar *s = NULL;
2176 if (search == NULL || wl == NULL)
2177 return (NULL);
2178 if (strlen(search) < 2)
2179 return (NULL);
2181 if (search[0] != '.')
2182 s = g_strdup_printf(".%s", search);
2183 else
2184 s = g_strdup(search);
2186 for (i = strlen(s) - 1; i >= 0; i--) {
2187 if (s[i] == '.') {
2188 dfind.d = &s[i];
2189 d = RB_FIND(domain_list, wl, &dfind);
2190 if (d)
2191 goto done;
2195 done:
2196 if (s)
2197 g_free(s);
2199 return (d);
2202 struct domain *
2203 wl_find_uri(const gchar *s, struct domain_list *wl)
2205 int i;
2206 char *ss;
2207 struct domain *r;
2209 if (s == NULL || wl == NULL)
2210 return (NULL);
2212 if (!strncmp(s, "http://", strlen("http://")))
2213 s = &s[strlen("http://")];
2214 else if (!strncmp(s, "https://", strlen("https://")))
2215 s = &s[strlen("https://")];
2217 if (strlen(s) < 2)
2218 return (NULL);
2220 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2221 /* chop string at first slash */
2222 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2223 ss = g_strdup(s);
2224 ss[i] = '\0';
2225 r = wl_find(ss, wl);
2226 g_free(ss);
2227 return (r);
2230 return (NULL);
2234 settings_add(char *var, char *val)
2236 int i, rv, *p;
2237 gfloat *f;
2238 char **s;
2240 /* get settings */
2241 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2242 if (strcmp(var, rs[i].name))
2243 continue;
2245 if (rs[i].s) {
2246 if (rs[i].s->set(&rs[i], val))
2247 errx(1, "invalid value for %s: %s", var, val);
2248 rv = 1;
2249 break;
2250 } else
2251 switch (rs[i].type) {
2252 case XT_S_INT:
2253 p = rs[i].ival;
2254 *p = atoi(val);
2255 rv = 1;
2256 break;
2257 case XT_S_STR:
2258 s = rs[i].sval;
2259 if (s == NULL)
2260 errx(1, "invalid sval for %s",
2261 rs[i].name);
2262 if (*s)
2263 g_free(*s);
2264 *s = g_strdup(val);
2265 rv = 1;
2266 break;
2267 case XT_S_FLOAT:
2268 f = rs[i].fval;
2269 *f = atof(val);
2270 rv = 1;
2271 break;
2272 case XT_S_INVALID:
2273 default:
2274 errx(1, "invalid type for %s", var);
2276 break;
2278 return (rv);
2281 #define WS "\n= \t"
2282 void
2283 config_parse(char *filename, int runtime)
2285 FILE *config, *f;
2286 char *line, *cp, *var, *val;
2287 size_t len, lineno = 0;
2288 int handled;
2289 char file[PATH_MAX];
2290 struct stat sb;
2292 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2294 if (filename == NULL)
2295 return;
2297 if (runtime && runtime_settings[0] != '\0') {
2298 snprintf(file, sizeof file, "%s/%s",
2299 work_dir, runtime_settings);
2300 if (stat(file, &sb)) {
2301 warnx("runtime file doesn't exist, creating it");
2302 if ((f = fopen(file, "w")) == NULL)
2303 err(1, "runtime");
2304 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2305 fclose(f);
2307 } else
2308 strlcpy(file, filename, sizeof file);
2310 if ((config = fopen(file, "r")) == NULL) {
2311 warn("config_parse: cannot open %s", filename);
2312 return;
2315 for (;;) {
2316 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2317 if (feof(config) || ferror(config))
2318 break;
2320 cp = line;
2321 cp += (long)strspn(cp, WS);
2322 if (cp[0] == '\0') {
2323 /* empty line */
2324 free(line);
2325 continue;
2328 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2329 startpage_add("invalid configuration file entry: %s",
2330 line);
2331 else {
2332 cp += (long)strspn(cp, WS);
2334 if ((val = strsep(&cp, "\0")) == NULL)
2335 break;
2337 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",
2338 var, val);
2339 handled = settings_add(var, val);
2341 if (handled == 0)
2342 startpage_add("invalid configuration file entry"
2343 ": %s=%s", var, val);
2346 free(line);
2349 fclose(config);
2352 char *
2353 js_ref_to_string(JSContextRef context, JSValueRef ref)
2355 char *s = NULL;
2356 size_t l;
2357 JSStringRef jsref;
2359 jsref = JSValueToStringCopy(context, ref, NULL);
2360 if (jsref == NULL)
2361 return (NULL);
2363 l = JSStringGetMaximumUTF8CStringSize(jsref);
2364 s = g_malloc(l);
2365 if (s)
2366 JSStringGetUTF8CString(jsref, s, l);
2367 JSStringRelease(jsref);
2369 return (s);
2372 void
2373 disable_hints(struct tab *t)
2375 bzero(t->hint_buf, sizeof t->hint_buf);
2376 bzero(t->hint_num, sizeof t->hint_num);
2377 run_script(t, "vimprobable_clear()");
2378 t->hints_on = 0;
2379 t->hint_mode = XT_HINT_NONE;
2382 void
2383 enable_hints(struct tab *t)
2385 bzero(t->hint_buf, sizeof t->hint_buf);
2386 run_script(t, "vimprobable_show_hints()");
2387 t->hints_on = 1;
2388 t->hint_mode = XT_HINT_NONE;
2391 #define XT_JS_OPEN ("open;")
2392 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2393 #define XT_JS_FIRE ("fire;")
2394 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2395 #define XT_JS_FOUND ("found;")
2396 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2399 run_script(struct tab *t, char *s)
2401 JSGlobalContextRef ctx;
2402 WebKitWebFrame *frame;
2403 JSStringRef str;
2404 JSValueRef val, exception;
2405 char *es, buf[128];
2407 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2408 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2410 frame = webkit_web_view_get_main_frame(t->wv);
2411 ctx = webkit_web_frame_get_global_context(frame);
2413 str = JSStringCreateWithUTF8CString(s);
2414 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2415 NULL, 0, &exception);
2416 JSStringRelease(str);
2418 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2419 if (val == NULL) {
2420 es = js_ref_to_string(ctx, exception);
2421 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2422 g_free(es);
2423 return (1);
2424 } else {
2425 es = js_ref_to_string(ctx, val);
2426 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2428 /* handle return value right here */
2429 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2430 disable_hints(t);
2431 marks_clear(t);
2432 load_uri(t, &es[XT_JS_OPEN_LEN]);
2435 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2436 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2437 &es[XT_JS_FIRE_LEN]);
2438 run_script(t, buf);
2439 disable_hints(t);
2442 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2443 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2444 disable_hints(t);
2447 g_free(es);
2450 return (0);
2454 hint(struct tab *t, struct karg *args)
2457 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2459 if (t->hints_on == 0)
2460 enable_hints(t);
2461 else
2462 disable_hints(t);
2464 return (0);
2467 void
2468 apply_style(struct tab *t)
2470 g_object_set(G_OBJECT(t->settings),
2471 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2475 userstyle(struct tab *t, struct karg *args)
2477 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2479 if (t->styled) {
2480 t->styled = 0;
2481 g_object_set(G_OBJECT(t->settings),
2482 "user-stylesheet-uri", NULL, (char *)NULL);
2483 } else {
2484 t->styled = 1;
2485 apply_style(t);
2487 return (0);
2491 * Doesn't work fully, due to the following bug:
2492 * https://bugs.webkit.org/show_bug.cgi?id=51747
2495 restore_global_history(void)
2497 char file[PATH_MAX];
2498 FILE *f;
2499 struct history *h;
2500 gchar *uri;
2501 gchar *title;
2502 const char delim[3] = {'\\', '\\', '\0'};
2504 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2506 if ((f = fopen(file, "r")) == NULL) {
2507 warnx("%s: fopen", __func__);
2508 return (1);
2511 for (;;) {
2512 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2513 if (feof(f) || ferror(f))
2514 break;
2516 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2517 if (feof(f) || ferror(f)) {
2518 free(uri);
2519 warnx("%s: broken history file\n", __func__);
2520 return (1);
2523 if (uri && strlen(uri) && title && strlen(title)) {
2524 webkit_web_history_item_new_with_data(uri, title);
2525 h = g_malloc(sizeof(struct history));
2526 h->uri = g_strdup(uri);
2527 h->title = g_strdup(title);
2528 RB_INSERT(history_list, &hl, h);
2529 completion_add_uri(h->uri);
2530 } else {
2531 warnx("%s: failed to restore history\n", __func__);
2532 free(uri);
2533 free(title);
2534 return (1);
2537 free(uri);
2538 free(title);
2539 uri = NULL;
2540 title = NULL;
2543 return (0);
2547 save_global_history_to_disk(struct tab *t)
2549 char file[PATH_MAX];
2550 FILE *f;
2551 struct history *h;
2553 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2555 if ((f = fopen(file, "w")) == NULL) {
2556 show_oops(t, "%s: global history file: %s",
2557 __func__, strerror(errno));
2558 return (1);
2561 RB_FOREACH_REVERSE(h, history_list, &hl) {
2562 if (h->uri && h->title)
2563 fprintf(f, "%s\n%s\n", h->uri, h->title);
2566 fclose(f);
2568 return (0);
2572 quit(struct tab *t, struct karg *args)
2574 if (save_global_history)
2575 save_global_history_to_disk(t);
2577 gtk_main_quit();
2579 return (1);
2582 void
2583 restore_sessions_list(void)
2585 DIR *sdir = NULL;
2586 struct dirent *dp = NULL;
2587 struct session *s;
2589 sdir = opendir(sessions_dir);
2590 if (sdir) {
2591 while ((dp = readdir(sdir)) != NULL)
2592 if (dp->d_type == DT_REG) {
2593 s = g_malloc(sizeof(struct session));
2594 s->name = g_strdup(dp->d_name);
2595 TAILQ_INSERT_TAIL(&sessions, s, entry);
2597 closedir(sdir);
2602 open_tabs(struct tab *t, struct karg *a)
2604 char file[PATH_MAX];
2605 FILE *f = NULL;
2606 char *uri = NULL;
2607 int rv = 1;
2608 struct tab *ti, *tt;
2610 if (a == NULL)
2611 goto done;
2613 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2614 if ((f = fopen(file, "r")) == NULL)
2615 goto done;
2617 ti = TAILQ_LAST(&tabs, tab_list);
2619 for (;;) {
2620 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2621 if (feof(f) || ferror(f))
2622 break;
2624 /* retrieve session name */
2625 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2626 strlcpy(named_session,
2627 &uri[strlen(XT_SAVE_SESSION_ID)],
2628 sizeof named_session);
2629 continue;
2632 if (uri && strlen(uri))
2633 create_new_tab(uri, NULL, 1, -1);
2635 free(uri);
2636 uri = NULL;
2639 /* close open tabs */
2640 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2641 for (;;) {
2642 tt = TAILQ_FIRST(&tabs);
2643 if (tt != ti) {
2644 delete_tab(tt);
2645 continue;
2647 delete_tab(tt);
2648 break;
2650 recalc_tabs();
2653 rv = 0;
2654 done:
2655 if (f)
2656 fclose(f);
2658 return (rv);
2662 restore_saved_tabs(void)
2664 char file[PATH_MAX];
2665 int unlink_file = 0;
2666 struct stat sb;
2667 struct karg a;
2668 int rv = 0;
2670 snprintf(file, sizeof file, "%s/%s",
2671 sessions_dir, XT_RESTART_TABS_FILE);
2672 if (stat(file, &sb) == -1)
2673 a.s = XT_SAVED_TABS_FILE;
2674 else {
2675 unlink_file = 1;
2676 a.s = XT_RESTART_TABS_FILE;
2679 a.i = XT_SES_DONOTHING;
2680 rv = open_tabs(NULL, &a);
2682 if (unlink_file)
2683 unlink(file);
2685 return (rv);
2689 save_tabs(struct tab *t, struct karg *a)
2691 char file[PATH_MAX];
2692 FILE *f;
2693 int num_tabs = 0, i;
2694 struct tab **stabs = NULL;
2696 if (a == NULL)
2697 return (1);
2698 if (a->s == NULL)
2699 snprintf(file, sizeof file, "%s/%s",
2700 sessions_dir, named_session);
2701 else
2702 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2704 if ((f = fopen(file, "w")) == NULL) {
2705 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2706 return (1);
2709 /* save session name */
2710 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2712 /* Save tabs, in the order they are arranged in the notebook. */
2713 num_tabs = sort_tabs_by_page_num(&stabs);
2715 for (i = 0; i < num_tabs; i++)
2716 if (stabs[i]) {
2717 if (get_uri(stabs[i]) != NULL)
2718 fprintf(f, "%s\n", get_uri(stabs[i]));
2719 else if (gtk_entry_get_text(GTK_ENTRY(
2720 stabs[i]->uri_entry)))
2721 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2722 stabs[i]->uri_entry)));
2725 g_free(stabs);
2727 /* try and make sure this gets to disk NOW. XXX Backup first? */
2728 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2729 show_oops(t, "May not have managed to save session: %s",
2730 strerror(errno));
2733 fclose(f);
2735 return (0);
2739 save_tabs_and_quit(struct tab *t, struct karg *args)
2741 struct karg a;
2743 a.s = NULL;
2744 save_tabs(t, &a);
2745 quit(t, NULL);
2747 return (1);
2751 run_page_script(struct tab *t, struct karg *args)
2753 const gchar *uri;
2754 char *tmp, script[PATH_MAX];
2756 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2757 if (tmp[0] == '\0') {
2758 show_oops(t, "no script specified");
2759 return (1);
2762 if ((uri = get_uri(t)) == NULL) {
2763 show_oops(t, "tab is empty, not running script");
2764 return (1);
2767 if (tmp[0] == '~')
2768 snprintf(script, sizeof script, "%s/%s",
2769 pwd->pw_dir, &tmp[1]);
2770 else
2771 strlcpy(script, tmp, sizeof script);
2773 switch (fork()) {
2774 case -1:
2775 show_oops(t, "can't fork to run script");
2776 return (1);
2777 /* NOTREACHED */
2778 case 0:
2779 break;
2780 default:
2781 return (0);
2784 /* child */
2785 execlp(script, script, uri, (void *)NULL);
2787 _exit(0);
2789 /* NOTREACHED */
2791 return (0);
2795 yank_uri(struct tab *t, struct karg *args)
2797 const gchar *uri;
2798 GtkClipboard *clipboard;
2800 if ((uri = get_uri(t)) == NULL)
2801 return (1);
2803 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2804 gtk_clipboard_set_text(clipboard, uri, -1);
2806 return (0);
2810 paste_uri(struct tab *t, struct karg *args)
2812 GtkClipboard *clipboard;
2813 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2814 gint len;
2815 gchar *p = NULL, *uri;
2817 /* try primary clipboard first */
2818 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2819 p = gtk_clipboard_wait_for_text(clipboard);
2821 /* if it failed get whatever text is in cut_buffer0 */
2822 if (p == NULL && xterm_workaround)
2823 if (gdk_property_get(gdk_get_default_root_window(),
2824 atom,
2825 gdk_atom_intern("STRING", FALSE),
2827 1024 * 1024 /* picked out of my butt */,
2828 FALSE,
2829 NULL,
2830 NULL,
2831 &len,
2832 (guchar **)&p)) {
2833 /* yes sir, we need to NUL the string */
2834 p[len] = '\0';
2837 if (p) {
2838 uri = p;
2839 while (*uri && isspace(*uri))
2840 uri++;
2841 if (strlen(uri) == 0) {
2842 show_oops(t, "empty paste buffer");
2843 goto done;
2845 if (guess_search == 0 && valid_url_type(uri)) {
2846 /* we can be clever and paste this in search box */
2847 show_oops(t, "not a valid URL");
2848 goto done;
2851 if (args->i == XT_PASTE_CURRENT_TAB)
2852 load_uri(t, uri);
2853 else if (args->i == XT_PASTE_NEW_TAB)
2854 create_new_tab(uri, NULL, 1, -1);
2857 done:
2858 if (p)
2859 g_free(p);
2861 return (0);
2864 gchar *
2865 find_domain(const gchar *s, int toplevel)
2867 SoupURI *uri;
2868 gchar *ret, *p;
2870 if (s == NULL)
2871 return (NULL);
2873 uri = soup_uri_new(s);
2875 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2876 return (NULL);
2879 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2880 if ((p = strrchr(uri->host, '.')) != NULL) {
2881 while(--p >= uri->host && *p != '.');
2882 p++;
2883 } else
2884 p = uri->host;
2885 } else
2886 p = uri->host;
2888 ret = g_strdup_printf(".%s", p);
2890 soup_uri_free(uri);
2892 return ret;
2896 toggle_cwl(struct tab *t, struct karg *args)
2898 struct domain *d;
2899 const gchar *uri;
2900 char *dom = NULL;
2901 int es;
2903 if (args == NULL)
2904 return (1);
2906 uri = get_uri(t);
2907 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2909 if (uri == NULL || dom == NULL ||
2910 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2911 show_oops(t, "Can't toggle domain in cookie white list");
2912 goto done;
2914 d = wl_find(dom, &c_wl);
2916 if (d == NULL)
2917 es = 0;
2918 else
2919 es = 1;
2921 if (args->i & XT_WL_TOGGLE)
2922 es = !es;
2923 else if ((args->i & XT_WL_ENABLE) && es != 1)
2924 es = 1;
2925 else if ((args->i & XT_WL_DISABLE) && es != 0)
2926 es = 0;
2928 if (es)
2929 /* enable cookies for domain */
2930 wl_add(dom, &c_wl, 0);
2931 else
2932 /* disable cookies for domain */
2933 RB_REMOVE(domain_list, &c_wl, d);
2935 if (args->i & XT_WL_RELOAD)
2936 webkit_web_view_reload(t->wv);
2938 done:
2939 g_free(dom);
2940 return (0);
2944 toggle_js(struct tab *t, struct karg *args)
2946 int es;
2947 const gchar *uri;
2948 struct domain *d;
2949 char *dom = NULL;
2951 if (args == NULL)
2952 return (1);
2954 g_object_get(G_OBJECT(t->settings),
2955 "enable-scripts", &es, (char *)NULL);
2956 if (args->i & XT_WL_TOGGLE)
2957 es = !es;
2958 else if ((args->i & XT_WL_ENABLE) && es != 1)
2959 es = 1;
2960 else if ((args->i & XT_WL_DISABLE) && es != 0)
2961 es = 0;
2962 else
2963 return (1);
2965 uri = get_uri(t);
2966 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2968 if (uri == NULL || dom == NULL ||
2969 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2970 show_oops(t, "Can't toggle domain in JavaScript white list");
2971 goto done;
2974 if (es) {
2975 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2976 wl_add(dom, &js_wl, 0 /* session */);
2977 } else {
2978 d = wl_find(dom, &js_wl);
2979 if (d)
2980 RB_REMOVE(domain_list, &js_wl, d);
2981 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2983 g_object_set(G_OBJECT(t->settings),
2984 "enable-scripts", es, (char *)NULL);
2985 g_object_set(G_OBJECT(t->settings),
2986 "javascript-can-open-windows-automatically", es, (char *)NULL);
2987 webkit_web_view_set_settings(t->wv, t->settings);
2989 if (args->i & XT_WL_RELOAD)
2990 webkit_web_view_reload(t->wv);
2991 done:
2992 if (dom)
2993 g_free(dom);
2994 return (0);
2998 toggle_pl(struct tab *t, struct karg *args)
3000 int es;
3001 const gchar *uri;
3002 struct domain *d;
3003 char *dom = NULL;
3005 if (args == NULL)
3006 return (1);
3008 g_object_get(G_OBJECT(t->settings),
3009 "enable-plugins", &es, (char *)NULL);
3010 if (args->i & XT_WL_TOGGLE)
3011 es = !es;
3012 else if ((args->i & XT_WL_ENABLE) && es != 1)
3013 es = 1;
3014 else if ((args->i & XT_WL_DISABLE) && es != 0)
3015 es = 0;
3016 else
3017 return (1);
3019 uri = get_uri(t);
3020 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3022 if (uri == NULL || dom == NULL ||
3023 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3024 show_oops(t, "Can't toggle domain in plugins white list");
3025 goto done;
3028 if (es)
3029 wl_add(dom, &pl_wl, 0 /* session */);
3030 else {
3031 d = wl_find(dom, &pl_wl);
3032 if (d)
3033 RB_REMOVE(domain_list, &pl_wl, d);
3035 g_object_set(G_OBJECT(t->settings),
3036 "enable-plugins", es, (char *)NULL);
3037 webkit_web_view_set_settings(t->wv, t->settings);
3039 if (args->i & XT_WL_RELOAD)
3040 webkit_web_view_reload(t->wv);
3041 done:
3042 if (dom)
3043 g_free(dom);
3044 return (0);
3047 void
3048 js_toggle_cb(GtkWidget *w, struct tab *t)
3050 struct karg a;
3051 int es, set;
3053 g_object_get(G_OBJECT(t->settings),
3054 "enable-scripts", &es, (char *)NULL);
3055 es = !es;
3056 if (es)
3057 set = XT_WL_ENABLE;
3058 else
3059 set = XT_WL_DISABLE;
3061 a.i = set | XT_WL_TOPLEVEL;
3062 toggle_pl(t, &a);
3064 a.i = set | XT_WL_TOPLEVEL;
3065 toggle_cwl(t, &a);
3067 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
3068 toggle_js(t, &a);
3072 toggle_src(struct tab *t, struct karg *args)
3074 gboolean mode;
3076 if (t == NULL)
3077 return (0);
3079 mode = webkit_web_view_get_view_source_mode(t->wv);
3080 webkit_web_view_set_view_source_mode(t->wv, !mode);
3081 webkit_web_view_reload(t->wv);
3083 return (0);
3086 void
3087 focus_webview(struct tab *t)
3089 if (t == NULL)
3090 return;
3092 /* only grab focus if we are visible */
3093 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
3094 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
3098 focus(struct tab *t, struct karg *args)
3100 if (t == NULL || args == NULL)
3101 return (1);
3103 if (show_url == 0)
3104 return (0);
3106 if (args->i == XT_FOCUS_URI)
3107 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
3108 else if (args->i == XT_FOCUS_SEARCH)
3109 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
3111 return (0);
3115 stats(struct tab *t, struct karg *args)
3117 char *page, *body, *s, line[64 * 1024];
3118 long long unsigned int line_count = 0;
3119 FILE *r_cookie_f;
3121 if (t == NULL)
3122 show_oops(NULL, "stats invalid parameters");
3124 line[0] = '\0';
3125 if (save_rejected_cookies) {
3126 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3127 for (;;) {
3128 s = fgets(line, sizeof line, r_cookie_f);
3129 if (s == NULL || feof(r_cookie_f) ||
3130 ferror(r_cookie_f))
3131 break;
3132 line_count++;
3134 fclose(r_cookie_f);
3135 snprintf(line, sizeof line,
3136 "<br/>Cookies blocked(*) total: %llu", line_count);
3137 } else
3138 show_oops(t, "Can't open blocked cookies file: %s",
3139 strerror(errno));
3142 body = g_strdup_printf(
3143 "Cookies blocked(*) this session: %llu"
3144 "%s"
3145 "<p><small><b>*</b> results vary based on settings</small></p>",
3146 blocked_cookies,
3147 line);
3149 page = get_html_page("Statistics", body, "", 0);
3150 g_free(body);
3152 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3153 g_free(page);
3155 return (0);
3159 marco(struct tab *t, struct karg *args)
3161 char *page, line[64 * 1024];
3162 int len;
3164 if (t == NULL)
3165 show_oops(NULL, "marco invalid parameters");
3167 line[0] = '\0';
3168 snprintf(line, sizeof line, "%s", marco_message(&len));
3170 page = get_html_page("Marco Sez...", line, "", 0);
3172 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3173 g_free(page);
3175 return (0);
3179 blank(struct tab *t, struct karg *args)
3181 if (t == NULL)
3182 show_oops(NULL, "blank invalid parameters");
3184 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3186 return (0);
3190 about(struct tab *t, struct karg *args)
3192 char *page, *body;
3194 if (t == NULL)
3195 show_oops(NULL, "about invalid parameters");
3197 body = g_strdup_printf("<b>Version: %s</b>"
3198 #ifdef XXXTERM_BUILDSTR
3199 "<br><b>Build: %s</b>"
3200 #endif
3201 "<p>"
3202 "Authors:"
3203 "<ul>"
3204 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3205 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3206 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3207 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3208 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3209 "</ul>"
3210 "Copyrights and licenses can be found on the XXXTerm "
3211 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3212 "</p>",
3213 #ifdef XXXTERM_BUILDSTR
3214 version, XXXTERM_BUILDSTR
3215 #else
3216 version
3217 #endif
3220 page = get_html_page("About", body, "", 0);
3221 g_free(body);
3223 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3224 g_free(page);
3226 return (0);
3230 help(struct tab *t, struct karg *args)
3232 char *page, *head, *body;
3234 if (t == NULL)
3235 show_oops(NULL, "help invalid parameters");
3237 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3238 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3239 "</head>\n";
3240 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3241 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3242 "cgi-bin/man-cgi?xxxterm</a>";
3244 page = get_html_page(XT_NAME, body, head, FALSE);
3246 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3247 g_free(page);
3249 return (0);
3253 startpage(struct tab *t, struct karg *args)
3255 char *page, *body, *b;
3256 struct sp *s;
3258 if (t == NULL)
3259 show_oops(NULL, "startpage invalid parameters");
3261 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3263 TAILQ_FOREACH(s, &spl, entry) {
3264 b = body;
3265 body = g_strdup_printf("%s%s<br>", body, s->line);
3266 g_free(b);
3269 page = get_html_page("Startup Exception", body, "", 0);
3270 g_free(body);
3272 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3273 g_free(page);
3275 return (0);
3278 void
3279 startpage_add(const char *fmt, ...)
3281 va_list ap;
3282 char *msg;
3283 struct sp *s;
3285 if (fmt == NULL)
3286 return;
3288 va_start(ap, fmt);
3289 if (vasprintf(&msg, fmt, ap) == -1)
3290 errx(1, "startpage_add failed");
3291 va_end(ap);
3293 s = g_malloc0(sizeof *s);
3294 s->line = msg;
3296 TAILQ_INSERT_TAIL(&spl, s, entry);
3300 * update all favorite tabs apart from one. Pass NULL if
3301 * you want to update all.
3303 void
3304 update_favorite_tabs(struct tab *apart_from)
3306 struct tab *t;
3307 if (!updating_fl_tabs) {
3308 updating_fl_tabs = 1; /* stop infinite recursion */
3309 TAILQ_FOREACH(t, &tabs, entry)
3310 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3311 && (t != apart_from))
3312 xtp_page_fl(t, NULL);
3313 updating_fl_tabs = 0;
3317 /* show a list of favorites (bookmarks) */
3319 xtp_page_fl(struct tab *t, struct karg *args)
3321 char file[PATH_MAX];
3322 FILE *f;
3323 char *uri = NULL, *title = NULL;
3324 size_t len, lineno = 0;
3325 int i, failed = 0;
3326 char *body, *tmp, *page = NULL;
3327 const char delim[3] = {'\\', '\\', '\0'};
3329 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3331 if (t == NULL)
3332 warn("%s: bad param", __func__);
3334 /* new session key */
3335 if (!updating_fl_tabs)
3336 generate_xtp_session_key(&fl_session_key);
3338 /* open favorites */
3339 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3340 if ((f = fopen(file, "r")) == NULL) {
3341 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3342 return (1);
3345 /* body */
3346 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3347 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3348 "<th style='width: 40px'>Rm</th></tr>\n");
3350 for (i = 1;;) {
3351 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3352 break;
3353 if (strlen(title) == 0 || title[0] == '#') {
3354 free(title);
3355 title = NULL;
3356 continue;
3359 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3360 if (feof(f) || ferror(f)) {
3361 show_oops(t, "favorites file corrupt");
3362 failed = 1;
3363 break;
3366 tmp = body;
3367 body = g_strdup_printf("%s<tr>"
3368 "<td>%d</td>"
3369 "<td><a href='%s'>%s</a></td>"
3370 "<td style='text-align: center'>"
3371 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3372 "</tr>\n",
3373 body, i, uri, title,
3374 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3376 g_free(tmp);
3378 free(uri);
3379 uri = NULL;
3380 free(title);
3381 title = NULL;
3382 i++;
3384 fclose(f);
3386 /* if none, say so */
3387 if (i == 1) {
3388 tmp = body;
3389 body = g_strdup_printf("%s<tr>"
3390 "<td colspan='3' style='text-align: center'>"
3391 "No favorites - To add one use the 'favadd' command."
3392 "</td></tr>", body);
3393 g_free(tmp);
3396 tmp = body;
3397 body = g_strdup_printf("%s</table>", body);
3398 g_free(tmp);
3400 if (uri)
3401 free(uri);
3402 if (title)
3403 free(title);
3405 /* render */
3406 if (!failed) {
3407 page = get_html_page("Favorites", body, "", 1);
3408 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3409 g_free(page);
3412 update_favorite_tabs(t);
3414 if (body)
3415 g_free(body);
3417 return (failed);
3420 void
3421 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3422 size_t cert_count, char *title)
3424 gnutls_datum_t cinfo;
3425 char *tmp, *body;
3426 int i;
3428 body = g_strdup("");
3430 for (i = 0; i < cert_count; i++) {
3431 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3432 &cinfo))
3433 return;
3435 tmp = body;
3436 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3437 body, i, cinfo.data);
3438 gnutls_free(cinfo.data);
3439 g_free(tmp);
3442 tmp = get_html_page(title, body, "", 0);
3443 g_free(body);
3445 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3446 g_free(tmp);
3450 ca_cmd(struct tab *t, struct karg *args)
3452 FILE *f = NULL;
3453 int rv = 1, certs = 0, certs_read;
3454 struct stat sb;
3455 gnutls_datum_t dt;
3456 gnutls_x509_crt_t *c = NULL;
3457 char *certs_buf = NULL, *s;
3459 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3460 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3461 return (1);
3464 if (fstat(fileno(f), &sb) == -1) {
3465 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3466 goto done;
3469 certs_buf = g_malloc(sb.st_size + 1);
3470 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3471 show_oops(t, "Can't read CA file: %s", strerror(errno));
3472 goto done;
3474 certs_buf[sb.st_size] = '\0';
3476 s = certs_buf;
3477 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3478 certs++;
3479 s += strlen("BEGIN CERTIFICATE");
3482 bzero(&dt, sizeof dt);
3483 dt.data = (unsigned char *)certs_buf;
3484 dt.size = sb.st_size;
3485 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3486 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3487 GNUTLS_X509_FMT_PEM, 0);
3488 if (certs_read <= 0) {
3489 show_oops(t, "No cert(s) available");
3490 goto done;
3492 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3493 done:
3494 if (c)
3495 g_free(c);
3496 if (certs_buf)
3497 g_free(certs_buf);
3498 if (f)
3499 fclose(f);
3501 return (rv);
3505 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3506 size_t domain_sz)
3508 SoupURI *su = NULL;
3509 struct addrinfo hints, *res = NULL, *ai;
3510 int rv = -1, s = -1, on, error;
3511 char port[8];
3512 static gchar myerror[256]; /* this is not thread safe */
3514 myerror[0] = '\0';
3515 *error_str = myerror;
3516 if (uri && !g_str_has_prefix(uri, "https://")) {
3517 *error_str = "invalid URI";
3518 goto done;
3521 su = soup_uri_new(uri);
3522 if (su == NULL) {
3523 *error_str = "invalid soup URI";
3524 goto done;
3526 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3527 *error_str = "invalid HTTPS URI";
3528 goto done;
3531 snprintf(port, sizeof port, "%d", su->port);
3532 bzero(&hints, sizeof(struct addrinfo));
3533 hints.ai_flags = AI_CANONNAME;
3534 hints.ai_family = AF_UNSPEC;
3535 hints.ai_socktype = SOCK_STREAM;
3537 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3538 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3539 gai_strerror(errno));
3540 goto done;
3543 for (ai = res; ai; ai = ai->ai_next) {
3544 if (s != -1) {
3545 close(s);
3546 s = -1;
3549 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3550 continue;
3551 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3552 if (s == -1)
3553 continue;
3554 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3555 sizeof(on)) == -1)
3556 continue;
3557 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3558 break;
3560 if (s == -1) {
3561 snprintf(myerror, sizeof myerror,
3562 "could not obtain certificates from: %s",
3563 su->host);
3564 goto done;
3567 if (domain)
3568 strlcpy(domain, su->host, domain_sz);
3569 rv = s;
3570 done:
3571 if (su)
3572 soup_uri_free(su);
3573 if (res)
3574 freeaddrinfo(res);
3575 if (rv == -1 && s != -1)
3576 close(s);
3578 return (rv);
3582 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3584 if (gsession)
3585 gnutls_deinit(gsession);
3586 if (xcred)
3587 gnutls_certificate_free_credentials(xcred);
3589 return (0);
3593 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3594 gnutls_certificate_credentials_t *xc)
3596 gnutls_certificate_credentials_t xcred;
3597 gnutls_session_t gsession;
3598 int rv = 1;
3599 static gchar myerror[1024]; /* this is not thread safe */
3601 if (gs == NULL || xc == NULL)
3602 goto done;
3604 myerror[0] = '\0';
3605 *gs = NULL;
3606 *xc = NULL;
3608 gnutls_certificate_allocate_credentials(&xcred);
3609 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3610 GNUTLS_X509_FMT_PEM);
3612 gnutls_init(&gsession, GNUTLS_CLIENT);
3613 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3614 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3615 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3616 if ((rv = gnutls_handshake(gsession)) < 0) {
3617 snprintf(myerror, sizeof myerror,
3618 "gnutls_handshake failed %d fatal %d %s",
3620 gnutls_error_is_fatal(rv),
3621 gnutls_strerror_name(rv));
3622 stop_tls(gsession, xcred);
3623 goto done;
3626 gnutls_credentials_type_t cred;
3627 cred = gnutls_auth_get_type(gsession);
3628 if (cred != GNUTLS_CRD_CERTIFICATE) {
3629 snprintf(myerror, sizeof myerror,
3630 "gnutls_auth_get_type failed %d",
3631 (int)cred);
3632 stop_tls(gsession, xcred);
3633 goto done;
3636 *gs = gsession;
3637 *xc = xcred;
3638 rv = 0;
3639 done:
3640 *error_str = myerror;
3641 return (rv);
3645 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3646 size_t *cert_count)
3648 unsigned int len;
3649 const gnutls_datum_t *cl;
3650 gnutls_x509_crt_t *all_certs;
3651 int i, rv = 1;
3653 if (certs == NULL || cert_count == NULL)
3654 goto done;
3655 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3656 goto done;
3657 cl = gnutls_certificate_get_peers(gsession, &len);
3658 if (len == 0)
3659 goto done;
3661 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3662 for (i = 0; i < len; i++) {
3663 gnutls_x509_crt_init(&all_certs[i]);
3664 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3665 GNUTLS_X509_FMT_PEM < 0)) {
3666 g_free(all_certs);
3667 goto done;
3671 *certs = all_certs;
3672 *cert_count = len;
3673 rv = 0;
3674 done:
3675 return (rv);
3678 void
3679 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3681 int i;
3683 for (i = 0; i < cert_count; i++)
3684 gnutls_x509_crt_deinit(certs[i]);
3685 g_free(certs);
3688 void
3689 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3691 GdkColor c_text, c_base;
3693 gdk_color_parse(text, &c_text);
3694 gdk_color_parse(base, &c_base);
3696 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3697 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3698 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3699 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3701 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3702 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3703 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3704 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3707 void
3708 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3709 size_t cert_count, char *domain)
3711 size_t cert_buf_sz;
3712 char cert_buf[64 * 1024], file[PATH_MAX];
3713 int i;
3714 FILE *f;
3715 GdkColor color;
3717 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3718 return;
3720 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3721 if ((f = fopen(file, "w")) == NULL) {
3722 show_oops(t, "Can't create cert file %s %s",
3723 file, strerror(errno));
3724 return;
3727 for (i = 0; i < cert_count; i++) {
3728 cert_buf_sz = sizeof cert_buf;
3729 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3730 cert_buf, &cert_buf_sz)) {
3731 show_oops(t, "gnutls_x509_crt_export failed");
3732 goto done;
3734 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3735 show_oops(t, "Can't write certs: %s", strerror(errno));
3736 goto done;
3740 /* not the best spot but oh well */
3741 gdk_color_parse("lightblue", &color);
3742 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3743 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3744 done:
3745 fclose(f);
3748 enum cert_trust {
3749 CERT_LOCAL,
3750 CERT_TRUSTED,
3751 CERT_UNTRUSTED,
3752 CERT_BAD
3755 enum cert_trust
3756 load_compare_cert(const gchar *uri, const gchar **error_str)
3758 char domain[8182], file[PATH_MAX];
3759 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3760 int s = -1, i;
3761 unsigned int error = 0;
3762 FILE *f = NULL;
3763 size_t cert_buf_sz, cert_count;
3764 enum cert_trust rv = CERT_UNTRUSTED;
3765 static gchar serr[80]; /* this isn't thread safe */
3766 gnutls_session_t gsession;
3767 gnutls_x509_crt_t *certs;
3768 gnutls_certificate_credentials_t xcred;
3770 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3772 serr[0] = '\0';
3773 *error_str = serr;
3774 if ((s = connect_socket_from_uri(uri, error_str, domain,
3775 sizeof domain)) == -1)
3776 return (rv);
3778 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3780 /* go ssl/tls */
3781 if (start_tls(error_str, s, &gsession, &xcred))
3782 goto done;
3783 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3785 /* verify certs in case cert file doesn't exist */
3786 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3787 GNUTLS_E_SUCCESS) {
3788 *error_str = "Invalid certificates";
3789 goto done;
3792 /* get certs */
3793 if (get_connection_certs(gsession, &certs, &cert_count)) {
3794 *error_str = "Can't get connection certificates";
3795 goto done;
3798 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3799 if ((f = fopen(file, "r")) == NULL) {
3800 if (!error)
3801 rv = CERT_TRUSTED;
3802 goto freeit;
3805 for (i = 0; i < cert_count; i++) {
3806 cert_buf_sz = sizeof cert_buf;
3807 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3808 cert_buf, &cert_buf_sz)) {
3809 goto freeit;
3811 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3812 rv = CERT_BAD; /* critical */
3813 goto freeit;
3815 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3816 rv = CERT_BAD; /* critical */
3817 goto freeit;
3819 rv = CERT_LOCAL;
3822 freeit:
3823 if (f)
3824 fclose(f);
3825 free_connection_certs(certs, cert_count);
3826 done:
3827 /* we close the socket first for speed */
3828 if (s != -1)
3829 close(s);
3831 /* only complain if we didn't save it locally */
3832 if (error && rv != CERT_LOCAL) {
3833 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3834 if (error & GNUTLS_CERT_INVALID)
3835 strlcat(serr, "invalid, ", sizeof serr);
3836 if (error & GNUTLS_CERT_REVOKED)
3837 strlcat(serr, "revoked, ", sizeof serr);
3838 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3839 strlcat(serr, "signer not found, ", sizeof serr);
3840 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3841 strlcat(serr, "not signed by CA, ", sizeof serr);
3842 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3843 strlcat(serr, "insecure algorithm, ", sizeof serr);
3844 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3845 strlcat(serr, "not activated, ", sizeof serr);
3846 if (error & GNUTLS_CERT_EXPIRED)
3847 strlcat(serr, "expired, ", sizeof serr);
3848 for (i = strlen(serr) - 1; i > 0; i--)
3849 if (serr[i] == ',') {
3850 serr[i] = '\0';
3851 break;
3853 *error_str = serr;
3856 stop_tls(gsession, xcred);
3858 return (rv);
3862 cert_cmd(struct tab *t, struct karg *args)
3864 const gchar *uri, *error_str = NULL;
3865 char domain[8182];
3866 int s = -1;
3867 size_t cert_count;
3868 gnutls_session_t gsession;
3869 gnutls_x509_crt_t *certs;
3870 gnutls_certificate_credentials_t xcred;
3872 if (t == NULL)
3873 return (1);
3875 if (ssl_ca_file == NULL) {
3876 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3877 return (1);
3880 if ((uri = get_uri(t)) == NULL) {
3881 show_oops(t, "Invalid URI");
3882 return (1);
3885 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3886 sizeof domain)) == -1) {
3887 show_oops(t, "%s", error_str);
3888 return (1);
3891 /* go ssl/tls */
3892 if (start_tls(&error_str, s, &gsession, &xcred))
3893 goto done;
3895 /* get certs */
3896 if (get_connection_certs(gsession, &certs, &cert_count)) {
3897 show_oops(t, "get_connection_certs failed");
3898 goto done;
3901 if (args->i & XT_SHOW)
3902 show_certs(t, certs, cert_count, "Certificate Chain");
3903 else if (args->i & XT_SAVE)
3904 save_certs(t, certs, cert_count, domain);
3906 free_connection_certs(certs, cert_count);
3907 done:
3908 /* we close the socket first for speed */
3909 if (s != -1)
3910 close(s);
3911 stop_tls(gsession, xcred);
3912 if (error_str && strlen(error_str))
3913 show_oops(t, "%s", error_str);
3914 return (0);
3918 remove_cookie(int index)
3920 int i, rv = 1;
3921 GSList *cf;
3922 SoupCookie *c;
3924 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3926 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3928 for (i = 1; cf; cf = cf->next, i++) {
3929 if (i != index)
3930 continue;
3931 c = cf->data;
3932 print_cookie("remove cookie", c);
3933 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3934 rv = 0;
3935 break;
3938 soup_cookies_free(cf);
3940 return (rv);
3944 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3946 struct domain *d;
3947 char *tmp, *body;
3949 body = g_strdup("");
3951 /* p list */
3952 if (args->i & XT_WL_PERSISTENT) {
3953 tmp = body;
3954 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3955 g_free(tmp);
3956 RB_FOREACH(d, domain_list, wl) {
3957 if (d->handy == 0)
3958 continue;
3959 tmp = body;
3960 body = g_strdup_printf("%s%s<br/>", body, d->d);
3961 g_free(tmp);
3965 /* s list */
3966 if (args->i & XT_WL_SESSION) {
3967 tmp = body;
3968 body = g_strdup_printf("%s<h2>Session</h2>", body);
3969 g_free(tmp);
3970 RB_FOREACH(d, domain_list, wl) {
3971 if (d->handy == 1)
3972 continue;
3973 tmp = body;
3974 body = g_strdup_printf("%s%s<br/>", body, d->d);
3975 g_free(tmp);
3979 tmp = get_html_page(title, body, "", 0);
3980 g_free(body);
3981 if (wl == &js_wl)
3982 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3983 else if (wl == &c_wl)
3984 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3985 else
3986 load_webkit_string(t, tmp, XT_URI_ABOUT_PLUGINWL);
3987 g_free(tmp);
3988 return (0);
3991 #define XT_WL_INVALID (0)
3992 #define XT_WL_JAVASCRIPT (1)
3993 #define XT_WL_COOKIE (2)
3994 #define XT_WL_PLUGIN (3)
3996 wl_save(struct tab *t, struct karg *args, int list)
3998 char file[PATH_MAX], *lst_str = NULL;
3999 FILE *f;
4000 char *line = NULL, *lt = NULL, *dom = NULL;
4001 size_t linelen;
4002 const gchar *uri;
4003 struct karg a;
4004 struct domain *d;
4005 GSList *cf;
4006 SoupCookie *ci, *c;
4008 if (t == NULL || args == NULL)
4009 return (1);
4011 if (runtime_settings[0] == '\0')
4012 return (1);
4014 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
4015 if ((f = fopen(file, "r+")) == NULL)
4016 return (1);
4018 switch (list) {
4019 case XT_WL_JAVASCRIPT:
4020 lst_str = "JavaScript";
4021 lt = g_strdup_printf("js_wl=%s", dom);
4022 break;
4023 case XT_WL_COOKIE:
4024 lst_str = "Cookie";
4025 lt = g_strdup_printf("cookie_wl=%s", dom);
4026 break;
4027 case XT_WL_PLUGIN:
4028 lst_str = "Plugin";
4029 lt = g_strdup_printf("pl_wl=%s", dom);
4030 break;
4031 default:
4032 show_oops(t, "Invalid list id: %d", list);
4033 return (1);
4036 uri = get_uri(t);
4037 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
4038 if (uri == NULL || dom == NULL ||
4039 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
4040 show_oops(t, "Can't add domain to %s white list", lst_str);
4041 goto done;
4044 while (!feof(f)) {
4045 line = fparseln(f, &linelen, NULL, NULL, 0);
4046 if (line == NULL)
4047 continue;
4048 if (!strcmp(line, lt))
4049 goto done;
4050 free(line);
4051 line = NULL;
4054 fprintf(f, "%s\n", lt);
4056 a.i = XT_WL_ENABLE;
4057 a.i |= args->i;
4058 switch (list) {
4059 case XT_WL_JAVASCRIPT:
4060 d = wl_find(dom, &js_wl);
4061 if (!d) {
4062 settings_add("js_wl", dom);
4063 d = wl_find(dom, &js_wl);
4065 toggle_js(t, &a);
4066 break;
4068 case XT_WL_COOKIE:
4069 d = wl_find(dom, &c_wl);
4070 if (!d) {
4071 settings_add("cookie_wl", dom);
4072 d = wl_find(dom, &c_wl);
4074 toggle_cwl(t, &a);
4076 /* find and add to persistent jar */
4077 cf = soup_cookie_jar_all_cookies(s_cookiejar);
4078 for (;cf; cf = cf->next) {
4079 ci = cf->data;
4080 if (!strcmp(dom, ci->domain) ||
4081 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
4082 c = soup_cookie_copy(ci);
4083 _soup_cookie_jar_add_cookie(p_cookiejar, c);
4086 soup_cookies_free(cf);
4087 break;
4089 case XT_WL_PLUGIN:
4090 d = wl_find(dom, &pl_wl);
4091 if (!d) {
4092 settings_add("pl_wl", dom);
4093 d = wl_find(dom, &pl_wl);
4095 toggle_pl(t, &a);
4096 break;
4097 default:
4098 abort(); /* can't happen */
4100 if (d)
4101 d->handy = 1;
4103 done:
4104 if (line)
4105 free(line);
4106 if (dom)
4107 g_free(dom);
4108 if (lt)
4109 g_free(lt);
4110 fclose(f);
4112 return (0);
4116 js_show_wl(struct tab *t, struct karg *args)
4118 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
4119 wl_show(t, args, "JavaScript White List", &js_wl);
4121 return (0);
4125 cookie_show_wl(struct tab *t, struct karg *args)
4127 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
4128 wl_show(t, args, "Cookie White List", &c_wl);
4130 return (0);
4134 cookie_cmd(struct tab *t, struct karg *args)
4136 if (args->i & XT_SHOW)
4137 wl_show(t, args, "Cookie White List", &c_wl);
4138 else if (args->i & XT_WL_TOGGLE) {
4139 args->i |= XT_WL_RELOAD;
4140 toggle_cwl(t, args);
4141 } else if (args->i & XT_SAVE) {
4142 args->i |= XT_WL_RELOAD;
4143 wl_save(t, args, XT_WL_COOKIE);
4144 } else if (args->i & XT_DELETE)
4145 show_oops(t, "'cookie delete' currently unimplemented");
4147 return (0);
4151 js_cmd(struct tab *t, struct karg *args)
4153 if (args->i & XT_SHOW)
4154 wl_show(t, args, "JavaScript White List", &js_wl);
4155 else if (args->i & XT_SAVE) {
4156 args->i |= XT_WL_RELOAD;
4157 wl_save(t, args, XT_WL_JAVASCRIPT);
4158 } else if (args->i & XT_WL_TOGGLE) {
4159 args->i |= XT_WL_RELOAD;
4160 toggle_js(t, args);
4161 } else if (args->i & XT_DELETE)
4162 show_oops(t, "'js delete' currently unimplemented");
4164 return (0);
4168 pl_show_wl(struct tab *t, struct karg *args)
4170 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
4171 wl_show(t, args, "Plugin White List", &pl_wl);
4173 return (0);
4177 pl_cmd(struct tab *t, struct karg *args)
4179 if (args->i & XT_SHOW)
4180 wl_show(t, args, "Plugin White List", &pl_wl);
4181 else if (args->i & XT_SAVE) {
4182 args->i |= XT_WL_RELOAD;
4183 wl_save(t, args, XT_WL_PLUGIN);
4184 } else if (args->i & XT_WL_TOGGLE) {
4185 args->i |= XT_WL_RELOAD;
4186 toggle_pl(t, args);
4187 } else if (args->i & XT_DELETE)
4188 show_oops(t, "'plugin delete' currently unimplemented");
4190 return (0);
4194 toplevel_cmd(struct tab *t, struct karg *args)
4196 js_toggle_cb(t->js_toggle, t);
4198 return (0);
4202 add_favorite(struct tab *t, struct karg *args)
4204 char file[PATH_MAX];
4205 FILE *f;
4206 char *line = NULL;
4207 size_t urilen, linelen;
4208 const gchar *uri, *title;
4210 if (t == NULL)
4211 return (1);
4213 /* don't allow adding of xtp pages to favorites */
4214 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4215 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4216 return (1);
4219 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4220 if ((f = fopen(file, "r+")) == NULL) {
4221 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4222 return (1);
4225 title = get_title(t, FALSE);
4226 uri = get_uri(t);
4228 if (title == NULL || uri == NULL) {
4229 show_oops(t, "can't add page to favorites");
4230 goto done;
4233 urilen = strlen(uri);
4235 for (;;) {
4236 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4237 if (feof(f) || ferror(f))
4238 break;
4240 if (linelen == urilen && !strcmp(line, uri))
4241 goto done;
4243 free(line);
4244 line = NULL;
4247 fprintf(f, "\n%s\n%s", title, uri);
4248 done:
4249 if (line)
4250 free(line);
4251 fclose(f);
4253 update_favorite_tabs(NULL);
4255 return (0);
4259 can_go_back_for_real(struct tab *t)
4261 int i;
4262 WebKitWebHistoryItem *item;
4263 const gchar *uri;
4265 if (t == NULL)
4266 return (FALSE);
4268 /* rely on webkit to make sure we can go backward when on an about page */
4269 uri = get_uri(t);
4270 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4271 return (webkit_web_view_can_go_back(t->wv));
4273 /* the back/forwars list is stupid so help determine if we can go back */
4274 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4275 item != NULL;
4276 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4277 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4278 return (TRUE);
4281 return (FALSE);
4285 can_go_forward_for_real(struct tab *t)
4287 int i;
4288 WebKitWebHistoryItem *item;
4289 const gchar *uri;
4291 if (t == NULL)
4292 return (FALSE);
4294 /* rely on webkit to make sure we can go forward when on an about page */
4295 uri = get_uri(t);
4296 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4297 return (webkit_web_view_can_go_forward(t->wv));
4299 /* the back/forwars list is stupid so help selecting a different item */
4300 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4301 item != NULL;
4302 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4303 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4304 return (TRUE);
4307 return (FALSE);
4310 void
4311 go_back_for_real(struct tab *t)
4313 int i;
4314 WebKitWebHistoryItem *item;
4315 const gchar *uri;
4317 if (t == NULL)
4318 return;
4320 uri = get_uri(t);
4321 if (uri == NULL) {
4322 webkit_web_view_go_back(t->wv);
4323 return;
4325 /* the back/forwars list is stupid so help selecting a different item */
4326 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4327 item != NULL;
4328 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4329 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4330 webkit_web_view_go_to_back_forward_item(t->wv, item);
4331 break;
4336 void
4337 go_forward_for_real(struct tab *t)
4339 int i;
4340 WebKitWebHistoryItem *item;
4341 const gchar *uri;
4343 if (t == NULL)
4344 return;
4346 uri = get_uri(t);
4347 if (uri == NULL) {
4348 webkit_web_view_go_forward(t->wv);
4349 return;
4351 /* the back/forwars list is stupid so help selecting a different item */
4352 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4353 item != NULL;
4354 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4355 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4356 webkit_web_view_go_to_back_forward_item(t->wv, item);
4357 break;
4363 navaction(struct tab *t, struct karg *args)
4365 WebKitWebHistoryItem *item;
4366 WebKitWebFrame *frame;
4368 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4369 t->tab_id, args->i);
4371 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4372 if (t->item) {
4373 if (args->i == XT_NAV_BACK)
4374 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4375 else
4376 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4377 if (item == NULL)
4378 return (XT_CB_PASSTHROUGH);
4379 webkit_web_view_go_to_back_forward_item(t->wv, item);
4380 t->item = NULL;
4381 return (XT_CB_PASSTHROUGH);
4384 switch (args->i) {
4385 case XT_NAV_BACK:
4386 marks_clear(t);
4387 go_back_for_real(t);
4388 break;
4389 case XT_NAV_FORWARD:
4390 marks_clear(t);
4391 go_forward_for_real(t);
4392 break;
4393 case XT_NAV_RELOAD:
4394 frame = webkit_web_view_get_main_frame(t->wv);
4395 webkit_web_frame_reload(frame);
4396 break;
4398 return (XT_CB_PASSTHROUGH);
4402 move(struct tab *t, struct karg *args)
4404 GtkAdjustment *adjust;
4405 double pi, si, pos, ps, upper, lower, max;
4406 double percent;
4408 switch (args->i) {
4409 case XT_MOVE_DOWN:
4410 case XT_MOVE_UP:
4411 case XT_MOVE_BOTTOM:
4412 case XT_MOVE_TOP:
4413 case XT_MOVE_PAGEDOWN:
4414 case XT_MOVE_PAGEUP:
4415 case XT_MOVE_HALFDOWN:
4416 case XT_MOVE_HALFUP:
4417 case XT_MOVE_PERCENT:
4418 adjust = t->adjust_v;
4419 break;
4420 default:
4421 adjust = t->adjust_h;
4422 break;
4425 pos = gtk_adjustment_get_value(adjust);
4426 ps = gtk_adjustment_get_page_size(adjust);
4427 upper = gtk_adjustment_get_upper(adjust);
4428 lower = gtk_adjustment_get_lower(adjust);
4429 si = gtk_adjustment_get_step_increment(adjust);
4430 pi = gtk_adjustment_get_page_increment(adjust);
4431 max = upper - ps;
4433 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4434 "max %f si %f pi %f\n",
4435 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4436 pos, ps, upper, lower, max, si, pi);
4438 switch (args->i) {
4439 case XT_MOVE_DOWN:
4440 case XT_MOVE_RIGHT:
4441 pos += si;
4442 gtk_adjustment_set_value(adjust, MIN(pos, max));
4443 break;
4444 case XT_MOVE_UP:
4445 case XT_MOVE_LEFT:
4446 pos -= si;
4447 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4448 break;
4449 case XT_MOVE_BOTTOM:
4450 case XT_MOVE_FARRIGHT:
4451 gtk_adjustment_set_value(adjust, max);
4452 break;
4453 case XT_MOVE_TOP:
4454 case XT_MOVE_FARLEFT:
4455 gtk_adjustment_set_value(adjust, lower);
4456 break;
4457 case XT_MOVE_PAGEDOWN:
4458 pos += pi;
4459 gtk_adjustment_set_value(adjust, MIN(pos, max));
4460 break;
4461 case XT_MOVE_PAGEUP:
4462 pos -= pi;
4463 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4464 break;
4465 case XT_MOVE_HALFDOWN:
4466 pos += pi / 2;
4467 gtk_adjustment_set_value(adjust, MIN(pos, max));
4468 break;
4469 case XT_MOVE_HALFUP:
4470 pos -= pi / 2;
4471 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4472 break;
4473 case XT_MOVE_PERCENT:
4474 percent = atoi(args->s) / 100.0;
4475 pos = max * percent;
4476 if (pos < 0.0 || pos > max)
4477 break;
4478 gtk_adjustment_set_value(adjust, pos);
4479 break;
4480 default:
4481 return (XT_CB_PASSTHROUGH);
4484 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4486 return (XT_CB_HANDLED);
4489 void
4490 url_set_visibility(void)
4492 struct tab *t;
4494 TAILQ_FOREACH(t, &tabs, entry)
4495 if (show_url == 0) {
4496 gtk_widget_hide(t->toolbar);
4497 focus_webview(t);
4498 } else
4499 gtk_widget_show(t->toolbar);
4502 void
4503 notebook_tab_set_visibility(void)
4505 if (show_tabs == 0) {
4506 gtk_widget_hide(tab_bar);
4507 gtk_notebook_set_show_tabs(notebook, FALSE);
4508 } else {
4509 if (tab_style == XT_TABS_NORMAL) {
4510 gtk_widget_hide(tab_bar);
4511 gtk_notebook_set_show_tabs(notebook, TRUE);
4512 } else if (tab_style == XT_TABS_COMPACT) {
4513 gtk_widget_show(tab_bar);
4514 gtk_notebook_set_show_tabs(notebook, FALSE);
4519 void
4520 statusbar_set_visibility(void)
4522 struct tab *t;
4524 TAILQ_FOREACH(t, &tabs, entry)
4525 if (show_statusbar == 0) {
4526 gtk_widget_hide(t->statusbar_box);
4527 focus_webview(t);
4528 } else
4529 gtk_widget_show(t->statusbar_box);
4532 void
4533 url_set(struct tab *t, int enable_url_entry)
4535 GdkPixbuf *pixbuf;
4536 int progress;
4538 show_url = enable_url_entry;
4540 if (enable_url_entry) {
4541 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4542 GTK_ENTRY_ICON_PRIMARY, NULL);
4543 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4544 } else {
4545 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4546 GTK_ENTRY_ICON_PRIMARY);
4547 progress =
4548 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4549 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4550 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4551 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4552 progress);
4557 fullscreen(struct tab *t, struct karg *args)
4559 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4561 if (t == NULL)
4562 return (XT_CB_PASSTHROUGH);
4564 if (show_url == 0) {
4565 url_set(t, 1);
4566 show_tabs = 1;
4567 } else {
4568 url_set(t, 0);
4569 show_tabs = 0;
4572 url_set_visibility();
4573 notebook_tab_set_visibility();
4575 return (XT_CB_HANDLED);
4579 statustoggle(struct tab *t, struct karg *args)
4581 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4583 if (show_statusbar == 1) {
4584 show_statusbar = 0;
4585 statusbar_set_visibility();
4586 } else if (show_statusbar == 0) {
4587 show_statusbar = 1;
4588 statusbar_set_visibility();
4590 return (XT_CB_HANDLED);
4594 urlaction(struct tab *t, struct karg *args)
4596 int rv = XT_CB_HANDLED;
4598 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4600 if (t == NULL)
4601 return (XT_CB_PASSTHROUGH);
4603 switch (args->i) {
4604 case XT_URL_SHOW:
4605 if (show_url == 0) {
4606 url_set(t, 1);
4607 url_set_visibility();
4609 break;
4610 case XT_URL_HIDE:
4611 if (show_url == 1) {
4612 url_set(t, 0);
4613 url_set_visibility();
4615 break;
4617 return (rv);
4621 tabaction(struct tab *t, struct karg *args)
4623 int rv = XT_CB_HANDLED;
4624 char *url = args->s;
4625 struct undo *u;
4626 struct tab *tt;
4628 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4630 if (t == NULL)
4631 return (XT_CB_PASSTHROUGH);
4633 switch (args->i) {
4634 case XT_TAB_NEW:
4635 if (strlen(url) > 0)
4636 create_new_tab(url, NULL, 1, args->precount);
4637 else
4638 create_new_tab(NULL, NULL, 1, args->precount);
4639 break;
4640 case XT_TAB_DELETE:
4641 if (args->precount < 0)
4642 delete_tab(t);
4643 else
4644 TAILQ_FOREACH(tt, &tabs, entry)
4645 if (tt->tab_id == args->precount - 1) {
4646 delete_tab(tt);
4647 break;
4649 break;
4650 case XT_TAB_DELQUIT:
4651 if (gtk_notebook_get_n_pages(notebook) > 1)
4652 delete_tab(t);
4653 else
4654 quit(t, args);
4655 break;
4656 case XT_TAB_OPEN:
4657 if (strlen(url) > 0)
4659 else {
4660 rv = XT_CB_PASSTHROUGH;
4661 goto done;
4663 load_uri(t, url);
4664 break;
4665 case XT_TAB_SHOW:
4666 if (show_tabs == 0) {
4667 show_tabs = 1;
4668 notebook_tab_set_visibility();
4670 break;
4671 case XT_TAB_HIDE:
4672 if (show_tabs == 1) {
4673 show_tabs = 0;
4674 notebook_tab_set_visibility();
4676 break;
4677 case XT_TAB_NEXTSTYLE:
4678 if (tab_style == XT_TABS_NORMAL) {
4679 tab_style = XT_TABS_COMPACT;
4680 recolor_compact_tabs();
4682 else
4683 tab_style = XT_TABS_NORMAL;
4684 notebook_tab_set_visibility();
4685 break;
4686 case XT_TAB_UNDO_CLOSE:
4687 if (undo_count == 0) {
4688 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4689 __func__);
4690 goto done;
4691 } else {
4692 undo_count--;
4693 u = TAILQ_FIRST(&undos);
4694 create_new_tab(u->uri, u, 1, -1);
4696 TAILQ_REMOVE(&undos, u, entry);
4697 g_free(u->uri);
4698 /* u->history is freed in create_new_tab() */
4699 g_free(u);
4701 break;
4702 default:
4703 rv = XT_CB_PASSTHROUGH;
4704 goto done;
4707 done:
4708 if (args->s) {
4709 g_free(args->s);
4710 args->s = NULL;
4713 return (rv);
4717 resizetab(struct tab *t, struct karg *args)
4719 if (t == NULL || args == NULL) {
4720 show_oops(NULL, "resizetab invalid parameters");
4721 return (XT_CB_PASSTHROUGH);
4724 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4725 t->tab_id, args->i);
4727 setzoom_webkit(t, args->i);
4729 return (XT_CB_HANDLED);
4733 movetab(struct tab *t, struct karg *args)
4735 int n, dest;
4737 if (t == NULL || args == NULL) {
4738 show_oops(NULL, "movetab invalid parameters");
4739 return (XT_CB_PASSTHROUGH);
4742 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4743 t->tab_id, args->i);
4745 if (args->i >= XT_TAB_INVALID)
4746 return (XT_CB_PASSTHROUGH);
4748 if (TAILQ_EMPTY(&tabs))
4749 return (XT_CB_PASSTHROUGH);
4751 n = gtk_notebook_get_n_pages(notebook);
4752 dest = gtk_notebook_get_current_page(notebook);
4754 switch (args->i) {
4755 case XT_TAB_NEXT:
4756 if (args->precount < 0)
4757 dest = dest == n - 1 ? 0 : dest + 1;
4758 else
4759 dest = args->precount - 1;
4761 break;
4762 case XT_TAB_PREV:
4763 if (args->precount < 0)
4764 dest -= 1;
4765 else
4766 dest -= args->precount % n;
4768 if (dest < 0)
4769 dest += n;
4771 break;
4772 case XT_TAB_FIRST:
4773 dest = 0;
4774 break;
4775 case XT_TAB_LAST:
4776 dest = n - 1;
4777 break;
4778 default:
4779 return (XT_CB_PASSTHROUGH);
4782 if (dest < 0 || dest >= n)
4783 return (XT_CB_PASSTHROUGH);
4784 if (t->tab_id == dest) {
4785 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4786 return (XT_CB_HANDLED);
4789 set_current_tab(dest);
4791 return (XT_CB_HANDLED);
4794 int cmd_prefix = 0;
4797 command(struct tab *t, struct karg *args)
4799 char *s = NULL, *ss = NULL;
4800 GdkColor color;
4801 const gchar *uri;
4803 if (t == NULL || args == NULL) {
4804 show_oops(NULL, "command invalid parameters");
4805 return (XT_CB_PASSTHROUGH);
4808 switch (args->i) {
4809 case '/':
4810 s = "/";
4811 break;
4812 case '?':
4813 s = "?";
4814 break;
4815 case ':':
4816 if (cmd_prefix == 0)
4817 s = ":";
4818 else {
4819 ss = g_strdup_printf(":%d", cmd_prefix);
4820 s = ss;
4821 cmd_prefix = 0;
4823 break;
4824 case XT_CMD_OPEN:
4825 s = ":open ";
4826 break;
4827 case XT_CMD_TABNEW:
4828 s = ":tabnew ";
4829 break;
4830 case XT_CMD_OPEN_CURRENT:
4831 s = ":open ";
4832 /* FALL THROUGH */
4833 case XT_CMD_TABNEW_CURRENT:
4834 if (!s) /* FALL THROUGH? */
4835 s = ":tabnew ";
4836 if ((uri = get_uri(t)) != NULL) {
4837 ss = g_strdup_printf("%s%s", s, uri);
4838 s = ss;
4840 break;
4841 default:
4842 show_oops(t, "command: invalid opcode %d", args->i);
4843 return (XT_CB_PASSTHROUGH);
4846 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4848 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4849 gdk_color_parse(XT_COLOR_WHITE, &color);
4850 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4851 show_cmd(t);
4852 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4853 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4855 if (ss)
4856 g_free(ss);
4858 return (XT_CB_HANDLED);
4862 * Return a new string with a download row (in html)
4863 * appended. Old string is freed.
4865 char *
4866 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4869 WebKitDownloadStatus stat;
4870 char *status_html = NULL, *cmd_html = NULL, *new_html;
4871 gdouble progress;
4872 char cur_sz[FMT_SCALED_STRSIZE];
4873 char tot_sz[FMT_SCALED_STRSIZE];
4874 char *xtp_prefix;
4876 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4878 /* All actions wil take this form:
4879 * xxxt://class/seskey
4881 xtp_prefix = g_strdup_printf("%s%d/%s/",
4882 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4884 stat = webkit_download_get_status(dl->download);
4886 switch (stat) {
4887 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4888 status_html = g_strdup_printf("Finished");
4889 cmd_html = g_strdup_printf(
4890 "<a href='%s%d/%d'>Remove</a>",
4891 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4892 break;
4893 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4894 /* gather size info */
4895 progress = 100 * webkit_download_get_progress(dl->download);
4897 fmt_scaled(
4898 webkit_download_get_current_size(dl->download), cur_sz);
4899 fmt_scaled(
4900 webkit_download_get_total_size(dl->download), tot_sz);
4902 status_html = g_strdup_printf(
4903 "<div style='width: 100%%' align='center'>"
4904 "<div class='progress-outer'>"
4905 "<div class='progress-inner' style='width: %.2f%%'>"
4906 "</div></div></div>"
4907 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4908 progress, cur_sz, tot_sz, progress);
4910 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4911 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4913 break;
4914 /* LLL */
4915 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4916 status_html = g_strdup_printf("Cancelled");
4917 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4918 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4919 break;
4920 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4921 status_html = g_strdup_printf("Error!");
4922 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4923 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4924 break;
4925 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4926 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4927 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4928 status_html = g_strdup_printf("Starting");
4929 break;
4930 default:
4931 show_oops(t, "%s: unknown download status", __func__);
4934 new_html = g_strdup_printf(
4935 "%s\n<tr><td>%s</td><td>%s</td>"
4936 "<td style='text-align:center'>%s</td></tr>\n",
4937 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4938 status_html, cmd_html);
4939 g_free(html);
4941 if (status_html)
4942 g_free(status_html);
4944 if (cmd_html)
4945 g_free(cmd_html);
4947 g_free(xtp_prefix);
4949 return new_html;
4953 * update all download tabs apart from one. Pass NULL if
4954 * you want to update all.
4956 void
4957 update_download_tabs(struct tab *apart_from)
4959 struct tab *t;
4960 if (!updating_dl_tabs) {
4961 updating_dl_tabs = 1; /* stop infinite recursion */
4962 TAILQ_FOREACH(t, &tabs, entry)
4963 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4964 && (t != apart_from))
4965 xtp_page_dl(t, NULL);
4966 updating_dl_tabs = 0;
4971 * update all cookie tabs apart from one. Pass NULL if
4972 * you want to update all.
4974 void
4975 update_cookie_tabs(struct tab *apart_from)
4977 struct tab *t;
4978 if (!updating_cl_tabs) {
4979 updating_cl_tabs = 1; /* stop infinite recursion */
4980 TAILQ_FOREACH(t, &tabs, entry)
4981 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4982 && (t != apart_from))
4983 xtp_page_cl(t, NULL);
4984 updating_cl_tabs = 0;
4989 * update all history tabs apart from one. Pass NULL if
4990 * you want to update all.
4992 void
4993 update_history_tabs(struct tab *apart_from)
4995 struct tab *t;
4997 if (!updating_hl_tabs) {
4998 updating_hl_tabs = 1; /* stop infinite recursion */
4999 TAILQ_FOREACH(t, &tabs, entry)
5000 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
5001 && (t != apart_from))
5002 xtp_page_hl(t, NULL);
5003 updating_hl_tabs = 0;
5007 /* cookie management XTP page */
5009 xtp_page_cl(struct tab *t, struct karg *args)
5011 char *body, *page, *tmp;
5012 int i = 1; /* all ids start 1 */
5013 GSList *sc, *pc, *pc_start;
5014 SoupCookie *c;
5015 char *type, *table_headers, *last_domain;
5017 DNPRINTF(XT_D_CMD, "%s", __func__);
5019 if (t == NULL) {
5020 show_oops(NULL, "%s invalid parameters", __func__);
5021 return (1);
5024 /* Generate a new session key */
5025 if (!updating_cl_tabs)
5026 generate_xtp_session_key(&cl_session_key);
5028 /* table headers */
5029 table_headers = g_strdup_printf("<table><tr>"
5030 "<th>Type</th>"
5031 "<th>Name</th>"
5032 "<th style='width:200px'>Value</th>"
5033 "<th>Path</th>"
5034 "<th>Expires</th>"
5035 "<th>Secure</th>"
5036 "<th>HTTP<br />only</th>"
5037 "<th style='width:40px'>Rm</th></tr>\n");
5039 sc = soup_cookie_jar_all_cookies(s_cookiejar);
5040 pc = soup_cookie_jar_all_cookies(p_cookiejar);
5041 pc_start = pc;
5043 body = NULL;
5044 last_domain = strdup("");
5045 for (; sc; sc = sc->next) {
5046 c = sc->data;
5048 if (strcmp(last_domain, c->domain) != 0) {
5049 /* new domain */
5050 free(last_domain);
5051 last_domain = strdup(c->domain);
5053 if (body != NULL) {
5054 tmp = body;
5055 body = g_strdup_printf("%s</table>"
5056 "<h2>%s</h2>%s\n",
5057 body, c->domain, table_headers);
5058 g_free(tmp);
5059 } else {
5060 /* first domain */
5061 body = g_strdup_printf("<h2>%s</h2>%s\n",
5062 c->domain, table_headers);
5066 type = "Session";
5067 for (pc = pc_start; pc; pc = pc->next)
5068 if (soup_cookie_equal(pc->data, c)) {
5069 type = "Session + Persistent";
5070 break;
5073 tmp = body;
5074 body = g_strdup_printf(
5075 "%s\n<tr>"
5076 "<td>%s</td>"
5077 "<td style='word-wrap:normal'>%s</td>"
5078 "<td>"
5079 " <textarea rows='4'>%s</textarea>"
5080 "</td>"
5081 "<td>%s</td>"
5082 "<td>%s</td>"
5083 "<td>%d</td>"
5084 "<td>%d</td>"
5085 "<td style='text-align:center'>"
5086 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
5087 body,
5088 type,
5089 c->name,
5090 c->value,
5091 c->path,
5092 c->expires ?
5093 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
5094 c->secure,
5095 c->http_only,
5097 XT_XTP_STR,
5098 XT_XTP_CL,
5099 cl_session_key,
5100 XT_XTP_CL_REMOVE,
5104 g_free(tmp);
5105 i++;
5108 soup_cookies_free(sc);
5109 soup_cookies_free(pc);
5111 /* small message if there are none */
5112 if (i == 1) {
5113 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5114 "colspan='8'>No Cookies</td></tr>\n", table_headers);
5116 tmp = body;
5117 body = g_strdup_printf("%s</table>", body);
5118 g_free(tmp);
5120 page = get_html_page("Cookie Jar", body, "", TRUE);
5121 g_free(body);
5122 g_free(table_headers);
5123 g_free(last_domain);
5125 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
5126 update_cookie_tabs(t);
5128 g_free(page);
5130 return (0);
5134 xtp_page_hl(struct tab *t, struct karg *args)
5136 char *body, *page, *tmp;
5137 struct history *h;
5138 int i = 1; /* all ids start 1 */
5140 DNPRINTF(XT_D_CMD, "%s", __func__);
5142 if (t == NULL) {
5143 show_oops(NULL, "%s invalid parameters", __func__);
5144 return (1);
5147 /* Generate a new session key */
5148 if (!updating_hl_tabs)
5149 generate_xtp_session_key(&hl_session_key);
5151 /* body */
5152 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
5153 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
5155 RB_FOREACH_REVERSE(h, history_list, &hl) {
5156 tmp = body;
5157 body = g_strdup_printf(
5158 "%s\n<tr>"
5159 "<td><a href='%s'>%s</a></td>"
5160 "<td>%s</td>"
5161 "<td style='text-align: center'>"
5162 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
5163 body, h->uri, h->uri, h->title,
5164 XT_XTP_STR, XT_XTP_HL, hl_session_key,
5165 XT_XTP_HL_REMOVE, i);
5167 g_free(tmp);
5168 i++;
5171 /* small message if there are none */
5172 if (i == 1) {
5173 tmp = body;
5174 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5175 "colspan='3'>No History</td></tr>\n", body);
5176 g_free(tmp);
5179 tmp = body;
5180 body = g_strdup_printf("%s</table>", body);
5181 g_free(tmp);
5183 page = get_html_page("History", body, "", TRUE);
5184 g_free(body);
5187 * update all history manager tabs as the xtp session
5188 * key has now changed. No need to update the current tab.
5189 * Already did that above.
5191 update_history_tabs(t);
5193 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5194 g_free(page);
5196 return (0);
5200 * Generate a web page detailing the status of any downloads
5203 xtp_page_dl(struct tab *t, struct karg *args)
5205 struct download *dl;
5206 char *body, *page, *tmp;
5207 char *ref;
5208 int n_dl = 1;
5210 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5212 if (t == NULL) {
5213 show_oops(NULL, "%s invalid parameters", __func__);
5214 return (1);
5218 * Generate a new session key for next page instance.
5219 * This only happens for the top level call to xtp_page_dl()
5220 * in which case updating_dl_tabs is 0.
5222 if (!updating_dl_tabs)
5223 generate_xtp_session_key(&dl_session_key);
5225 /* header - with refresh so as to update */
5226 if (refresh_interval >= 1)
5227 ref = g_strdup_printf(
5228 "<meta http-equiv='refresh' content='%u"
5229 ";url=%s%d/%s/%d' />\n",
5230 refresh_interval,
5231 XT_XTP_STR,
5232 XT_XTP_DL,
5233 dl_session_key,
5234 XT_XTP_DL_LIST);
5235 else
5236 ref = g_strdup("");
5238 body = g_strdup_printf("<div align='center'>"
5239 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5240 "</p><table><tr><th style='width: 60%%'>"
5241 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5242 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5244 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5245 body = xtp_page_dl_row(t, body, dl);
5246 n_dl++;
5249 /* message if no downloads in list */
5250 if (n_dl == 1) {
5251 tmp = body;
5252 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5253 " style='text-align: center'>"
5254 "No downloads</td></tr>\n", body);
5255 g_free(tmp);
5258 tmp = body;
5259 body = g_strdup_printf("%s</table></div>", body);
5260 g_free(tmp);
5262 page = get_html_page("Downloads", body, ref, 1);
5263 g_free(ref);
5264 g_free(body);
5267 * update all download manager tabs as the xtp session
5268 * key has now changed. No need to update the current tab.
5269 * Already did that above.
5271 update_download_tabs(t);
5273 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5274 g_free(page);
5276 return (0);
5280 search(struct tab *t, struct karg *args)
5282 gboolean d;
5284 if (t == NULL || args == NULL) {
5285 show_oops(NULL, "search invalid parameters");
5286 return (1);
5289 switch (args->i) {
5290 case XT_SEARCH_NEXT:
5291 d = t->search_forward;
5292 break;
5293 case XT_SEARCH_PREV:
5294 d = !t->search_forward;
5295 break;
5296 default:
5297 return (XT_CB_PASSTHROUGH);
5300 if (t->search_text == NULL) {
5301 if (global_search == NULL)
5302 return (XT_CB_PASSTHROUGH);
5303 else {
5304 d = t->search_forward = TRUE;
5305 t->search_text = g_strdup(global_search);
5306 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5307 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5311 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5312 t->tab_id, args->i, t->search_forward, t->search_text);
5314 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5316 return (XT_CB_HANDLED);
5319 struct settings_args {
5320 char **body;
5321 int i;
5324 void
5325 print_setting(struct settings *s, char *val, void *cb_args)
5327 char *tmp, *color;
5328 struct settings_args *sa = cb_args;
5330 if (sa == NULL)
5331 return;
5333 if (s->flags & XT_SF_RUNTIME)
5334 color = "#22cc22";
5335 else
5336 color = "#cccccc";
5338 tmp = *sa->body;
5339 *sa->body = g_strdup_printf(
5340 "%s\n<tr>"
5341 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5342 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5343 *sa->body,
5344 color,
5345 s->name,
5346 color,
5349 g_free(tmp);
5350 sa->i++;
5354 set_show(struct tab *t, struct karg *args)
5356 char *body, *page, *tmp;
5357 int i = 1;
5358 struct settings_args sa;
5360 bzero(&sa, sizeof sa);
5361 sa.body = &body;
5363 /* body */
5364 body = g_strdup_printf("<div align='center'><table><tr>"
5365 "<th align='left'>Setting</th>"
5366 "<th align='left'>Value</th></tr>\n");
5368 settings_walk(print_setting, &sa);
5369 i = sa.i;
5371 /* small message if there are none */
5372 if (i == 1) {
5373 tmp = body;
5374 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5375 "colspan='2'>No settings</td></tr>\n", body);
5376 g_free(tmp);
5379 tmp = body;
5380 body = g_strdup_printf("%s</table></div>", body);
5381 g_free(tmp);
5383 page = get_html_page("Settings", body, "", 0);
5385 g_free(body);
5387 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5389 g_free(page);
5391 return (XT_CB_PASSTHROUGH);
5395 set(struct tab *t, struct karg *args)
5397 char *p, *val;
5398 int i;
5400 if (args == NULL || args->s == NULL)
5401 return (set_show(t, args));
5403 /* strip spaces */
5404 p = g_strstrip(args->s);
5406 if (strlen(p) == 0)
5407 return (set_show(t, args));
5409 /* we got some sort of string */
5410 val = g_strrstr(p, "=");
5411 if (val) {
5412 *val++ = '\0';
5413 val = g_strchomp(val);
5414 p = g_strchomp(p);
5416 for (i = 0; i < LENGTH(rs); i++) {
5417 if (strcmp(rs[i].name, p))
5418 continue;
5420 if (rs[i].activate) {
5421 if (rs[i].activate(val))
5422 show_oops(t, "%s invalid value %s",
5423 p, val);
5424 else
5425 show_oops(t, ":set %s = %s", p, val);
5426 goto done;
5427 } else {
5428 show_oops(t, "not a runtime option: %s", p);
5429 goto done;
5432 show_oops(t, "unknown option: %s", p);
5433 } else {
5434 p = g_strchomp(p);
5436 for (i = 0; i < LENGTH(rs); i++) {
5437 if (strcmp(rs[i].name, p))
5438 continue;
5440 /* XXX this could use some cleanup */
5441 switch (rs[i].type) {
5442 case XT_S_INT:
5443 if (rs[i].ival)
5444 show_oops(t, "%s = %d",
5445 rs[i].name, *rs[i].ival);
5446 else if (rs[i].s && rs[i].s->get)
5447 show_oops(t, "%s = %s",
5448 rs[i].name,
5449 rs[i].s->get(&rs[i]));
5450 else if (rs[i].s && rs[i].s->get == NULL)
5451 show_oops(t, "%s = ...", rs[i].name);
5452 else
5453 show_oops(t, "%s = ", rs[i].name);
5454 break;
5455 case XT_S_FLOAT:
5456 if (rs[i].fval)
5457 show_oops(t, "%s = %f",
5458 rs[i].name, *rs[i].fval);
5459 else if (rs[i].s && rs[i].s->get)
5460 show_oops(t, "%s = %s",
5461 rs[i].name,
5462 rs[i].s->get(&rs[i]));
5463 else if (rs[i].s && rs[i].s->get == NULL)
5464 show_oops(t, "%s = ...", rs[i].name);
5465 else
5466 show_oops(t, "%s = ", rs[i].name);
5467 break;
5468 case XT_S_STR:
5469 if (rs[i].sval && *rs[i].sval)
5470 show_oops(t, "%s = %s",
5471 rs[i].name, *rs[i].sval);
5472 else if (rs[i].s && rs[i].s->get)
5473 show_oops(t, "%s = %s",
5474 rs[i].name,
5475 rs[i].s->get(&rs[i]));
5476 else if (rs[i].s && rs[i].s->get == NULL)
5477 show_oops(t, "%s = ...", rs[i].name);
5478 else
5479 show_oops(t, "%s = ", rs[i].name);
5480 break;
5481 default:
5482 show_oops(t, "unknown type for %s", rs[i].name);
5483 goto done;
5486 goto done;
5488 show_oops(t, "unknown option: %s", p);
5490 done:
5491 return (XT_CB_PASSTHROUGH);
5495 session_save(struct tab *t, char *filename)
5497 struct karg a;
5498 int rv = 1;
5499 struct session *s;
5501 if (strlen(filename) == 0)
5502 goto done;
5504 if (filename[0] == '.' || filename[0] == '/')
5505 goto done;
5507 a.s = filename;
5508 if (save_tabs(t, &a))
5509 goto done;
5510 strlcpy(named_session, filename, sizeof named_session);
5512 /* add the new session to the list of sessions */
5513 s = g_malloc(sizeof(struct session));
5514 s->name = g_strdup(filename);
5515 TAILQ_INSERT_TAIL(&sessions, s, entry);
5517 rv = 0;
5518 done:
5519 return (rv);
5523 session_open(struct tab *t, char *filename)
5525 struct karg a;
5526 int rv = 1;
5528 if (strlen(filename) == 0)
5529 goto done;
5531 if (filename[0] == '.' || filename[0] == '/')
5532 goto done;
5534 a.s = filename;
5535 a.i = XT_SES_CLOSETABS;
5536 if (open_tabs(t, &a))
5537 goto done;
5539 strlcpy(named_session, filename, sizeof named_session);
5541 rv = 0;
5542 done:
5543 return (rv);
5547 session_delete(struct tab *t, char *filename)
5549 char file[PATH_MAX];
5550 int rv = 1;
5551 struct session *s;
5553 if (strlen(filename) == 0)
5554 goto done;
5556 if (filename[0] == '.' || filename[0] == '/')
5557 goto done;
5559 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5560 if (unlink(file))
5561 goto done;
5563 if (!strcmp(filename, named_session))
5564 strlcpy(named_session, XT_SAVED_TABS_FILE,
5565 sizeof named_session);
5567 /* remove session from sessions list */
5568 TAILQ_FOREACH(s, &sessions, entry) {
5569 if (!strcmp(s->name, filename))
5570 break;
5572 if (s == NULL)
5573 goto done;
5574 TAILQ_REMOVE(&sessions, s, entry);
5575 g_free((gpointer) s->name);
5576 g_free(s);
5578 rv = 0;
5579 done:
5580 return (rv);
5584 session_cmd(struct tab *t, struct karg *args)
5586 char *filename = args->s;
5588 if (t == NULL)
5589 return (1);
5591 if (args->i & XT_SHOW)
5592 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5593 XT_SAVED_TABS_FILE : named_session);
5594 else if (args->i & XT_SAVE) {
5595 if (session_save(t, filename)) {
5596 show_oops(t, "Can't save session: %s",
5597 filename ? filename : "INVALID");
5598 goto done;
5600 } else if (args->i & XT_OPEN) {
5601 if (session_open(t, filename)) {
5602 show_oops(t, "Can't open session: %s",
5603 filename ? filename : "INVALID");
5604 goto done;
5606 } else if (args->i & XT_DELETE) {
5607 if (session_delete(t, filename)) {
5608 show_oops(t, "Can't delete session: %s",
5609 filename ? filename : "INVALID");
5610 goto done;
5613 done:
5614 return (XT_CB_PASSTHROUGH);
5618 * Make a hardcopy of the page
5621 print_page(struct tab *t, struct karg *args)
5623 WebKitWebFrame *frame;
5624 GtkPageSetup *ps;
5625 GtkPrintOperation *op;
5626 GtkPrintOperationAction action;
5627 GtkPrintOperationResult print_res;
5628 GError *g_err = NULL;
5629 int marg_l, marg_r, marg_t, marg_b;
5631 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5633 ps = gtk_page_setup_new();
5634 op = gtk_print_operation_new();
5635 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5636 frame = webkit_web_view_get_main_frame(t->wv);
5638 /* the default margins are too small, so we will bump them */
5639 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5640 XT_PRINT_EXTRA_MARGIN;
5641 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5642 XT_PRINT_EXTRA_MARGIN;
5643 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5644 XT_PRINT_EXTRA_MARGIN;
5645 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5646 XT_PRINT_EXTRA_MARGIN;
5648 /* set margins */
5649 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5650 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5651 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5652 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5654 gtk_print_operation_set_default_page_setup(op, ps);
5656 /* this appears to free 'op' and 'ps' */
5657 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5659 /* check it worked */
5660 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5661 show_oops(NULL, "can't print: %s", g_err->message);
5662 g_error_free (g_err);
5663 return (1);
5666 return (0);
5670 go_home(struct tab *t, struct karg *args)
5672 load_uri(t, home);
5673 return (0);
5677 set_encoding(struct tab *t, struct karg *args)
5679 const gchar *e;
5681 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5682 e = webkit_web_view_get_custom_encoding(t->wv);
5683 if (e == NULL)
5684 e = webkit_web_view_get_encoding(t->wv);
5685 show_oops(t, "encoding: %s", e ? e : "N/A");
5686 } else
5687 webkit_web_view_set_custom_encoding(t->wv, args->s);
5689 return (0);
5693 restart(struct tab *t, struct karg *args)
5695 struct karg a;
5697 a.s = XT_RESTART_TABS_FILE;
5698 save_tabs(t, &a);
5699 execvp(start_argv[0], start_argv);
5700 /* NOTREACHED */
5702 return (0);
5705 #define CTRL GDK_CONTROL_MASK
5706 #define MOD1 GDK_MOD1_MASK
5707 #define SHFT GDK_SHIFT_MASK
5709 /* inherent to GTK not all keys will be caught at all times */
5710 /* XXX sort key bindings */
5711 struct key_binding {
5712 char *cmd;
5713 guint mask;
5714 guint use_in_entry;
5715 guint key;
5716 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5717 } keys[] = {
5718 { "cookiejar", MOD1, 0, GDK_j },
5719 { "downloadmgr", MOD1, 0, GDK_d },
5720 { "history", MOD1, 0, GDK_h },
5721 { "print", CTRL, 0, GDK_p },
5722 { "search", 0, 0, GDK_slash },
5723 { "searchb", 0, 0, GDK_question },
5724 { "statustoggle", CTRL, 0, GDK_n },
5725 { "command", 0, 0, GDK_colon },
5726 { "qa", CTRL, 0, GDK_q },
5727 { "restart", MOD1, 0, GDK_q },
5728 { "js toggle", CTRL, 0, GDK_j },
5729 { "cookie toggle", MOD1, 0, GDK_c },
5730 { "togglesrc", CTRL, 0, GDK_s },
5731 { "yankuri", 0, 0, GDK_y },
5732 { "pasteuricur", 0, 0, GDK_p },
5733 { "pasteurinew", 0, 0, GDK_P },
5734 { "toplevel toggle", 0, 0, GDK_F4 },
5735 { "help", 0, 0, GDK_F1 },
5736 { "run_script", MOD1, 0, GDK_r },
5738 /* search */
5739 { "searchnext", 0, 0, GDK_n },
5740 { "searchprevious", 0, 0, GDK_N },
5742 /* focus */
5743 { "focusaddress", 0, 0, GDK_F6 },
5744 { "focussearch", 0, 0, GDK_F7 },
5746 /* hinting */
5747 { "hinting", 0, 0, GDK_f },
5749 /* custom stylesheet */
5750 { "userstyle", 0, 0, GDK_i },
5752 /* navigation */
5753 { "goback", 0, 0, GDK_BackSpace },
5754 { "goback", MOD1, 0, GDK_Left },
5755 { "goforward", SHFT, 0, GDK_BackSpace },
5756 { "goforward", MOD1, 0, GDK_Right },
5757 { "reload", 0, 0, GDK_F5 },
5758 { "reload", CTRL, 0, GDK_r },
5759 { "reload", CTRL, 0, GDK_l },
5760 { "favorites", MOD1, 1, GDK_f },
5762 /* vertical movement */
5763 { "scrolldown", 0, 0, GDK_j },
5764 { "scrolldown", 0, 0, GDK_Down },
5765 { "scrollup", 0, 0, GDK_Up },
5766 { "scrollup", 0, 0, GDK_k },
5767 { "scrollbottom", 0, 0, GDK_G },
5768 { "scrollbottom", 0, 0, GDK_End },
5769 { "scrolltop", 0, 0, GDK_Home },
5770 { "scrollpagedown", 0, 0, GDK_space },
5771 { "scrollpagedown", CTRL, 0, GDK_f },
5772 { "scrollhalfdown", CTRL, 0, GDK_d },
5773 { "scrollpagedown", 0, 0, GDK_Page_Down },
5774 { "scrollpageup", 0, 0, GDK_Page_Up },
5775 { "scrollpageup", CTRL, 0, GDK_b },
5776 { "scrollhalfup", CTRL, 0, GDK_u },
5777 /* horizontal movement */
5778 { "scrollright", 0, 0, GDK_l },
5779 { "scrollright", 0, 0, GDK_Right },
5780 { "scrollleft", 0, 0, GDK_Left },
5781 { "scrollleft", 0, 0, GDK_h },
5782 { "scrollfarright", 0, 0, GDK_dollar },
5783 { "scrollfarleft", 0, 0, GDK_0 },
5785 /* tabs */
5786 { "tabnew", CTRL, 0, GDK_t },
5787 { "999tabnew", CTRL, 0, GDK_T },
5788 { "tabclose", CTRL, 1, GDK_w },
5789 { "tabundoclose", 0, 0, GDK_U },
5790 { "tabnext 1", CTRL, 0, GDK_1 },
5791 { "tabnext 2", CTRL, 0, GDK_2 },
5792 { "tabnext 3", CTRL, 0, GDK_3 },
5793 { "tabnext 4", CTRL, 0, GDK_4 },
5794 { "tabnext 5", CTRL, 0, GDK_5 },
5795 { "tabnext 6", CTRL, 0, GDK_6 },
5796 { "tabnext 7", CTRL, 0, GDK_7 },
5797 { "tabnext 8", CTRL, 0, GDK_8 },
5798 { "tabnext 9", CTRL, 0, GDK_9 },
5799 { "tabfirst", CTRL, 0, GDK_less },
5800 { "tablast", CTRL, 0, GDK_greater },
5801 { "tabprevious", CTRL, 0, GDK_Left },
5802 { "tabnext", CTRL, 0, GDK_Right },
5803 { "focusout", CTRL, 0, GDK_minus },
5804 { "focusin", CTRL, 0, GDK_plus },
5805 { "focusin", CTRL, 0, GDK_equal },
5806 { "focusreset", CTRL, 0, GDK_0 },
5808 /* command aliases (handy when -S flag is used) */
5809 { "promptopen", 0, 0, GDK_F9 },
5810 { "promptopencurrent", 0, 0, GDK_F10 },
5811 { "prompttabnew", 0, 0, GDK_F11 },
5812 { "prompttabnewcurrent",0, 0, GDK_F12 },
5814 TAILQ_HEAD(keybinding_list, key_binding);
5816 void
5817 walk_kb(struct settings *s,
5818 void (*cb)(struct settings *, char *, void *), void *cb_args)
5820 struct key_binding *k;
5821 char str[1024];
5823 if (s == NULL || cb == NULL) {
5824 show_oops(NULL, "walk_kb invalid parameters");
5825 return;
5828 TAILQ_FOREACH(k, &kbl, entry) {
5829 if (k->cmd == NULL)
5830 continue;
5831 str[0] = '\0';
5833 /* sanity */
5834 if (gdk_keyval_name(k->key) == NULL)
5835 continue;
5837 strlcat(str, k->cmd, sizeof str);
5838 strlcat(str, ",", sizeof str);
5840 if (k->mask & GDK_SHIFT_MASK)
5841 strlcat(str, "S-", sizeof str);
5842 if (k->mask & GDK_CONTROL_MASK)
5843 strlcat(str, "C-", sizeof str);
5844 if (k->mask & GDK_MOD1_MASK)
5845 strlcat(str, "M1-", sizeof str);
5846 if (k->mask & GDK_MOD2_MASK)
5847 strlcat(str, "M2-", sizeof str);
5848 if (k->mask & GDK_MOD3_MASK)
5849 strlcat(str, "M3-", sizeof str);
5850 if (k->mask & GDK_MOD4_MASK)
5851 strlcat(str, "M4-", sizeof str);
5852 if (k->mask & GDK_MOD5_MASK)
5853 strlcat(str, "M5-", sizeof str);
5855 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5856 cb(s, str, cb_args);
5860 void
5861 init_keybindings(void)
5863 int i;
5864 struct key_binding *k;
5866 for (i = 0; i < LENGTH(keys); i++) {
5867 k = g_malloc0(sizeof *k);
5868 k->cmd = keys[i].cmd;
5869 k->mask = keys[i].mask;
5870 k->use_in_entry = keys[i].use_in_entry;
5871 k->key = keys[i].key;
5872 TAILQ_INSERT_HEAD(&kbl, k, entry);
5874 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5875 k->cmd ? k->cmd : "unnamed key");
5879 void
5880 keybinding_clearall(void)
5882 struct key_binding *k, *next;
5884 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5885 next = TAILQ_NEXT(k, entry);
5886 if (k->cmd == NULL)
5887 continue;
5889 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5890 k->cmd ? k->cmd : "unnamed key");
5891 TAILQ_REMOVE(&kbl, k, entry);
5892 g_free(k);
5897 keybinding_add(char *cmd, char *key, int use_in_entry)
5899 struct key_binding *k;
5900 guint keyval, mask = 0;
5901 int i;
5903 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5905 /* Keys which are to be used in entry have been prefixed with an
5906 * exclamation mark. */
5907 if (use_in_entry)
5908 key++;
5910 /* find modifier keys */
5911 if (strstr(key, "S-"))
5912 mask |= GDK_SHIFT_MASK;
5913 if (strstr(key, "C-"))
5914 mask |= GDK_CONTROL_MASK;
5915 if (strstr(key, "M1-"))
5916 mask |= GDK_MOD1_MASK;
5917 if (strstr(key, "M2-"))
5918 mask |= GDK_MOD2_MASK;
5919 if (strstr(key, "M3-"))
5920 mask |= GDK_MOD3_MASK;
5921 if (strstr(key, "M4-"))
5922 mask |= GDK_MOD4_MASK;
5923 if (strstr(key, "M5-"))
5924 mask |= GDK_MOD5_MASK;
5926 /* find keyname */
5927 for (i = strlen(key) - 1; i > 0; i--)
5928 if (key[i] == '-')
5929 key = &key[i + 1];
5931 /* validate keyname */
5932 keyval = gdk_keyval_from_name(key);
5933 if (keyval == GDK_VoidSymbol) {
5934 warnx("invalid keybinding name %s", key);
5935 return (1);
5937 /* must run this test too, gtk+ doesn't handle 10 for example */
5938 if (gdk_keyval_name(keyval) == NULL) {
5939 warnx("invalid keybinding name %s", key);
5940 return (1);
5943 /* Remove eventual dupes. */
5944 TAILQ_FOREACH(k, &kbl, entry)
5945 if (k->key == keyval && k->mask == mask) {
5946 TAILQ_REMOVE(&kbl, k, entry);
5947 g_free(k);
5948 break;
5951 /* add keyname */
5952 k = g_malloc0(sizeof *k);
5953 k->cmd = g_strdup(cmd);
5954 k->mask = mask;
5955 k->use_in_entry = use_in_entry;
5956 k->key = keyval;
5958 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5959 k->cmd,
5960 k->mask,
5961 k->use_in_entry,
5962 k->key);
5963 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5964 k->cmd, gdk_keyval_name(keyval));
5966 TAILQ_INSERT_HEAD(&kbl, k, entry);
5968 return (0);
5972 add_kb(struct settings *s, char *entry)
5974 char *kb, *key;
5976 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5978 /* clearall is special */
5979 if (!strcmp(entry, "clearall")) {
5980 keybinding_clearall();
5981 return (0);
5984 kb = strstr(entry, ",");
5985 if (kb == NULL)
5986 return (1);
5987 *kb = '\0';
5988 key = kb + 1;
5990 return (keybinding_add(entry, key, key[0] == '!'));
5993 struct cmd {
5994 char *cmd;
5995 int level;
5996 int (*func)(struct tab *, struct karg *);
5997 int arg;
5998 int type;
5999 } cmds[] = {
6000 { "command", 0, command, ':', 0 },
6001 { "search", 0, command, '/', 0 },
6002 { "searchb", 0, command, '?', 0 },
6003 { "togglesrc", 0, toggle_src, 0, 0 },
6005 /* yanking and pasting */
6006 { "yankuri", 0, yank_uri, 0, 0 },
6007 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
6008 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
6009 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
6011 /* search */
6012 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
6013 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
6015 /* focus */
6016 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
6017 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
6019 /* hinting */
6020 { "hinting", 0, hint, 0, 0 },
6022 /* custom stylesheet */
6023 { "userstyle", 0, userstyle, 0, 0 },
6025 /* navigation */
6026 { "goback", 0, navaction, XT_NAV_BACK, 0 },
6027 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
6028 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
6030 /* vertical movement */
6031 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
6032 { "scrollup", 0, move, XT_MOVE_UP, 0 },
6033 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
6034 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
6035 { "1", 0, move, XT_MOVE_TOP, 0 },
6036 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
6037 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
6038 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
6039 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
6040 /* horizontal movement */
6041 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
6042 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
6043 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
6044 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
6046 { "favorites", 0, xtp_page_fl, 0, 0 },
6047 { "fav", 0, xtp_page_fl, 0, 0 },
6048 { "favadd", 0, add_favorite, 0, 0 },
6050 { "qall", 0, quit, 0, 0 },
6051 { "quitall", 0, quit, 0, 0 },
6052 { "w", 0, save_tabs, 0, 0 },
6053 { "wq", 0, save_tabs_and_quit, 0, 0 },
6054 { "help", 0, help, 0, 0 },
6055 { "about", 0, about, 0, 0 },
6056 { "stats", 0, stats, 0, 0 },
6057 { "version", 0, about, 0, 0 },
6059 /* js command */
6060 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6061 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6062 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
6063 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6064 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6065 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6066 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
6067 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6068 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6069 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6070 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6072 /* cookie command */
6073 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6074 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6075 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
6076 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6077 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6078 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6079 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
6080 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6081 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6082 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6083 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6085 /* plugin command */
6086 { "plugin", 0, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6087 { "save", 1, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6088 { "domain", 2, pl_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
6089 { "fqdn", 2, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6090 { "show", 1, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6091 { "all", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6092 { "persistent", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
6093 { "session", 2, pl_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6094 { "toggle", 1, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6095 { "domain", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6096 { "fqdn", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6098 /* toplevel (domain) command */
6099 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
6100 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
6102 /* cookie jar */
6103 { "cookiejar", 0, xtp_page_cl, 0, 0 },
6105 /* cert command */
6106 { "cert", 0, cert_cmd, XT_SHOW, 0 },
6107 { "save", 1, cert_cmd, XT_SAVE, 0 },
6108 { "show", 1, cert_cmd, XT_SHOW, 0 },
6110 { "ca", 0, ca_cmd, 0, 0 },
6111 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
6112 { "dl", 0, xtp_page_dl, 0, 0 },
6113 { "h", 0, xtp_page_hl, 0, 0 },
6114 { "history", 0, xtp_page_hl, 0, 0 },
6115 { "home", 0, go_home, 0, 0 },
6116 { "restart", 0, restart, 0, 0 },
6117 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
6118 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
6119 { "statustoggle", 0, statustoggle, 0, 0 },
6120 { "run_script", 0, run_page_script, 0, XT_USERARG },
6122 { "print", 0, print_page, 0, 0 },
6124 /* tabs */
6125 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
6126 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
6127 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
6128 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
6129 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
6130 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
6131 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
6132 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
6133 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
6134 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
6135 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
6136 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
6137 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
6138 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
6139 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
6140 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
6141 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
6142 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
6143 { "buffers", 0, buffers, 0, 0 },
6144 { "ls", 0, buffers, 0, 0 },
6145 { "tabs", 0, buffers, 0, 0 },
6146 { "encoding", 0, set_encoding, 0, XT_USERARG },
6148 /* command aliases (handy when -S flag is used) */
6149 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
6150 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
6151 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
6152 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
6154 /* settings */
6155 { "set", 0, set, 0, XT_SETARG },
6157 { "fullscreen", 0, fullscreen, 0, 0 },
6158 { "f", 0, fullscreen, 0, 0 },
6160 /* sessions */
6161 { "session", 0, session_cmd, XT_SHOW, 0 },
6162 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
6163 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
6164 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
6165 { "show", 1, session_cmd, XT_SHOW, 0 },
6168 struct {
6169 int index;
6170 int len;
6171 gchar *list[256];
6172 } cmd_status = {-1, 0};
6174 gboolean
6175 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6178 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
6179 btn_down = 0;
6181 return (FALSE);
6184 gboolean
6185 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6187 struct karg a;
6189 hide_oops(t);
6190 hide_buffers(t);
6192 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6193 btn_down = 1;
6194 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6195 /* go backward */
6196 a.i = XT_NAV_BACK;
6197 navaction(t, &a);
6199 return (TRUE);
6200 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6201 /* go forward */
6202 a.i = XT_NAV_FORWARD;
6203 navaction(t, &a);
6205 return (TRUE);
6208 return (FALSE);
6211 gboolean
6212 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6214 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6216 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6217 delete_tab(t);
6219 return (FALSE);
6223 * cancel, remove, etc. downloads
6225 void
6226 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6228 struct download find, *d = NULL;
6230 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6232 /* some commands require a valid download id */
6233 if (cmd != XT_XTP_DL_LIST) {
6234 /* lookup download in question */
6235 find.id = id;
6236 d = RB_FIND(download_list, &downloads, &find);
6238 if (d == NULL) {
6239 show_oops(t, "%s: no such download", __func__);
6240 return;
6244 /* decide what to do */
6245 switch (cmd) {
6246 case XT_XTP_DL_CANCEL:
6247 webkit_download_cancel(d->download);
6248 break;
6249 case XT_XTP_DL_REMOVE:
6250 webkit_download_cancel(d->download); /* just incase */
6251 g_object_unref(d->download);
6252 RB_REMOVE(download_list, &downloads, d);
6253 break;
6254 case XT_XTP_DL_LIST:
6255 /* Nothing */
6256 break;
6257 default:
6258 show_oops(t, "%s: unknown command", __func__);
6259 break;
6261 xtp_page_dl(t, NULL);
6265 * Actions on history, only does one thing for now, but
6266 * we provide the function for future actions
6268 void
6269 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6271 struct history *h, *next;
6272 int i = 1;
6274 switch (cmd) {
6275 case XT_XTP_HL_REMOVE:
6276 /* walk backwards, as listed in reverse */
6277 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6278 next = RB_PREV(history_list, &hl, h);
6279 if (id == i) {
6280 RB_REMOVE(history_list, &hl, h);
6281 g_free((gpointer) h->title);
6282 g_free((gpointer) h->uri);
6283 g_free(h);
6284 break;
6286 i++;
6288 break;
6289 case XT_XTP_HL_LIST:
6290 /* Nothing - just xtp_page_hl() below */
6291 break;
6292 default:
6293 show_oops(t, "%s: unknown command", __func__);
6294 break;
6297 xtp_page_hl(t, NULL);
6300 /* remove a favorite */
6301 void
6302 remove_favorite(struct tab *t, int index)
6304 char file[PATH_MAX], *title, *uri = NULL;
6305 char *new_favs, *tmp;
6306 FILE *f;
6307 int i;
6308 size_t len, lineno;
6310 /* open favorites */
6311 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6313 if ((f = fopen(file, "r")) == NULL) {
6314 show_oops(t, "%s: can't open favorites: %s",
6315 __func__, strerror(errno));
6316 return;
6319 /* build a string which will become the new favroites file */
6320 new_favs = g_strdup("");
6322 for (i = 1;;) {
6323 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6324 if (feof(f) || ferror(f))
6325 break;
6326 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6327 if (len == 0) {
6328 free(title);
6329 title = NULL;
6330 continue;
6333 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6334 if (feof(f) || ferror(f)) {
6335 show_oops(t, "%s: can't parse favorites %s",
6336 __func__, strerror(errno));
6337 goto clean;
6341 /* as long as this isn't the one we are deleting add to file */
6342 if (i != index) {
6343 tmp = new_favs;
6344 new_favs = g_strdup_printf("%s%s\n%s\n",
6345 new_favs, title, uri);
6346 g_free(tmp);
6349 free(uri);
6350 uri = NULL;
6351 free(title);
6352 title = NULL;
6353 i++;
6355 fclose(f);
6357 /* write back new favorites file */
6358 if ((f = fopen(file, "w")) == NULL) {
6359 show_oops(t, "%s: can't open favorites: %s",
6360 __func__, strerror(errno));
6361 goto clean;
6364 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
6365 show_oops(t, "%s: can't fwrite"); /* shut gcc up */
6366 fclose(f);
6368 clean:
6369 if (uri)
6370 free(uri);
6371 if (title)
6372 free(title);
6374 g_free(new_favs);
6377 void
6378 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6380 switch (cmd) {
6381 case XT_XTP_FL_LIST:
6382 /* nothing, just the below call to xtp_page_fl() */
6383 break;
6384 case XT_XTP_FL_REMOVE:
6385 remove_favorite(t, arg);
6386 break;
6387 default:
6388 show_oops(t, "%s: invalid favorites command", __func__);
6389 break;
6392 xtp_page_fl(t, NULL);
6395 void
6396 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6398 switch (cmd) {
6399 case XT_XTP_CL_LIST:
6400 /* nothing, just xtp_page_cl() */
6401 break;
6402 case XT_XTP_CL_REMOVE:
6403 remove_cookie(arg);
6404 break;
6405 default:
6406 show_oops(t, "%s: unknown cookie xtp command", __func__);
6407 break;
6410 xtp_page_cl(t, NULL);
6413 /* link an XTP class to it's session key and handler function */
6414 struct xtp_despatch {
6415 uint8_t xtp_class;
6416 char **session_key;
6417 void (*handle_func)(struct tab *, uint8_t, int);
6420 struct xtp_despatch xtp_despatches[] = {
6421 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6422 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6423 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6424 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6425 { XT_XTP_INVALID, NULL, NULL }
6429 * is the url xtp protocol? (xxxt://)
6430 * if so, parse and despatch correct bahvior
6433 parse_xtp_url(struct tab *t, const char *url)
6435 char *dup = NULL, *p, *last = NULL;
6436 uint8_t n_tokens = 0;
6437 char *tokens[4] = {NULL, NULL, NULL, ""};
6438 struct xtp_despatch *dsp, *dsp_match = NULL;
6439 uint8_t req_class;
6440 int ret = FALSE;
6443 * tokens array meaning:
6444 * tokens[0] = class
6445 * tokens[1] = session key
6446 * tokens[2] = action
6447 * tokens[3] = optional argument
6450 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6452 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6453 goto clean;
6455 dup = g_strdup(url + strlen(XT_XTP_STR));
6457 /* split out the url */
6458 for ((p = strtok_r(dup, "/", &last)); p;
6459 (p = strtok_r(NULL, "/", &last))) {
6460 if (n_tokens < 4)
6461 tokens[n_tokens++] = p;
6464 /* should be atleast three fields 'class/seskey/command/arg' */
6465 if (n_tokens < 3)
6466 goto clean;
6468 dsp = xtp_despatches;
6469 req_class = atoi(tokens[0]);
6470 while (dsp->xtp_class) {
6471 if (dsp->xtp_class == req_class) {
6472 dsp_match = dsp;
6473 break;
6475 dsp++;
6478 /* did we find one atall? */
6479 if (dsp_match == NULL) {
6480 show_oops(t, "%s: no matching xtp despatch found", __func__);
6481 goto clean;
6484 /* check session key and call despatch function */
6485 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6486 ret = TRUE; /* all is well, this was a valid xtp request */
6487 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6490 clean:
6491 if (dup)
6492 g_free(dup);
6494 return (ret);
6499 void
6500 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6502 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6504 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6506 if (t == NULL) {
6507 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6508 return;
6511 if (uri == NULL) {
6512 show_oops(t, "activate_uri_entry_cb no uri");
6513 return;
6516 uri += strspn(uri, "\t ");
6518 /* if xxxt:// treat specially */
6519 if (parse_xtp_url(t, uri))
6520 return;
6522 /* otherwise continue to load page normally */
6523 load_uri(t, (gchar *)uri);
6524 focus_webview(t);
6527 void
6528 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6530 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6531 char *newuri = NULL;
6532 gchar *enc_search;
6534 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6536 if (t == NULL) {
6537 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6538 return;
6541 if (search_string == NULL) {
6542 show_oops(t, "no search_string");
6543 return;
6546 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6548 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6549 newuri = g_strdup_printf(search_string, enc_search);
6550 g_free(enc_search);
6552 marks_clear(t);
6553 webkit_web_view_load_uri(t->wv, newuri);
6554 focus_webview(t);
6556 if (newuri)
6557 g_free(newuri);
6560 void
6561 check_and_set_cookie(const gchar *uri, struct tab *t)
6563 struct domain *d = NULL;
6564 int es = 0;
6566 if (uri == NULL || t == NULL)
6567 return;
6569 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6570 es = 0;
6571 else
6572 es = 1;
6574 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6575 es ? "enable" : "disable", uri);
6577 g_object_set(G_OBJECT(t->settings),
6578 "enable-html5-local-storage", es, (char *)NULL);
6579 webkit_web_view_set_settings(t->wv, t->settings);
6582 void
6583 check_and_set_js(const gchar *uri, struct tab *t)
6585 struct domain *d = NULL;
6586 int es = 0;
6588 if (uri == NULL || t == NULL)
6589 return;
6591 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6592 es = 0;
6593 else
6594 es = 1;
6596 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6597 es ? "enable" : "disable", uri);
6599 g_object_set(G_OBJECT(t->settings),
6600 "enable-scripts", es, (char *)NULL);
6601 g_object_set(G_OBJECT(t->settings),
6602 "javascript-can-open-windows-automatically", es, (char *)NULL);
6603 webkit_web_view_set_settings(t->wv, t->settings);
6605 button_set_stockid(t->js_toggle,
6606 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6609 void
6610 check_and_set_pl(const gchar *uri, struct tab *t)
6612 struct domain *d = NULL;
6613 int es = 0;
6615 if (uri == NULL || t == NULL)
6616 return;
6618 if ((d = wl_find_uri(uri, &pl_wl)) == NULL)
6619 es = 0;
6620 else
6621 es = 1;
6623 DNPRINTF(XT_D_JS, "check_and_set_pl: %s %s\n",
6624 es ? "enable" : "disable", uri);
6626 g_object_set(G_OBJECT(t->settings),
6627 "enable-plugins", es, (char *)NULL);
6628 webkit_web_view_set_settings(t->wv, t->settings);
6631 void
6632 color_address_bar(gpointer p)
6634 GdkColor color;
6635 struct tab *tt, *t = p;
6636 gchar *col_str = XT_COLOR_WHITE;
6637 const gchar *uri, *u = NULL, *error_str = NULL;
6639 #ifdef USE_THREADS
6640 gdk_threads_enter();
6641 #endif
6642 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6644 /* make sure t still exists */
6645 if (t == NULL)
6646 return;
6647 TAILQ_FOREACH(tt, &tabs, entry)
6648 if (t == tt)
6649 break;
6650 if (t != tt)
6651 goto done;
6653 if ((uri = get_uri(t)) == NULL)
6654 goto white;
6655 u = g_strdup(uri);
6657 #ifdef USE_THREADS
6658 gdk_threads_leave();
6659 #endif
6661 col_str = XT_COLOR_YELLOW;
6662 switch (load_compare_cert(u, &error_str)) {
6663 case CERT_LOCAL:
6664 col_str = XT_COLOR_BLUE;
6665 break;
6666 case CERT_TRUSTED:
6667 col_str = XT_COLOR_GREEN;
6668 break;
6669 case CERT_UNTRUSTED:
6670 col_str = XT_COLOR_YELLOW;
6671 break;
6672 case CERT_BAD:
6673 col_str = XT_COLOR_RED;
6674 break;
6677 #ifdef USE_THREADS
6678 gdk_threads_enter();
6679 #endif
6680 /* make sure t isn't deleted */
6681 TAILQ_FOREACH(tt, &tabs, entry)
6682 if (t == tt)
6683 break;
6684 if (t != tt)
6685 goto done;
6687 #ifdef USE_THREADS
6688 /* test to see if the user navigated away and canceled the thread */
6689 if (t->thread != g_thread_self())
6690 goto done;
6691 if ((uri = get_uri(t)) == NULL) {
6692 t->thread = NULL;
6693 goto done;
6695 if (strcmp(uri, u)) {
6696 /* make sure we are still the same url */
6697 t->thread = NULL;
6698 goto done;
6700 #endif
6701 white:
6702 gdk_color_parse(col_str, &color);
6703 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6705 if (!strcmp(col_str, XT_COLOR_WHITE))
6706 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6707 else
6708 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6710 if (error_str && error_str[0] != '\0')
6711 show_oops(t, "%s", error_str);
6712 #ifdef USE_THREADS
6713 t->thread = NULL;
6714 #endif
6715 done:
6716 /* t is invalid at this point */
6717 if (u)
6718 g_free((gpointer)u);
6719 #ifdef USE_THREADS
6720 gdk_threads_leave();
6721 #endif
6724 void
6725 show_ca_status(struct tab *t, const char *uri)
6727 GdkColor color;
6728 gchar *col_str = XT_COLOR_WHITE;
6730 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6731 ssl_strict_certs, ssl_ca_file, uri);
6733 if (t == NULL)
6734 return;
6736 if (uri == NULL)
6737 goto done;
6738 if (ssl_ca_file == NULL) {
6739 if (g_str_has_prefix(uri, "http://"))
6740 goto done;
6741 if (g_str_has_prefix(uri, "https://")) {
6742 col_str = XT_COLOR_RED;
6743 goto done;
6745 return;
6747 if (g_str_has_prefix(uri, "http://") ||
6748 !g_str_has_prefix(uri, "https://"))
6749 goto done;
6750 #ifdef USE_THREADS
6752 * It is not necessary to see if the thread is already running.
6753 * If the thread is in progress setting it to something else aborts it
6754 * on the way out.
6757 /* thread the coloring of the address bar */
6758 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6759 #else
6760 color_address_bar(t);
6761 #endif
6762 return;
6764 done:
6765 if (col_str) {
6766 gdk_color_parse(col_str, &color);
6767 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6769 if (!strcmp(col_str, XT_COLOR_WHITE))
6770 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6771 else
6772 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6776 void
6777 free_favicon(struct tab *t)
6779 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6780 __func__, t->icon_download, t->icon_request);
6782 if (t->icon_request)
6783 g_object_unref(t->icon_request);
6784 if (t->icon_dest_uri)
6785 g_free(t->icon_dest_uri);
6787 t->icon_request = NULL;
6788 t->icon_dest_uri = NULL;
6791 void
6792 xt_icon_from_name(struct tab *t, gchar *name)
6794 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6795 GTK_ENTRY_ICON_PRIMARY, "text-html");
6796 if (show_url == 0)
6797 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6798 GTK_ENTRY_ICON_PRIMARY, "text-html");
6799 else
6800 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6801 GTK_ENTRY_ICON_PRIMARY, NULL);
6804 void
6805 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6807 GdkPixbuf *pb_scaled;
6809 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6810 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6811 GDK_INTERP_BILINEAR);
6812 else
6813 pb_scaled = pb;
6815 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6816 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6817 if (show_url == 0)
6818 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6819 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6820 else
6821 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6822 GTK_ENTRY_ICON_PRIMARY, NULL);
6824 if (pb_scaled != pb)
6825 g_object_unref(pb_scaled);
6828 void
6829 xt_icon_from_file(struct tab *t, char *file)
6831 GdkPixbuf *pb;
6833 if (g_str_has_prefix(file, "file://"))
6834 file += strlen("file://");
6836 pb = gdk_pixbuf_new_from_file(file, NULL);
6837 if (pb) {
6838 xt_icon_from_pixbuf(t, pb);
6839 g_object_unref(pb);
6840 } else
6841 xt_icon_from_name(t, "text-html");
6844 gboolean
6845 is_valid_icon(char *file)
6847 gboolean valid = 0;
6848 const char *mime_type;
6849 GFileInfo *fi;
6850 GFile *gf;
6852 gf = g_file_new_for_path(file);
6853 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6854 NULL, NULL);
6855 mime_type = g_file_info_get_content_type(fi);
6856 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6857 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6858 g_strcmp0(mime_type, "image/png") == 0 ||
6859 g_strcmp0(mime_type, "image/gif") == 0 ||
6860 g_strcmp0(mime_type, "application/octet-stream") == 0;
6861 g_object_unref(fi);
6862 g_object_unref(gf);
6864 return (valid);
6867 void
6868 set_favicon_from_file(struct tab *t, char *file)
6870 struct stat sb;
6872 if (t == NULL || file == NULL)
6873 return;
6875 if (g_str_has_prefix(file, "file://"))
6876 file += strlen("file://");
6877 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6879 if (!stat(file, &sb)) {
6880 if (sb.st_size == 0 || !is_valid_icon(file)) {
6881 /* corrupt icon so trash it */
6882 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6883 __func__, file);
6884 unlink(file);
6885 /* no need to set icon to default here */
6886 return;
6889 xt_icon_from_file(t, file);
6892 void
6893 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6894 WebKitWebView *wv)
6896 WebKitDownloadStatus status = webkit_download_get_status(download);
6897 struct tab *tt = NULL, *t = NULL;
6900 * find the webview instead of passing in the tab as it could have been
6901 * deleted from underneath us.
6903 TAILQ_FOREACH(tt, &tabs, entry) {
6904 if (tt->wv == wv) {
6905 t = tt;
6906 break;
6909 if (t == NULL)
6910 return;
6912 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6913 __func__, t->tab_id, status);
6915 switch (status) {
6916 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6917 /* -1 */
6918 t->icon_download = NULL;
6919 free_favicon(t);
6920 break;
6921 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6922 /* 0 */
6923 break;
6924 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6925 /* 1 */
6926 break;
6927 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6928 /* 2 */
6929 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6930 __func__, t->tab_id);
6931 t->icon_download = NULL;
6932 free_favicon(t);
6933 break;
6934 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6935 /* 3 */
6937 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6938 __func__, t->icon_dest_uri);
6939 set_favicon_from_file(t, t->icon_dest_uri);
6940 /* these will be freed post callback */
6941 t->icon_request = NULL;
6942 t->icon_download = NULL;
6943 break;
6944 default:
6945 break;
6949 void
6950 abort_favicon_download(struct tab *t)
6952 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6954 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6955 if (t->icon_download) {
6956 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6957 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6958 webkit_download_cancel(t->icon_download);
6959 t->icon_download = NULL;
6960 } else
6961 free_favicon(t);
6962 #endif
6964 xt_icon_from_name(t, "text-html");
6967 void
6968 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6970 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6972 if (uri == NULL || t == NULL)
6973 return;
6975 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6976 /* take icon from WebKitIconDatabase */
6977 GdkPixbuf *pb;
6979 pb = webkit_web_view_get_icon_pixbuf(wv);
6980 if (pb) {
6981 xt_icon_from_pixbuf(t, pb);
6982 g_object_unref(pb);
6983 } else
6984 xt_icon_from_name(t, "text-html");
6985 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6986 /* download icon to cache dir */
6987 gchar *name_hash, file[PATH_MAX];
6988 struct stat sb;
6990 if (t->icon_request) {
6991 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6992 return;
6995 /* check to see if we got the icon in cache */
6996 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6997 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6998 g_free(name_hash);
7000 if (!stat(file, &sb)) {
7001 if (sb.st_size > 0) {
7002 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
7003 __func__, file);
7004 set_favicon_from_file(t, file);
7005 return;
7008 /* corrupt icon so trash it */
7009 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
7010 __func__, file);
7011 unlink(file);
7014 /* create download for icon */
7015 t->icon_request = webkit_network_request_new(uri);
7016 if (t->icon_request == NULL) {
7017 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
7018 __func__, uri);
7019 return;
7022 t->icon_download = webkit_download_new(t->icon_request);
7023 if (t->icon_download == NULL)
7024 return;
7026 /* we have to free icon_dest_uri later */
7027 t->icon_dest_uri = g_strdup_printf("file://%s", file);
7028 webkit_download_set_destination_uri(t->icon_download,
7029 t->icon_dest_uri);
7031 if (webkit_download_get_status(t->icon_download) ==
7032 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7033 g_object_unref(t->icon_request);
7034 g_free(t->icon_dest_uri);
7035 t->icon_request = NULL;
7036 t->icon_dest_uri = NULL;
7037 return;
7040 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
7041 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
7043 webkit_download_start(t->icon_download);
7044 #endif
7047 void
7048 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
7050 const gchar *uri = NULL, *title = NULL;
7051 struct history *h, find;
7052 struct karg a;
7053 GdkColor color;
7055 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
7056 webkit_web_view_get_load_status(wview),
7057 get_uri(t) ? get_uri(t) : "NOTHING");
7059 if (t == NULL) {
7060 show_oops(NULL, "notify_load_status_cb invalid parameters");
7061 return;
7064 switch (webkit_web_view_get_load_status(wview)) {
7065 case WEBKIT_LOAD_PROVISIONAL:
7066 /* 0 */
7067 abort_favicon_download(t);
7068 #if GTK_CHECK_VERSION(2, 20, 0)
7069 gtk_widget_show(t->spinner);
7070 gtk_spinner_start(GTK_SPINNER(t->spinner));
7071 #endif
7072 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
7074 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
7076 /* assume we are a new address */
7077 gdk_color_parse("white", &color);
7078 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
7079 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
7081 /* take focus if we are visible */
7082 focus_webview(t);
7083 t->focus_wv = 1;
7084 #ifdef USE_THREAD
7085 /* kill color thread */
7086 t->thread = NULL;
7087 #endif
7088 break;
7090 case WEBKIT_LOAD_COMMITTED:
7091 /* 1 */
7092 uri = get_uri(t);
7093 if (uri == NULL)
7094 return;
7095 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
7097 if (t->status) {
7098 g_free(t->status);
7099 t->status = NULL;
7101 set_status(t, (char *)uri, XT_STATUS_LOADING);
7103 /* check if js white listing is enabled */
7104 if (enable_plugin_whitelist)
7105 check_and_set_pl(uri, t);
7106 if (enable_cookie_whitelist)
7107 check_and_set_cookie(uri, t);
7108 if (enable_js_whitelist)
7109 check_and_set_js(uri, t);
7111 if (t->styled)
7112 apply_style(t);
7115 /* we know enough to autosave the session */
7116 if (session_autosave) {
7117 a.s = NULL;
7118 save_tabs(t, &a);
7121 show_ca_status(t, uri);
7122 break;
7124 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
7125 /* 3 */
7126 break;
7128 case WEBKIT_LOAD_FINISHED:
7129 /* 2 */
7130 uri = get_uri(t);
7131 if (uri == NULL)
7132 return;
7134 if (!strncmp(uri, "http://", strlen("http://")) ||
7135 !strncmp(uri, "https://", strlen("https://")) ||
7136 !strncmp(uri, "file://", strlen("file://"))) {
7137 find.uri = uri;
7138 h = RB_FIND(history_list, &hl, &find);
7139 if (!h) {
7140 title = get_title(t, FALSE);
7141 h = g_malloc(sizeof *h);
7142 h->uri = g_strdup(uri);
7143 h->title = g_strdup(title);
7144 RB_INSERT(history_list, &hl, h);
7145 completion_add_uri(h->uri);
7146 update_history_tabs(NULL);
7150 set_status(t, (char *)uri, XT_STATUS_URI);
7151 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7152 case WEBKIT_LOAD_FAILED:
7153 /* 4 */
7154 #endif
7155 #if GTK_CHECK_VERSION(2, 20, 0)
7156 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7157 gtk_widget_hide(t->spinner);
7158 #endif
7159 default:
7160 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
7161 break;
7164 if (t->item)
7165 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
7166 else
7167 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
7168 can_go_back_for_real(t));
7170 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
7171 can_go_forward_for_real(t));
7174 void
7175 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
7177 const gchar *title = NULL, *win_title = NULL;
7179 title = get_title(t, FALSE);
7180 win_title = get_title(t, TRUE);
7181 gtk_label_set_text(GTK_LABEL(t->label), title);
7182 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
7183 if (t->tab_id == gtk_notebook_get_current_page(notebook))
7184 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
7187 void
7188 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7190 run_script(t, JS_HINTING);
7193 void
7194 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
7196 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
7197 progress == 100 ? 0 : (double)progress / 100);
7198 if (show_url == 0) {
7199 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
7200 progress == 100 ? 0 : (double)progress / 100);
7203 update_statusbar_position(NULL, NULL);
7207 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
7208 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
7209 WebKitWebPolicyDecision *pd, struct tab *t)
7211 char *uri;
7212 WebKitWebNavigationReason reason;
7213 struct domain *d = NULL;
7215 if (t == NULL) {
7216 show_oops(NULL, "webview_npd_cb invalid parameters");
7217 return (FALSE);
7220 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
7221 t->ctrl_click,
7222 webkit_network_request_get_uri(request));
7224 uri = (char *)webkit_network_request_get_uri(request);
7226 /* if this is an xtp url, we don't load anything else */
7227 if (parse_xtp_url(t, uri))
7228 return (TRUE);
7230 if (t->ctrl_click) {
7231 t->ctrl_click = 0;
7232 create_new_tab(uri, NULL, ctrl_click_focus, -1);
7233 webkit_web_policy_decision_ignore(pd);
7234 return (TRUE); /* we made the decission */
7238 * This is a little hairy but it comes down to this:
7239 * when we run in whitelist mode we have to assist the browser in
7240 * opening the URL that it would have opened in a new tab.
7242 reason = webkit_web_navigation_action_get_reason(na);
7243 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7244 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7245 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7246 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7247 load_uri(t, uri);
7248 webkit_web_policy_decision_use(pd);
7249 return (TRUE); /* we made the decision */
7252 return (FALSE);
7255 WebKitWebView *
7256 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7258 struct tab *tt;
7259 struct domain *d = NULL;
7260 const gchar *uri;
7261 WebKitWebView *webview = NULL;
7263 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7264 webkit_web_view_get_uri(wv));
7266 if (tabless) {
7267 /* open in current tab */
7268 webview = t->wv;
7269 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7270 uri = webkit_web_view_get_uri(wv);
7271 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7272 return (NULL);
7274 tt = create_new_tab(NULL, NULL, 1, -1);
7275 webview = tt->wv;
7276 } else if (enable_scripts == 1) {
7277 tt = create_new_tab(NULL, NULL, 1, -1);
7278 webview = tt->wv;
7281 return (webview);
7284 gboolean
7285 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7287 const gchar *uri;
7288 struct domain *d = NULL;
7290 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7292 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7293 uri = webkit_web_view_get_uri(wv);
7294 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7295 return (FALSE);
7297 delete_tab(t);
7298 } else if (enable_scripts == 1)
7299 delete_tab(t);
7301 return (TRUE);
7305 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7307 /* we can not eat the event without throwing gtk off so defer it */
7309 /* catch middle click */
7310 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7311 t->ctrl_click = 1;
7312 goto done;
7315 /* catch ctrl click */
7316 if (e->type == GDK_BUTTON_RELEASE &&
7317 CLEAN(e->state) == GDK_CONTROL_MASK)
7318 t->ctrl_click = 1;
7319 else
7320 t->ctrl_click = 0;
7321 done:
7322 return (XT_CB_PASSTHROUGH);
7326 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7328 struct mime_type *m;
7330 m = find_mime_type(mime_type);
7331 if (m == NULL)
7332 return (1);
7333 if (m->mt_download)
7334 return (1);
7336 switch (fork()) {
7337 case -1:
7338 show_oops(t, "can't fork mime handler");
7339 return (1);
7340 /* NOTREACHED */
7341 case 0:
7342 break;
7343 default:
7344 return (0);
7347 /* child */
7348 execlp(m->mt_action, m->mt_action,
7349 webkit_network_request_get_uri(request), (void *)NULL);
7351 _exit(0);
7353 /* NOTREACHED */
7354 return (0);
7357 char *
7358 get_mime_type(const char *file)
7360 const gchar *m;
7361 char *mime_type = NULL;
7362 GFileInfo *fi;
7363 GFile *gf;
7365 if (g_str_has_prefix(file, "file://"))
7366 file += strlen("file://");
7368 gf = g_file_new_for_path(file);
7369 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7370 NULL, NULL);
7371 if ((m = g_file_info_get_content_type(fi)) != NULL)
7372 mime_type = g_strdup(m);
7373 g_object_unref(fi);
7374 g_object_unref(gf);
7376 return (mime_type);
7380 run_download_mimehandler(char *mime_type, char *file)
7382 struct mime_type *m;
7384 m = find_mime_type(mime_type);
7385 if (m == NULL)
7386 return (1);
7388 switch (fork()) {
7389 case -1:
7390 show_oops(NULL, "can't fork download mime handler");
7391 return (1);
7392 /* NOTREACHED */
7393 case 0:
7394 break;
7395 default:
7396 return (0);
7399 /* child */
7400 if (g_str_has_prefix(file, "file://"))
7401 file += strlen("file://");
7402 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7404 _exit(0);
7406 /* NOTREACHED */
7407 return (0);
7410 void
7411 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7412 WebKitWebView *wv)
7414 WebKitDownloadStatus status;
7415 const char *file = NULL;
7416 char *mime = NULL;
7418 if (download == NULL)
7419 return;
7420 status = webkit_download_get_status(download);
7421 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7422 return;
7424 file = webkit_download_get_destination_uri(download);
7425 if (file == NULL)
7426 return;
7427 mime = get_mime_type(file);
7428 if (mime == NULL)
7429 return;
7431 run_download_mimehandler((char *)mime, (char *)file);
7432 g_free(mime);
7436 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7437 WebKitNetworkRequest *request, char *mime_type,
7438 WebKitWebPolicyDecision *decision, struct tab *t)
7440 if (t == NULL) {
7441 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7442 return (FALSE);
7445 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7446 t->tab_id, mime_type);
7448 if (run_mimehandler(t, mime_type, request) == 0) {
7449 webkit_web_policy_decision_ignore(decision);
7450 focus_webview(t);
7451 return (TRUE);
7454 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7455 webkit_web_policy_decision_download(decision);
7456 return (TRUE);
7459 return (FALSE);
7463 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7464 struct tab *t)
7466 struct stat sb;
7467 const gchar *suggested_name;
7468 gchar *filename = NULL;
7469 char *uri = NULL;
7470 struct download *download_entry;
7471 int i, ret = TRUE;
7473 if (wk_download == NULL || t == NULL) {
7474 show_oops(NULL, "%s invalid parameters", __func__);
7475 return (FALSE);
7478 suggested_name = webkit_download_get_suggested_filename(wk_download);
7479 if (suggested_name == NULL)
7480 return (FALSE); /* abort download */
7482 i = 0;
7483 do {
7484 if (filename) {
7485 g_free(filename);
7486 filename = NULL;
7488 if (i) {
7489 g_free(uri);
7490 uri = NULL;
7491 filename = g_strdup_printf("%d%s", i, suggested_name);
7493 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7494 filename : suggested_name);
7495 i++;
7496 } while (!stat(uri + strlen("file://"), &sb));
7498 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7499 "local %s\n", __func__, t->tab_id, filename, uri);
7501 webkit_download_set_destination_uri(wk_download, uri);
7503 if (webkit_download_get_status(wk_download) ==
7504 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7505 show_oops(t, "%s: download failed to start", __func__);
7506 ret = FALSE;
7507 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7508 } else {
7509 /* connect "download first" mime handler */
7510 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7511 G_CALLBACK(download_status_changed_cb), NULL);
7513 download_entry = g_malloc(sizeof(struct download));
7514 download_entry->download = wk_download;
7515 download_entry->tab = t;
7516 download_entry->id = next_download_id++;
7517 RB_INSERT(download_list, &downloads, download_entry);
7518 /* get from history */
7519 g_object_ref(wk_download);
7520 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7521 show_oops(t, "Download of '%s' started...",
7522 basename((char *)webkit_download_get_destination_uri(wk_download)));
7525 if (uri)
7526 g_free(uri);
7528 if (filename)
7529 g_free(filename);
7531 /* sync other download manager tabs */
7532 update_download_tabs(NULL);
7535 * NOTE: never redirect/render the current tab before this
7536 * function returns. This will cause the download to never start.
7538 return (ret); /* start download */
7541 void
7542 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7544 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7546 if (t == NULL) {
7547 show_oops(NULL, "webview_hover_cb");
7548 return;
7551 if (uri)
7552 set_status(t, uri, XT_STATUS_LINK);
7553 else {
7554 if (t->status)
7555 set_status(t, t->status, XT_STATUS_NOTHING);
7560 mark(struct tab *t, struct karg *arg)
7562 char mark;
7563 int index;
7565 mark = arg->s[1];
7566 if ((index = marktoindex(mark)) == -1)
7567 return (-1);
7569 if (arg->i == XT_MARK_SET)
7570 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7571 else if (arg->i == XT_MARK_GOTO) {
7572 if (t->mark[index] == XT_INVALID_MARK) {
7573 show_oops(t, "mark '%c' does not exist", mark);
7574 return (-1);
7576 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7577 something changes the document size */
7578 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7581 return (0);
7584 void
7585 marks_clear(struct tab *t)
7587 int i;
7589 for (i = 0; i < LENGTH(t->mark); i++)
7590 t->mark[i] = XT_INVALID_MARK;
7594 qmarks_load(void)
7596 char file[PATH_MAX];
7597 char *line = NULL, *p;
7598 int index, i;
7599 FILE *f;
7600 size_t linelen;
7602 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7603 if ((f = fopen(file, "r+")) == NULL) {
7604 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7605 return (1);
7608 for (i = 1; ; i++) {
7609 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7610 break;
7611 if (strlen(line) == 0 || line[0] == '#') {
7612 free(line);
7613 line = NULL;
7614 continue;
7617 p = strtok(line, " \t");
7619 if (p == NULL || strlen(p) != 1 ||
7620 (index = marktoindex(*p)) == -1) {
7621 warnx("corrupt quickmarks file, line %d", i);
7622 break;
7625 p = strtok(NULL, " \t");
7626 if (qmarks[index] != NULL)
7627 g_free(qmarks[index]);
7628 qmarks[index] = g_strdup(p);
7631 fclose(f);
7633 return (0);
7637 qmarks_save(void)
7639 char file[PATH_MAX];
7640 int i;
7641 FILE *f;
7643 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7644 if ((f = fopen(file, "r+")) == NULL) {
7645 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7646 return (1);
7649 for (i = 0; i < XT_NOMARKS; i++)
7650 if (qmarks[i] != NULL)
7651 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7653 fclose(f);
7655 return (0);
7659 qmark(struct tab *t, struct karg *arg)
7661 char mark;
7662 int index;
7664 mark = arg->s[strlen(arg->s)-1];
7665 index = marktoindex(mark);
7666 if (index == -1)
7667 return (-1);
7669 switch (arg->i) {
7670 case XT_QMARK_SET:
7671 if (qmarks[index] != NULL)
7672 g_free(qmarks[index]);
7674 qmarks_load(); /* sync if multiple instances */
7675 qmarks[index] = g_strdup(get_uri(t));
7676 qmarks_save();
7677 break;
7678 case XT_QMARK_OPEN:
7679 if (qmarks[index] != NULL)
7680 load_uri(t, qmarks[index]);
7681 else {
7682 show_oops(t, "quickmark \"%c\" does not exist",
7683 mark);
7684 return (-1);
7686 break;
7687 case XT_QMARK_TAB:
7688 if (qmarks[index] != NULL)
7689 create_new_tab(qmarks[index], NULL, 1, -1);
7690 else {
7691 show_oops(t, "quickmark \"%c\" does not exist",
7692 mark);
7693 return (-1);
7695 break;
7698 return (0);
7702 go_up(struct tab *t, struct karg *args)
7704 int levels;
7705 char *uri;
7706 char *tmp;
7708 levels = atoi(args->s);
7709 if (levels == 0)
7710 levels = 1;
7712 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7713 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7714 return (1);
7715 tmp += strlen(XT_PROTO_DELIM);
7717 /* if an uri starts with a slash, leave it alone (for file:///) */
7718 if (tmp[0] == '/')
7719 tmp++;
7721 while (levels--) {
7722 char *p;
7724 p = strrchr(tmp, '/');
7725 if (p != NULL)
7726 *p = '\0';
7727 else
7728 break;
7731 load_uri(t, uri);
7732 g_free(uri);
7734 return (0);
7738 gototab(struct tab *t, struct karg *args)
7740 int tab;
7741 struct karg arg = {0, NULL, -1};
7743 tab = atoi(args->s);
7745 arg.i = XT_TAB_NEXT;
7746 arg.precount = tab;
7748 movetab(t, &arg);
7750 return (0);
7754 zoom_amount(struct tab *t, struct karg *arg)
7756 struct karg narg = {0, NULL, -1};
7758 narg.i = atoi(arg->s);
7759 resizetab(t, &narg);
7761 return (0);
7765 flip_colon(struct tab *t, struct karg *arg)
7767 struct karg narg = {0, NULL, -1};
7768 char *p;
7770 if (t == NULL || arg == NULL)
7771 return (1);
7773 p = strstr(arg->s, ":");
7774 if (p == NULL)
7775 return (1);
7776 *p = '\0';
7778 narg.i = ':';
7779 narg.s = arg->s;
7780 command(t, &narg);
7782 return (0);
7785 /* buffer commands receive the regex that triggered them in arg.s */
7786 char bcmd[XT_BUFCMD_SZ];
7787 struct buffercmd {
7788 char *regex;
7789 int precount;
7790 #define XT_PRE_NO (0)
7791 #define XT_PRE_YES (1)
7792 #define XT_PRE_MAYBE (2)
7793 char *cmd;
7794 int (*func)(struct tab *, struct karg *);
7795 int arg;
7796 regex_t cregex;
7797 } buffercmds[] = {
7798 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7799 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7800 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7801 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7802 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7803 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7804 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7805 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7806 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7807 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7808 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7809 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7810 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7811 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7812 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7813 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7814 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7815 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7818 void
7819 buffercmd_init(void)
7821 int i;
7823 for (i = 0; i < LENGTH(buffercmds); i++)
7824 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7825 REG_EXTENDED | REG_NOSUB))
7826 startpage_add("invalid buffercmd regex %s",
7827 buffercmds[i].regex);
7830 void
7831 buffercmd_abort(struct tab *t)
7833 int i;
7835 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7836 for (i = 0; i < LENGTH(bcmd); i++)
7837 bcmd[i] = '\0';
7839 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7840 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7843 void
7844 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7846 struct karg arg = {0, NULL, -1};
7848 arg.i = cmd->arg;
7849 arg.s = g_strdup(bcmd);
7851 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7852 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7853 cmd->func(t, &arg);
7855 if (arg.s)
7856 g_free(arg.s);
7858 buffercmd_abort(t);
7861 gboolean
7862 buffercmd_addkey(struct tab *t, guint keyval)
7864 int i, c, match ;
7865 char s[XT_BUFCMD_SZ];
7867 if (keyval == GDK_Escape) {
7868 buffercmd_abort(t);
7869 return (XT_CB_HANDLED);
7872 /* key with modifier or non-ascii character */
7873 if (!isascii(keyval))
7874 return (XT_CB_PASSTHROUGH);
7876 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7877 "to buffer \"%s\"\n", keyval, bcmd);
7879 for (i = 0; i < LENGTH(bcmd); i++)
7880 if (bcmd[i] == '\0') {
7881 bcmd[i] = keyval;
7882 break;
7885 /* buffer full, ignore input */
7886 if (i >= LENGTH(bcmd) -1) {
7887 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7888 buffercmd_abort(t);
7889 return (XT_CB_HANDLED);
7892 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7894 /* find exact match */
7895 for (i = 0; i < LENGTH(buffercmds); i++)
7896 if (regexec(&buffercmds[i].cregex, bcmd,
7897 (size_t) 0, NULL, 0) == 0) {
7898 buffercmd_execute(t, &buffercmds[i]);
7899 goto done;
7902 /* find non exact matches to see if we need to abort ot not */
7903 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7904 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7905 c = -1;
7906 s[0] = '\0';
7907 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7908 if (isdigit(bcmd[0])) {
7909 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7910 continue;
7911 } else {
7912 c = 0;
7913 if (sscanf(bcmd, "%s", s) == 0)
7914 continue;
7916 } else if (buffercmds[i].precount == XT_PRE_YES) {
7917 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7918 continue;
7919 } else {
7920 if (sscanf(bcmd, "%s", s) == 0)
7921 continue;
7923 if (c == -1 && buffercmds[i].precount)
7924 continue;
7925 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7926 match++;
7928 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7929 i, match, buffercmds[i].cmd, c, s);
7931 if (match == 0) {
7932 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7933 buffercmd_abort(t);
7936 done:
7937 return (XT_CB_HANDLED);
7940 gboolean
7941 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7943 struct key_binding *k;
7945 /* handle keybindings if buffercmd is empty.
7946 if not empty, allow commands like C-n */
7947 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7948 TAILQ_FOREACH(k, &kbl, entry)
7949 if (e->keyval == k->key
7950 && (entry ? k->use_in_entry : 1)) {
7951 if (k->mask == 0) {
7952 if ((e->state & (CTRL | MOD1)) == 0)
7953 return (cmd_execute(t, k->cmd));
7954 } else if ((e->state & k->mask) == k->mask) {
7955 return (cmd_execute(t, k->cmd));
7959 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7960 return buffercmd_addkey(t, e->keyval);
7962 return (XT_CB_PASSTHROUGH);
7966 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7968 char s[2], buf[128];
7969 const char *errstr = NULL;
7971 /* don't use w directly; use t->whatever instead */
7973 if (t == NULL) {
7974 show_oops(NULL, "wv_keypress_after_cb");
7975 return (XT_CB_PASSTHROUGH);
7978 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7979 e->keyval, e->state, t);
7981 if (t->hints_on) {
7982 /* ESC */
7983 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7984 disable_hints(t);
7985 return (XT_CB_HANDLED);
7988 /* RETURN */
7989 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7990 if (errstr) {
7991 /* we have a string */
7992 } else {
7993 /* we have a number */
7994 snprintf(buf, sizeof buf,
7995 "vimprobable_fire(%s)", t->hint_num);
7996 run_script(t, buf);
7998 disable_hints(t);
8001 /* BACKSPACE */
8002 /* XXX unfuck this */
8003 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
8004 if (t->hint_mode == XT_HINT_NUMERICAL) {
8005 /* last input was numerical */
8006 int l;
8007 l = strlen(t->hint_num);
8008 if (l > 0) {
8009 l--;
8010 if (l == 0) {
8011 disable_hints(t);
8012 enable_hints(t);
8013 } else {
8014 t->hint_num[l] = '\0';
8015 goto num;
8018 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
8019 /* last input was alphanumerical */
8020 int l;
8021 l = strlen(t->hint_buf);
8022 if (l > 0) {
8023 l--;
8024 if (l == 0) {
8025 disable_hints(t);
8026 enable_hints(t);
8027 } else {
8028 t->hint_buf[l] = '\0';
8029 goto anum;
8032 } else {
8033 /* bogus */
8034 disable_hints(t);
8038 /* numerical input */
8039 if (CLEAN(e->state) == 0 &&
8040 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
8041 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
8042 snprintf(s, sizeof s, "%c", e->keyval);
8043 strlcat(t->hint_num, s, sizeof t->hint_num);
8044 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
8045 t->hint_num);
8046 num:
8047 if (errstr) {
8048 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
8049 "invalid link number\n");
8050 disable_hints(t);
8051 } else {
8052 snprintf(buf, sizeof buf,
8053 "vimprobable_update_hints(%s)",
8054 t->hint_num);
8055 t->hint_mode = XT_HINT_NUMERICAL;
8056 run_script(t, buf);
8059 /* empty the counter buffer */
8060 bzero(t->hint_buf, sizeof t->hint_buf);
8061 return (XT_CB_HANDLED);
8064 /* alphanumerical input */
8065 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
8066 e->keyval <= GDK_z) ||
8067 (CLEAN(e->state) == GDK_SHIFT_MASK &&
8068 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
8069 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
8070 e->keyval <= GDK_9) ||
8071 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
8072 (t->hint_mode != XT_HINT_NUMERICAL))))) {
8073 snprintf(s, sizeof s, "%c", e->keyval);
8074 strlcat(t->hint_buf, s, sizeof t->hint_buf);
8075 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
8076 " %s\n", t->hint_buf);
8077 anum:
8078 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
8079 run_script(t, buf);
8081 snprintf(buf, sizeof buf,
8082 "vimprobable_show_hints('%s')", t->hint_buf);
8083 t->hint_mode = XT_HINT_ALPHANUM;
8084 run_script(t, buf);
8086 /* empty the counter buffer */
8087 bzero(t->hint_num, sizeof t->hint_num);
8088 return (XT_CB_HANDLED);
8091 return (XT_CB_HANDLED);
8092 } else {
8093 /* prefix input*/
8094 snprintf(s, sizeof s, "%c", e->keyval);
8095 if (CLEAN(e->state) == 0 && isdigit(s[0]))
8096 cmd_prefix = 10 * cmd_prefix + atoi(s);
8099 return (handle_keypress(t, e, 0));
8103 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8105 hide_oops(t);
8107 /* Hide buffers, if they are visible, with escape. */
8108 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
8109 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
8110 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8111 hide_buffers(t);
8112 return (XT_CB_HANDLED);
8115 return (XT_CB_PASSTHROUGH);
8118 gboolean
8119 search_continue(struct tab *t)
8121 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
8122 gboolean rv = FALSE;
8124 if (c[0] == ':')
8125 goto done;
8126 if (strlen(c) == 1) {
8127 webkit_web_view_unmark_text_matches(t->wv);
8128 goto done;
8131 if (c[0] == '/')
8132 t->search_forward = TRUE;
8133 else if (c[0] == '?')
8134 t->search_forward = FALSE;
8135 else
8136 goto done;
8138 rv = TRUE;
8139 done:
8140 return (rv);
8143 gboolean
8144 search_cb(struct tab *t)
8146 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
8147 GdkColor color;
8149 if (search_continue(t) == FALSE)
8150 goto done;
8152 /* search */
8153 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
8154 TRUE) == FALSE) {
8155 /* not found, mark red */
8156 gdk_color_parse(XT_COLOR_RED, &color);
8157 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
8158 /* unmark and remove selection */
8159 webkit_web_view_unmark_text_matches(t->wv);
8160 /* my kingdom for a way to unselect text in webview */
8161 } else {
8162 /* found, highlight all */
8163 webkit_web_view_unmark_text_matches(t->wv);
8164 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
8165 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
8166 gdk_color_parse(XT_COLOR_WHITE, &color);
8167 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
8169 done:
8170 t->search_id = 0;
8171 return (FALSE);
8175 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8177 const gchar *c = gtk_entry_get_text(w);
8179 if (t == NULL) {
8180 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
8181 return (XT_CB_PASSTHROUGH);
8184 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
8185 e->keyval, e->state, t);
8187 if (search_continue(t) == FALSE)
8188 goto done;
8190 /* if search length is > 4 then no longer play timeout games */
8191 if (strlen(c) > 4) {
8192 if (t->search_id) {
8193 g_source_remove(t->search_id);
8194 t->search_id = 0;
8196 search_cb(t);
8197 goto done;
8200 /* reestablish a new timer if the user types fast */
8201 if (t->search_id)
8202 g_source_remove(t->search_id);
8203 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
8205 done:
8206 return (XT_CB_PASSTHROUGH);
8209 gboolean
8210 match_uri(const gchar *uri, const gchar *key) {
8211 gchar *voffset;
8212 size_t len;
8213 gboolean match = FALSE;
8215 len = strlen(key);
8217 if (!strncmp(key, uri, len))
8218 match = TRUE;
8219 else {
8220 voffset = strstr(uri, "/") + 2;
8221 if (!strncmp(key, voffset, len))
8222 match = TRUE;
8223 else if (g_str_has_prefix(voffset, "www.")) {
8224 voffset = voffset + strlen("www.");
8225 if (!strncmp(key, voffset, len))
8226 match = TRUE;
8230 return (match);
8233 gboolean
8234 match_session(const gchar *name, const gchar *key) {
8235 char *sub;
8237 sub = strcasestr(name, key);
8239 return sub == name;
8242 void
8243 cmd_getlist(int id, char *key)
8245 int i, dep, c = 0;
8246 struct history *h;
8247 struct session *s;
8249 if (id >= 0) {
8250 if (cmds[id].type & XT_URLARG) {
8251 RB_FOREACH_REVERSE(h, history_list, &hl)
8252 if (match_uri(h->uri, key)) {
8253 cmd_status.list[c] = (char *)h->uri;
8254 if (++c > 255)
8255 break;
8257 cmd_status.len = c;
8258 return;
8259 } else if (cmds[id].type & XT_SESSARG) {
8260 TAILQ_FOREACH(s, &sessions, entry)
8261 if (match_session(s->name, key)) {
8262 cmd_status.list[c] = (char *)s->name;
8263 if (++c > 255)
8264 break;
8266 cmd_status.len = c;
8267 return;
8268 } else if (cmds[id].type & XT_SETARG) {
8269 for (i = 0; i < LENGTH(rs); i++)
8270 if(!strncmp(key, rs[i].name, strlen(key)))
8271 cmd_status.list[c++] = rs[i].name;
8272 cmd_status.len = c;
8273 return;
8277 dep = (id == -1) ? 0 : cmds[id].level + 1;
8279 for (i = id + 1; i < LENGTH(cmds); i++) {
8280 if (cmds[i].level < dep)
8281 break;
8282 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8283 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8284 cmd_status.list[c++] = cmds[i].cmd;
8288 cmd_status.len = c;
8291 char *
8292 cmd_getnext(int dir)
8294 cmd_status.index += dir;
8296 if (cmd_status.index < 0)
8297 cmd_status.index = cmd_status.len - 1;
8298 else if (cmd_status.index >= cmd_status.len)
8299 cmd_status.index = 0;
8301 return cmd_status.list[cmd_status.index];
8305 cmd_tokenize(char *s, char *tokens[])
8307 int i = 0;
8308 char *tok, *last = NULL;
8309 size_t len = strlen(s);
8310 bool blank;
8312 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8313 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8314 tok = strtok_r(NULL, " ", &last), i++)
8315 tokens[i] = tok;
8317 if (blank && i < 3)
8318 tokens[i++] = "";
8320 return (i);
8323 void
8324 cmd_complete(struct tab *t, char *str, int dir)
8326 GtkEntry *w = GTK_ENTRY(t->cmd);
8327 int i, j, levels, c = 0, dep = 0, parent = -1;
8328 int matchcount = 0;
8329 char *tok, *match, *s = g_strdup(str);
8330 char *tokens[3];
8331 char res[XT_MAX_URL_LENGTH + 32] = ":";
8332 char *sc = s;
8334 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8336 /* copy prefix*/
8337 for (i = 0; isdigit(s[i]); i++)
8338 res[i + 1] = s[i];
8340 for (; isspace(s[i]); i++)
8341 res[i + 1] = s[i];
8343 s += i;
8345 levels = cmd_tokenize(s, tokens);
8347 for (i = 0; i < levels - 1; i++) {
8348 tok = tokens[i];
8349 matchcount = 0;
8350 for (j = c; j < LENGTH(cmds); j++) {
8351 if (cmds[j].level < dep)
8352 break;
8353 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8354 strlen(tok))) {
8355 matchcount++;
8356 c = j + 1;
8357 if (strlen(tok) == strlen(cmds[j].cmd)) {
8358 matchcount = 1;
8359 break;
8364 if (matchcount == 1) {
8365 strlcat(res, tok, sizeof res);
8366 strlcat(res, " ", sizeof res);
8367 dep++;
8368 } else {
8369 g_free(sc);
8370 return;
8373 parent = c - 1;
8376 if (cmd_status.index == -1)
8377 cmd_getlist(parent, tokens[i]);
8379 if (cmd_status.len > 0) {
8380 match = cmd_getnext(dir);
8381 strlcat(res, match, sizeof res);
8382 gtk_entry_set_text(w, res);
8383 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8386 g_free(sc);
8389 gboolean
8390 cmd_execute(struct tab *t, char *str)
8392 struct cmd *cmd = NULL;
8393 char *tok, *last = NULL, *s = g_strdup(str), *sc;
8394 char prefixstr[4];
8395 int j, len, c = 0, dep = 0, matchcount = 0;
8396 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8397 struct karg arg = {0, NULL, -1};
8399 sc = s;
8401 /* copy prefix*/
8402 for (j = 0; j<3 && isdigit(s[j]); j++)
8403 prefixstr[j]=s[j];
8405 prefixstr[j]='\0';
8407 s += j;
8408 while (isspace(s[0]))
8409 s++;
8411 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8412 prefix = atoi(prefixstr);
8413 else
8414 s = sc;
8416 for (tok = strtok_r(s, " ", &last); tok;
8417 tok = strtok_r(NULL, " ", &last)) {
8418 matchcount = 0;
8419 for (j = c; j < LENGTH(cmds); j++) {
8420 if (cmds[j].level < dep)
8421 break;
8422 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8423 strlen(tok);
8424 if (cmds[j].level == dep &&
8425 !strncmp(tok, cmds[j].cmd, len)) {
8426 matchcount++;
8427 c = j + 1;
8428 cmd = &cmds[j];
8429 if (len == strlen(cmds[j].cmd)) {
8430 matchcount = 1;
8431 break;
8435 if (matchcount == 1) {
8436 if (cmd->type > 0)
8437 goto execute_cmd;
8438 dep++;
8439 } else {
8440 show_oops(t, "Invalid command: %s", str);
8441 goto done;
8444 execute_cmd:
8445 if (cmd == NULL) {
8446 show_oops(t, "Empty command");
8447 goto done;
8449 arg.i = cmd->arg;
8451 if (prefix != -1)
8452 arg.precount = prefix;
8453 else if (cmd_prefix > 0)
8454 arg.precount = cmd_prefix;
8456 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8457 show_oops(t, "No prefix allowed: %s", str);
8458 goto done;
8460 if (cmd->type > 1)
8461 arg.s = last ? g_strdup(last) : g_strdup("");
8462 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8463 if (arg.s == NULL) {
8464 show_oops(t, "Invalid command");
8465 goto done;
8467 arg.precount = atoi(arg.s);
8468 if (arg.precount <= 0) {
8469 if (arg.s[0] == '0')
8470 show_oops(t, "Zero count");
8471 else
8472 show_oops(t, "Trailing characters");
8473 goto done;
8477 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8478 __func__, arg.precount, arg.s);
8480 cmd->func(t, &arg);
8482 rv = XT_CB_HANDLED;
8483 done:
8484 if (j > 0)
8485 cmd_prefix = 0;
8486 g_free(sc);
8487 if (arg.s)
8488 g_free(arg.s);
8490 return (rv);
8494 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8496 if (t == NULL) {
8497 show_oops(NULL, "entry_key_cb invalid parameters");
8498 return (XT_CB_PASSTHROUGH);
8501 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8502 e->keyval, e->state, t);
8504 hide_oops(t);
8506 if (e->keyval == GDK_Escape) {
8507 /* don't use focus_webview(t) because we want to type :cmds */
8508 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8511 return (handle_keypress(t, e, 1));
8514 struct command_entry *
8515 history_prev(struct command_list *l, struct command_entry *at)
8517 if (at == NULL)
8518 at = TAILQ_LAST(l, command_list);
8519 else {
8520 at = TAILQ_PREV(at, command_list, entry);
8521 if (at == NULL)
8522 at = TAILQ_LAST(l, command_list);
8525 return (at);
8528 struct command_entry *
8529 history_next(struct command_list *l, struct command_entry *at)
8531 if (at == NULL)
8532 at = TAILQ_FIRST(l);
8533 else {
8534 at = TAILQ_NEXT(at, entry);
8535 if (at == NULL)
8536 at = TAILQ_FIRST(l);
8539 return (at);
8543 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8545 int rv = XT_CB_HANDLED;
8546 const gchar *c = gtk_entry_get_text(w);
8548 if (t == NULL) {
8549 show_oops(NULL, "cmd_keypress_cb parameters");
8550 return (XT_CB_PASSTHROUGH);
8553 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8554 e->keyval, e->state, t);
8556 /* sanity */
8557 if (c == NULL)
8558 e->keyval = GDK_Escape;
8559 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8560 e->keyval = GDK_Escape;
8562 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8563 e->keyval != GDK_ISO_Left_Tab)
8564 cmd_status.index = -1;
8566 switch (e->keyval) {
8567 case GDK_Tab:
8568 if (c[0] == ':')
8569 cmd_complete(t, (char *)&c[1], 1);
8570 goto done;
8571 case GDK_ISO_Left_Tab:
8572 if (c[0] == ':')
8573 cmd_complete(t, (char *)&c[1], -1);
8575 goto done;
8576 case GDK_Down:
8577 if (c[0] != ':') {
8578 if ((search_at = history_next(&shl, search_at))) {
8579 search_at->line[0] = c[0];
8580 gtk_entry_set_text(w, search_at->line);
8581 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8583 } else {
8584 if ((history_at = history_prev(&chl, history_at))) {
8585 history_at->line[0] = c[0];
8586 gtk_entry_set_text(w, history_at->line);
8587 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8591 goto done;
8592 case GDK_Up:
8593 if (c[0] != ':') {
8594 if ((search_at = history_next(&shl, search_at))) {
8595 search_at->line[0] = c[0];
8596 gtk_entry_set_text(w, search_at->line);
8597 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8599 } else {
8600 if ((history_at = history_next(&chl, history_at))) {
8601 history_at->line[0] = c[0];
8602 gtk_entry_set_text(w, history_at->line);
8603 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8607 goto done;
8608 case GDK_BackSpace:
8609 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8610 break;
8611 /* FALLTHROUGH */
8612 case GDK_Escape:
8613 hide_cmd(t);
8614 focus_webview(t);
8616 /* cancel search */
8617 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8618 webkit_web_view_unmark_text_matches(t->wv);
8619 goto done;
8622 rv = XT_CB_PASSTHROUGH;
8623 done:
8624 return (rv);
8627 void
8628 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8630 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8633 void
8634 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8636 /* popup menu enabled */
8637 t->popup = 1;
8641 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8643 if (t == NULL) {
8644 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8645 return (XT_CB_PASSTHROUGH);
8648 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8649 t->tab_id, t->popup);
8651 /* if popup is enabled don't lose focus */
8652 if (t->popup) {
8653 t->popup = 0;
8654 return (XT_CB_PASSTHROUGH);
8657 hide_cmd(t);
8658 hide_oops(t);
8660 if (show_url == 0 || t->focus_wv)
8661 focus_webview(t);
8662 else
8663 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8665 return (XT_CB_PASSTHROUGH);
8668 void
8669 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8671 char *s;
8672 const gchar *c = gtk_entry_get_text(entry);
8674 if (t == NULL) {
8675 show_oops(NULL, "cmd_activate_cb invalid parameters");
8676 return;
8679 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8681 hide_cmd(t);
8683 /* sanity */
8684 if (c == NULL)
8685 goto done;
8686 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8687 goto done;
8688 if (strlen(c) < 2)
8689 goto done;
8690 s = (char *)&c[1];
8692 if (c[0] == '/' || c[0] == '?') {
8693 /* see if there is a timer pending */
8694 if (t->search_id) {
8695 g_source_remove(t->search_id);
8696 t->search_id = 0;
8697 search_cb(t);
8700 if (t->search_text) {
8701 g_free(t->search_text);
8702 t->search_text = NULL;
8705 t->search_text = g_strdup(s);
8706 if (global_search)
8707 g_free(global_search);
8708 global_search = g_strdup(s);
8709 t->search_forward = c[0] == '/';
8711 history_add(&shl, search_file, s, &search_history_count);
8712 goto done;
8715 history_add(&chl, command_file, s, &cmd_history_count);
8716 cmd_execute(t, s);
8717 done:
8718 return;
8721 void
8722 backward_cb(GtkWidget *w, struct tab *t)
8724 struct karg a;
8726 if (t == NULL) {
8727 show_oops(NULL, "backward_cb invalid parameters");
8728 return;
8731 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8733 a.i = XT_NAV_BACK;
8734 navaction(t, &a);
8737 void
8738 forward_cb(GtkWidget *w, struct tab *t)
8740 struct karg a;
8742 if (t == NULL) {
8743 show_oops(NULL, "forward_cb invalid parameters");
8744 return;
8747 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8749 a.i = XT_NAV_FORWARD;
8750 navaction(t, &a);
8753 void
8754 home_cb(GtkWidget *w, struct tab *t)
8756 if (t == NULL) {
8757 show_oops(NULL, "home_cb invalid parameters");
8758 return;
8761 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8763 load_uri(t, home);
8766 void
8767 stop_cb(GtkWidget *w, struct tab *t)
8769 WebKitWebFrame *frame;
8771 if (t == NULL) {
8772 show_oops(NULL, "stop_cb invalid parameters");
8773 return;
8776 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8778 frame = webkit_web_view_get_main_frame(t->wv);
8779 if (frame == NULL) {
8780 show_oops(t, "stop_cb: no frame");
8781 return;
8784 webkit_web_frame_stop_loading(frame);
8785 abort_favicon_download(t);
8788 void
8789 setup_webkit(struct tab *t)
8791 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8792 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8793 FALSE, (char *)NULL);
8794 else
8795 warnx("webkit does not have \"enable-dns-prefetching\" property");
8796 g_object_set(G_OBJECT(t->settings),
8797 "user-agent", t->user_agent, (char *)NULL);
8798 g_object_set(G_OBJECT(t->settings),
8799 "enable-scripts", enable_scripts, (char *)NULL);
8800 g_object_set(G_OBJECT(t->settings),
8801 "enable-plugins", enable_plugins, (char *)NULL);
8802 g_object_set(G_OBJECT(t->settings),
8803 "javascript-can-open-windows-automatically", enable_scripts,
8804 (char *)NULL);
8805 g_object_set(G_OBJECT(t->settings),
8806 "enable-html5-database", FALSE, (char *)NULL);
8807 g_object_set(G_OBJECT(t->settings),
8808 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8809 g_object_set(G_OBJECT(t->settings),
8810 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8811 g_object_set(G_OBJECT(t->settings),
8812 "spell_checking_languages", spell_check_languages, (char *)NULL);
8813 g_object_set(G_OBJECT(t->wv),
8814 "full-content-zoom", TRUE, (char *)NULL);
8816 webkit_web_view_set_settings(t->wv, t->settings);
8819 gboolean
8820 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8822 struct tab *ti, *t = NULL;
8823 gdouble view_size, value, max;
8824 gchar *position;
8826 TAILQ_FOREACH(ti, &tabs, entry)
8827 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8828 t = ti;
8829 break;
8832 if (t == NULL)
8833 return FALSE;
8835 if (adjustment == NULL)
8836 adjustment = gtk_scrolled_window_get_vadjustment(
8837 GTK_SCROLLED_WINDOW(t->browser_win));
8839 view_size = gtk_adjustment_get_page_size(adjustment);
8840 value = gtk_adjustment_get_value(adjustment);
8841 max = gtk_adjustment_get_upper(adjustment) - view_size;
8843 if (max == 0)
8844 position = g_strdup("All");
8845 else if (value == max)
8846 position = g_strdup("Bot");
8847 else if (value == 0)
8848 position = g_strdup("Top");
8849 else
8850 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8852 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8853 g_free(position);
8855 return (TRUE);
8858 GtkWidget *
8859 create_browser(struct tab *t)
8861 GtkWidget *w;
8862 gchar *strval;
8863 GtkAdjustment *adjustment;
8865 if (t == NULL) {
8866 show_oops(NULL, "create_browser invalid parameters");
8867 return (NULL);
8870 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8871 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8872 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8873 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8875 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8876 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8877 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8879 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8880 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8882 /* set defaults */
8883 t->settings = webkit_web_settings_new();
8885 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8887 if (user_agent == NULL) {
8888 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8889 (char *)NULL);
8890 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8891 g_free(strval);
8892 } else
8893 t->user_agent = g_strdup(user_agent);
8895 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8897 adjustment =
8898 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8899 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8900 G_CALLBACK(update_statusbar_position), NULL);
8902 setup_webkit(t);
8904 return (w);
8907 GtkWidget *
8908 create_window(void)
8910 GtkWidget *w;
8912 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8913 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8914 gtk_widget_set_name(w, "xxxterm");
8915 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8916 g_signal_connect(G_OBJECT(w), "delete_event",
8917 G_CALLBACK (gtk_main_quit), NULL);
8919 return (w);
8922 GtkWidget *
8923 create_kiosk_toolbar(struct tab *t)
8925 GtkWidget *toolbar = NULL, *b;
8927 b = gtk_hbox_new(FALSE, 0);
8928 toolbar = b;
8929 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8931 /* backward button */
8932 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8933 gtk_widget_set_sensitive(t->backward, FALSE);
8934 g_signal_connect(G_OBJECT(t->backward), "clicked",
8935 G_CALLBACK(backward_cb), t);
8936 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8938 /* forward button */
8939 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8940 gtk_widget_set_sensitive(t->forward, FALSE);
8941 g_signal_connect(G_OBJECT(t->forward), "clicked",
8942 G_CALLBACK(forward_cb), t);
8943 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8945 /* home button */
8946 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8947 gtk_widget_set_sensitive(t->gohome, true);
8948 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8949 G_CALLBACK(home_cb), t);
8950 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8952 /* create widgets but don't use them */
8953 t->uri_entry = gtk_entry_new();
8954 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8955 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8956 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8958 return (toolbar);
8961 GtkWidget *
8962 create_toolbar(struct tab *t)
8964 GtkWidget *toolbar = NULL, *b, *eb1;
8966 b = gtk_hbox_new(FALSE, 0);
8967 toolbar = b;
8968 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8970 /* backward button */
8971 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8972 gtk_widget_set_sensitive(t->backward, FALSE);
8973 g_signal_connect(G_OBJECT(t->backward), "clicked",
8974 G_CALLBACK(backward_cb), t);
8975 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8977 /* forward button */
8978 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8979 gtk_widget_set_sensitive(t->forward, FALSE);
8980 g_signal_connect(G_OBJECT(t->forward), "clicked",
8981 G_CALLBACK(forward_cb), t);
8982 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8983 FALSE, 0);
8985 /* stop button */
8986 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8987 gtk_widget_set_sensitive(t->stop, FALSE);
8988 g_signal_connect(G_OBJECT(t->stop), "clicked",
8989 G_CALLBACK(stop_cb), t);
8990 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8991 FALSE, 0);
8993 /* JS button */
8994 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8995 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8996 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8997 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8998 G_CALLBACK(js_toggle_cb), t);
8999 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
9001 t->uri_entry = gtk_entry_new();
9002 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
9003 G_CALLBACK(activate_uri_entry_cb), t);
9004 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
9005 G_CALLBACK(entry_key_cb), t);
9006 completion_add(t);
9007 eb1 = gtk_hbox_new(FALSE, 0);
9008 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
9009 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
9010 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
9012 /* search entry */
9013 if (search_string) {
9014 GtkWidget *eb2;
9015 t->search_entry = gtk_entry_new();
9016 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
9017 g_signal_connect(G_OBJECT(t->search_entry), "activate",
9018 G_CALLBACK(activate_search_entry_cb), t);
9019 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
9020 G_CALLBACK(entry_key_cb), t);
9021 gtk_widget_set_size_request(t->search_entry, -1, -1);
9022 eb2 = gtk_hbox_new(FALSE, 0);
9023 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
9024 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
9026 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
9029 return (toolbar);
9032 GtkWidget *
9033 create_buffers(struct tab *t)
9035 GtkCellRenderer *renderer;
9036 GtkWidget *view;
9038 view = gtk_tree_view_new();
9040 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
9042 renderer = gtk_cell_renderer_text_new();
9043 gtk_tree_view_insert_column_with_attributes
9044 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
9046 renderer = gtk_cell_renderer_text_new();
9047 gtk_tree_view_insert_column_with_attributes
9048 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
9049 (char *)NULL);
9051 gtk_tree_view_set_model
9052 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
9054 return view;
9057 void
9058 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
9059 GtkTreeViewColumn *col, struct tab *t)
9061 GtkTreeIter iter;
9062 guint id;
9064 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9066 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
9067 path)) {
9068 gtk_tree_model_get
9069 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
9070 set_current_tab(id - 1);
9073 hide_buffers(t);
9076 /* after tab reordering/creation/removal */
9077 void
9078 recalc_tabs(void)
9080 struct tab *t;
9081 int maxid = 0;
9083 TAILQ_FOREACH(t, &tabs, entry) {
9084 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
9085 if (t->tab_id > maxid)
9086 maxid = t->tab_id;
9088 gtk_widget_show(t->tab_elems.sep);
9091 TAILQ_FOREACH(t, &tabs, entry) {
9092 if (t->tab_id == maxid) {
9093 gtk_widget_hide(t->tab_elems.sep);
9094 break;
9099 /* after active tab change */
9100 void
9101 recolor_compact_tabs(void)
9103 struct tab *t;
9104 int curid = 0;
9105 GdkColor color;
9107 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9108 TAILQ_FOREACH(t, &tabs, entry)
9109 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
9110 &color);
9112 curid = gtk_notebook_get_current_page(notebook);
9113 TAILQ_FOREACH(t, &tabs, entry)
9114 if (t->tab_id == curid) {
9115 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
9116 gtk_widget_modify_fg(t->tab_elems.label,
9117 GTK_STATE_NORMAL, &color);
9118 break;
9122 void
9123 set_current_tab(int page_num)
9125 buffercmd_abort(get_current_tab());
9126 gtk_notebook_set_current_page(notebook, page_num);
9127 recolor_compact_tabs();
9131 undo_close_tab_save(struct tab *t)
9133 int m, n;
9134 const gchar *uri;
9135 struct undo *u1, *u2;
9136 GList *items;
9137 WebKitWebHistoryItem *item;
9139 if ((uri = get_uri(t)) == NULL)
9140 return (1);
9142 u1 = g_malloc0(sizeof(struct undo));
9143 u1->uri = g_strdup(uri);
9145 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9147 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
9148 n = webkit_web_back_forward_list_get_back_length(t->bfl);
9149 u1->back = n;
9151 /* forward history */
9152 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
9154 while (items) {
9155 item = items->data;
9156 u1->history = g_list_prepend(u1->history,
9157 webkit_web_history_item_copy(item));
9158 items = g_list_next(items);
9161 /* current item */
9162 if (m) {
9163 item = webkit_web_back_forward_list_get_current_item(t->bfl);
9164 u1->history = g_list_prepend(u1->history,
9165 webkit_web_history_item_copy(item));
9168 /* back history */
9169 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
9171 while (items) {
9172 item = items->data;
9173 u1->history = g_list_prepend(u1->history,
9174 webkit_web_history_item_copy(item));
9175 items = g_list_next(items);
9178 TAILQ_INSERT_HEAD(&undos, u1, entry);
9180 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
9181 u2 = TAILQ_LAST(&undos, undo_tailq);
9182 TAILQ_REMOVE(&undos, u2, entry);
9183 g_free(u2->uri);
9184 g_list_free(u2->history);
9185 g_free(u2);
9186 } else
9187 undo_count++;
9189 return (0);
9192 void
9193 delete_tab(struct tab *t)
9195 struct karg a;
9197 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
9199 if (t == NULL)
9200 return;
9203 * no need to join thread here because it won't access t on completion
9206 buffercmd_abort(t);
9207 TAILQ_REMOVE(&tabs, t, entry);
9209 /* Halt all webkit activity. */
9210 abort_favicon_download(t);
9211 webkit_web_view_stop_loading(t->wv);
9213 /* Save the tab, so we can undo the close. */
9214 undo_close_tab_save(t);
9216 if (browser_mode == XT_BM_KIOSK) {
9217 gtk_widget_destroy(t->uri_entry);
9218 gtk_widget_destroy(t->stop);
9219 gtk_widget_destroy(t->js_toggle);
9222 gtk_widget_destroy(t->tab_elems.eventbox);
9223 gtk_widget_destroy(t->vbox);
9225 /* just in case */
9226 if (t->search_id)
9227 g_source_remove(t->search_id);
9229 g_free(t->user_agent);
9230 g_free(t->stylesheet);
9231 g_free(t->tmp_uri);
9232 g_free(t);
9234 if (TAILQ_EMPTY(&tabs)) {
9235 if (browser_mode == XT_BM_KIOSK)
9236 create_new_tab(home, NULL, 1, -1);
9237 else
9238 create_new_tab(NULL, NULL, 1, -1);
9241 /* recreate session */
9242 if (session_autosave) {
9243 a.s = NULL;
9244 save_tabs(t, &a);
9247 recalc_tabs();
9248 recolor_compact_tabs();
9251 void
9252 update_statusbar_zoom(struct tab *t)
9254 gfloat zoom;
9255 char s[16] = { '\0' };
9257 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9258 if ((zoom <= 0.99 || zoom >= 1.01))
9259 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9260 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9263 void
9264 setzoom_webkit(struct tab *t, int adjust)
9266 #define XT_ZOOMPERCENT 0.04
9268 gfloat zoom;
9270 if (t == NULL) {
9271 show_oops(NULL, "setzoom_webkit invalid parameters");
9272 return;
9275 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9276 if (adjust == XT_ZOOM_IN)
9277 zoom += XT_ZOOMPERCENT;
9278 else if (adjust == XT_ZOOM_OUT)
9279 zoom -= XT_ZOOMPERCENT;
9280 else if (adjust > 0)
9281 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9282 else {
9283 show_oops(t, "setzoom_webkit invalid zoom value");
9284 return;
9287 if (zoom < XT_ZOOMPERCENT)
9288 zoom = XT_ZOOMPERCENT;
9289 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9290 update_statusbar_zoom(t);
9293 gboolean
9294 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9296 struct tab *t = (struct tab *) data;
9298 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9300 switch (event->button) {
9301 case 1:
9302 set_current_tab(t->tab_id);
9303 break;
9304 case 2:
9305 delete_tab(t);
9306 break;
9309 return TRUE;
9312 void
9313 append_tab(struct tab *t)
9315 if (t == NULL)
9316 return;
9318 TAILQ_INSERT_TAIL(&tabs, t, entry);
9319 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9322 GtkWidget *
9323 create_sbe(int width)
9325 GtkWidget *sbe;
9327 sbe = gtk_entry_new();
9328 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9329 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9330 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9331 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9332 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9333 gtk_widget_set_size_request(sbe, width, -1);
9335 return sbe;
9338 struct tab *
9339 create_new_tab(char *title, struct undo *u, int focus, int position)
9341 struct tab *t;
9342 int load = 1, id;
9343 GtkWidget *b, *bb;
9344 WebKitWebHistoryItem *item;
9345 GList *items;
9346 GdkColor color;
9347 char *p;
9348 int sbe_p = 0, sbe_b = 0,
9349 sbe_z = 0;
9351 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9353 if (tabless && !TAILQ_EMPTY(&tabs)) {
9354 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9355 return (NULL);
9358 t = g_malloc0(sizeof *t);
9360 if (title == NULL) {
9361 title = "(untitled)";
9362 load = 0;
9365 t->vbox = gtk_vbox_new(FALSE, 0);
9367 /* label + button for tab */
9368 b = gtk_hbox_new(FALSE, 0);
9369 t->tab_content = b;
9371 #if GTK_CHECK_VERSION(2, 20, 0)
9372 t->spinner = gtk_spinner_new();
9373 #endif
9374 t->label = gtk_label_new(title);
9375 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9376 gtk_widget_set_size_request(t->label, 100, 0);
9377 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9378 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9379 gtk_widget_set_size_request(b, 130, 0);
9381 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9382 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9383 #if GTK_CHECK_VERSION(2, 20, 0)
9384 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9385 #endif
9387 /* toolbar */
9388 if (browser_mode == XT_BM_KIOSK) {
9389 t->toolbar = create_kiosk_toolbar(t);
9390 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9392 } else {
9393 t->toolbar = create_toolbar(t);
9394 if (fancy_bar)
9395 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9396 FALSE, 0);
9399 /* marks */
9400 marks_clear(t);
9402 /* browser */
9403 t->browser_win = create_browser(t);
9404 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9406 /* oops message for user feedback */
9407 t->oops = gtk_entry_new();
9408 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9409 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9410 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9411 gdk_color_parse(XT_COLOR_RED, &color);
9412 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9413 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9414 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9416 /* command entry */
9417 t->cmd = gtk_entry_new();
9418 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9419 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9420 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9421 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9423 /* status bar */
9424 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9426 t->sbe.statusbar = gtk_entry_new();
9427 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9428 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9429 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9430 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9432 /* create these widgets only if specified in statusbar_elems */
9434 t->sbe.position = create_sbe(40);
9435 t->sbe.zoom = create_sbe(40);
9436 t->sbe.buffercmd = create_sbe(60);
9438 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9440 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9441 TRUE, FALSE);
9443 /* gtk widgets cannot be added to a box twice. sbe_* variables
9444 make sure of this */
9445 for (p = statusbar_elems; *p != '\0'; p++) {
9446 switch (*p) {
9447 case '|':
9449 GtkWidget *sep = gtk_vseparator_new();
9451 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9452 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9453 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9454 FALSE, FALSE, FALSE);
9455 break;
9457 case 'P':
9458 if (sbe_p) {
9459 warnx("flag \"%c\" specified more than "
9460 "once in statusbar_elems\n", *p);
9461 break;
9463 sbe_p = 1;
9464 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9465 t->sbe.position, FALSE, FALSE, FALSE);
9466 break;
9467 case 'B':
9468 if (sbe_b) {
9469 warnx("flag \"%c\" specified more than "
9470 "once in statusbar_elems\n", *p);
9471 break;
9473 sbe_b = 1;
9474 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9475 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9476 break;
9477 case 'Z':
9478 if (sbe_z) {
9479 warnx("flag \"%c\" specified more than "
9480 "once in statusbar_elems\n", *p);
9481 break;
9483 sbe_z = 1;
9484 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9485 t->sbe.zoom, FALSE, FALSE, FALSE);
9486 break;
9487 default:
9488 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9489 break;
9493 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9495 /* buffer list */
9496 t->buffers = create_buffers(t);
9497 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9499 /* xtp meaning is normal by default */
9500 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9502 /* set empty favicon */
9503 xt_icon_from_name(t, "text-html");
9505 /* and show it all */
9506 gtk_widget_show_all(b);
9507 gtk_widget_show_all(t->vbox);
9509 /* compact tab bar */
9510 t->tab_elems.label = gtk_label_new(title);
9511 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9512 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9513 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9514 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9516 t->tab_elems.eventbox = gtk_event_box_new();
9517 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9518 t->tab_elems.sep = gtk_vseparator_new();
9520 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9521 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9522 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9523 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9524 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9525 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9527 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9528 TRUE, 0);
9529 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9530 FALSE, 0);
9531 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9532 t->tab_elems.box);
9534 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9535 TRUE, 0);
9536 gtk_widget_show_all(t->tab_elems.eventbox);
9538 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9539 append_tab(t);
9540 else {
9541 id = position >= 0 ? position :
9542 gtk_notebook_get_current_page(notebook) + 1;
9543 if (id > gtk_notebook_get_n_pages(notebook))
9544 append_tab(t);
9545 else {
9546 TAILQ_INSERT_TAIL(&tabs, t, entry);
9547 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9548 gtk_box_reorder_child(GTK_BOX(tab_bar),
9549 t->tab_elems.eventbox, id);
9550 recalc_tabs();
9554 #if GTK_CHECK_VERSION(2, 20, 0)
9555 /* turn spinner off if we are a new tab without uri */
9556 if (!load) {
9557 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9558 gtk_widget_hide(t->spinner);
9560 #endif
9561 /* make notebook tabs reorderable */
9562 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9564 /* compact tabs clickable */
9565 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9566 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9568 g_object_connect(G_OBJECT(t->cmd),
9569 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9570 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9571 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9572 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9573 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9574 (char *)NULL);
9576 /* reuse wv_button_cb to hide oops */
9577 g_object_connect(G_OBJECT(t->oops),
9578 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9579 (char *)NULL);
9581 g_signal_connect(t->buffers,
9582 "row-activated", G_CALLBACK(row_activated_cb), t);
9583 g_object_connect(G_OBJECT(t->buffers),
9584 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9586 g_object_connect(G_OBJECT(t->wv),
9587 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9588 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9589 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9590 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9591 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9592 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9593 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9594 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9595 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9596 "signal::event", G_CALLBACK(webview_event_cb), t,
9597 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9598 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9599 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9600 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9601 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9602 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9603 (char *)NULL);
9604 g_signal_connect(t->wv,
9605 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9606 g_signal_connect(t->wv,
9607 "notify::title", G_CALLBACK(notify_title_cb), t);
9609 /* hijack the unused keys as if we were the browser */
9610 g_object_connect(G_OBJECT(t->toolbar),
9611 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9612 (char *)NULL);
9614 g_signal_connect(G_OBJECT(bb), "button_press_event",
9615 G_CALLBACK(tab_close_cb), t);
9617 /* setup history */
9618 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9619 /* restore the tab's history */
9620 if (u && u->history) {
9621 items = u->history;
9622 while (items) {
9623 item = items->data;
9624 webkit_web_back_forward_list_add_item(t->bfl, item);
9625 items = g_list_next(items);
9628 item = g_list_nth_data(u->history, u->back);
9629 if (item)
9630 webkit_web_view_go_to_back_forward_item(t->wv, item);
9632 g_list_free(items);
9633 g_list_free(u->history);
9634 } else
9635 webkit_web_back_forward_list_clear(t->bfl);
9637 /* hide stuff */
9638 hide_cmd(t);
9639 hide_oops(t);
9640 hide_buffers(t);
9641 url_set_visibility();
9642 statusbar_set_visibility();
9644 if (focus) {
9645 set_current_tab(t->tab_id);
9646 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9647 t->tab_id);
9649 if (load) {
9650 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9651 load_uri(t, title);
9652 } else {
9653 if (show_url == 1)
9654 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9655 else
9656 focus_webview(t);
9658 } else if (load)
9659 load_uri(t, title);
9661 recolor_compact_tabs();
9662 setzoom_webkit(t, XT_ZOOM_NORMAL);
9663 return (t);
9666 void
9667 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9668 gpointer *udata)
9670 struct tab *t;
9671 const gchar *uri;
9673 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9675 if (gtk_notebook_get_current_page(notebook) == -1)
9676 recalc_tabs();
9678 TAILQ_FOREACH(t, &tabs, entry) {
9679 if (t->tab_id == pn) {
9680 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9681 "%d\n", pn);
9683 uri = get_title(t, TRUE);
9684 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9686 hide_cmd(t);
9687 hide_oops(t);
9689 if (t->focus_wv) {
9690 /* can't use focus_webview here */
9691 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9697 void
9698 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9699 gpointer *udata)
9701 struct tab *t = NULL, *tt;
9703 recalc_tabs();
9705 TAILQ_FOREACH(tt, &tabs, entry)
9706 if (tt->tab_id == pn) {
9707 t = tt;
9708 break;
9710 if (t == NULL)
9711 return;
9712 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9714 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9715 t->tab_id);
9718 void
9719 menuitem_response(struct tab *t)
9721 gtk_notebook_set_current_page(notebook, t->tab_id);
9724 gboolean
9725 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9727 GtkWidget *menu, *menu_items;
9728 GdkEventButton *bevent;
9729 const gchar *uri;
9730 struct tab *ti;
9732 if (event->type == GDK_BUTTON_PRESS) {
9733 bevent = (GdkEventButton *) event;
9734 menu = gtk_menu_new();
9736 TAILQ_FOREACH(ti, &tabs, entry) {
9737 if ((uri = get_uri(ti)) == NULL)
9738 /* XXX make sure there is something to print */
9739 /* XXX add gui pages in here to look purdy */
9740 uri = "(untitled)";
9741 menu_items = gtk_menu_item_new_with_label(uri);
9742 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9743 gtk_widget_show(menu_items);
9745 g_signal_connect_swapped((menu_items),
9746 "activate", G_CALLBACK(menuitem_response),
9747 (gpointer)ti);
9750 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9751 bevent->button, bevent->time);
9753 /* unref object so it'll free itself when popped down */
9754 #if !GTK_CHECK_VERSION(3, 0, 0)
9755 /* XXX does not need unref with gtk+3? */
9756 g_object_ref_sink(menu);
9757 g_object_unref(menu);
9758 #endif
9760 return (TRUE /* eat event */);
9763 return (FALSE /* propagate */);
9767 icon_size_map(int icon_size)
9769 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9770 icon_size > GTK_ICON_SIZE_DIALOG)
9771 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9773 return (icon_size);
9776 GtkWidget *
9777 create_button(char *name, char *stockid, int size)
9779 GtkWidget *button, *image;
9780 gchar *rcstring;
9781 int gtk_icon_size;
9783 rcstring = g_strdup_printf(
9784 "style \"%s-style\"\n"
9785 "{\n"
9786 " GtkWidget::focus-padding = 0\n"
9787 " GtkWidget::focus-line-width = 0\n"
9788 " xthickness = 0\n"
9789 " ythickness = 0\n"
9790 "}\n"
9791 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9792 gtk_rc_parse_string(rcstring);
9793 g_free(rcstring);
9794 button = gtk_button_new();
9795 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9796 gtk_icon_size = icon_size_map(size ? size : icon_size);
9798 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9799 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9800 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9801 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9802 gtk_widget_set_name(button, name);
9803 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9805 return (button);
9808 void
9809 button_set_stockid(GtkWidget *button, char *stockid)
9811 GtkWidget *image;
9813 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9814 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9815 gtk_button_set_image(GTK_BUTTON(button), image);
9818 void
9819 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9821 gchar *p = NULL;
9822 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9823 gint len;
9825 if (xterm_workaround == 0)
9826 return;
9829 * xterm doesn't play nice with clipboards because it clears the
9830 * primary when clicked. We rely on primary being set to properly
9831 * handle middle mouse button clicks (paste). So when someone clears
9832 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9833 * other application behavior (as in DON'T clear primary).
9836 p = gtk_clipboard_wait_for_text(primary);
9837 if (p == NULL) {
9838 if (gdk_property_get(gdk_get_default_root_window(),
9839 atom,
9840 gdk_atom_intern("STRING", FALSE),
9842 1024 * 1024 /* picked out of my butt */,
9843 FALSE,
9844 NULL,
9845 NULL,
9846 &len,
9847 (guchar **)&p)) {
9848 /* yes sir, we need to NUL the string */
9849 p[len] = '\0';
9850 gtk_clipboard_set_text(primary, p, -1);
9854 if (p)
9855 g_free(p);
9858 void
9859 create_canvas(void)
9861 GtkWidget *vbox;
9862 GList *l = NULL;
9863 GdkPixbuf *pb;
9864 char file[PATH_MAX];
9865 int i;
9867 vbox = gtk_vbox_new(FALSE, 0);
9868 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9869 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9870 #if !GTK_CHECK_VERSION(3, 0, 0)
9871 /* XXX seems to be needed with gtk+2 */
9872 gtk_notebook_set_tab_hborder(notebook, 0);
9873 gtk_notebook_set_tab_vborder(notebook, 0);
9874 #endif
9875 gtk_notebook_set_scrollable(notebook, TRUE);
9876 gtk_notebook_set_show_border(notebook, FALSE);
9877 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9879 abtn = gtk_button_new();
9880 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9881 gtk_widget_set_size_request(arrow, -1, -1);
9882 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9883 gtk_widget_set_size_request(abtn, -1, 20);
9885 #if GTK_CHECK_VERSION(2, 20, 0)
9886 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9887 #endif
9888 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9890 /* compact tab bar */
9891 tab_bar = gtk_hbox_new(TRUE, 0);
9893 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9894 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9895 gtk_widget_set_size_request(vbox, -1, -1);
9897 g_object_connect(G_OBJECT(notebook),
9898 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9899 (char *)NULL);
9900 g_object_connect(G_OBJECT(notebook),
9901 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9902 NULL, (char *)NULL);
9903 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9904 G_CALLBACK(arrow_cb), NULL);
9906 main_window = create_window();
9907 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9909 /* icons */
9910 for (i = 0; i < LENGTH(icons); i++) {
9911 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9912 pb = gdk_pixbuf_new_from_file(file, NULL);
9913 l = g_list_append(l, pb);
9915 gtk_window_set_default_icon_list(l);
9917 /* clipboard work around */
9918 if (xterm_workaround)
9919 g_signal_connect(
9920 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9921 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9923 gtk_widget_show_all(abtn);
9924 gtk_widget_show_all(main_window);
9925 notebook_tab_set_visibility();
9928 void
9929 set_hook(void **hook, char *name)
9931 if (hook == NULL)
9932 errx(1, "set_hook");
9934 if (*hook == NULL) {
9935 *hook = dlsym(RTLD_NEXT, name);
9936 if (*hook == NULL)
9937 errx(1, "can't hook %s", name);
9941 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9942 gboolean
9943 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9945 g_return_val_if_fail(cookie1, FALSE);
9946 g_return_val_if_fail(cookie2, FALSE);
9948 return (!strcmp (cookie1->name, cookie2->name) &&
9949 !strcmp (cookie1->value, cookie2->value) &&
9950 !strcmp (cookie1->path, cookie2->path) &&
9951 !strcmp (cookie1->domain, cookie2->domain));
9954 void
9955 transfer_cookies(void)
9957 GSList *cf;
9958 SoupCookie *sc, *pc;
9960 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9962 for (;cf; cf = cf->next) {
9963 pc = cf->data;
9964 sc = soup_cookie_copy(pc);
9965 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9968 soup_cookies_free(cf);
9971 void
9972 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9974 GSList *cf;
9975 SoupCookie *ci;
9977 print_cookie("soup_cookie_jar_delete_cookie", c);
9979 if (cookies_enabled == 0)
9980 return;
9982 if (jar == NULL || c == NULL)
9983 return;
9985 /* find and remove from persistent jar */
9986 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9988 for (;cf; cf = cf->next) {
9989 ci = cf->data;
9990 if (soup_cookie_equal(ci, c)) {
9991 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9992 break;
9996 soup_cookies_free(cf);
9998 /* delete from session jar */
9999 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
10002 void
10003 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
10005 struct domain *d = NULL;
10006 SoupCookie *c;
10007 FILE *r_cookie_f;
10009 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
10010 jar, p_cookiejar, s_cookiejar);
10012 if (cookies_enabled == 0)
10013 return;
10015 /* see if we are up and running */
10016 if (p_cookiejar == NULL) {
10017 _soup_cookie_jar_add_cookie(jar, cookie);
10018 return;
10020 /* disallow p_cookiejar adds, shouldn't happen */
10021 if (jar == p_cookiejar)
10022 return;
10024 /* sanity */
10025 if (jar == NULL || cookie == NULL)
10026 return;
10028 if (enable_cookie_whitelist &&
10029 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
10030 blocked_cookies++;
10031 DNPRINTF(XT_D_COOKIE,
10032 "soup_cookie_jar_add_cookie: reject %s\n",
10033 cookie->domain);
10034 if (save_rejected_cookies) {
10035 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
10036 show_oops(NULL, "can't open reject cookie file");
10037 return;
10039 fseek(r_cookie_f, 0, SEEK_END);
10040 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
10041 cookie->http_only ? "#HttpOnly_" : "",
10042 cookie->domain,
10043 *cookie->domain == '.' ? "TRUE" : "FALSE",
10044 cookie->path,
10045 cookie->secure ? "TRUE" : "FALSE",
10046 cookie->expires ?
10047 (gulong)soup_date_to_time_t(cookie->expires) :
10049 cookie->name,
10050 cookie->value);
10051 fflush(r_cookie_f);
10052 fclose(r_cookie_f);
10054 if (!allow_volatile_cookies)
10055 return;
10058 if (cookie->expires == NULL && session_timeout) {
10059 soup_cookie_set_expires(cookie,
10060 soup_date_new_from_now(session_timeout));
10061 print_cookie("modified add cookie", cookie);
10064 /* see if we are white listed for persistence */
10065 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
10066 /* add to persistent jar */
10067 c = soup_cookie_copy(cookie);
10068 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
10069 _soup_cookie_jar_add_cookie(p_cookiejar, c);
10072 /* add to session jar */
10073 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
10074 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
10077 void
10078 setup_cookies(void)
10080 char file[PATH_MAX];
10082 set_hook((void *)&_soup_cookie_jar_add_cookie,
10083 "soup_cookie_jar_add_cookie");
10084 set_hook((void *)&_soup_cookie_jar_delete_cookie,
10085 "soup_cookie_jar_delete_cookie");
10087 if (cookies_enabled == 0)
10088 return;
10091 * the following code is intricate due to overriding several libsoup
10092 * functions.
10093 * do not alter order of these operations.
10096 /* rejected cookies */
10097 if (save_rejected_cookies)
10098 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
10099 XT_REJECT_FILE);
10101 /* persistent cookies */
10102 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
10103 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
10105 /* session cookies */
10106 s_cookiejar = soup_cookie_jar_new();
10107 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
10108 cookie_policy, (void *)NULL);
10109 transfer_cookies();
10111 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
10114 void
10115 setup_proxy(char *uri)
10117 if (proxy_uri) {
10118 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
10119 soup_uri_free(proxy_uri);
10120 proxy_uri = NULL;
10122 if (http_proxy) {
10123 if (http_proxy != uri) {
10124 g_free(http_proxy);
10125 http_proxy = NULL;
10129 if (uri) {
10130 http_proxy = g_strdup(uri);
10131 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
10132 proxy_uri = soup_uri_new(http_proxy);
10133 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
10134 g_object_set(session, "proxy-uri", proxy_uri,
10135 (char *)NULL);
10140 set_http_proxy(char *proxy)
10142 SoupURI *uri;
10144 if (proxy == NULL)
10145 return (1);
10147 /* see if we need to clear it instead */
10148 if (strlen(proxy) == 0) {
10149 setup_proxy(NULL);
10150 return (0);
10153 uri = soup_uri_new(proxy);
10154 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
10155 return (1);
10157 setup_proxy(proxy);
10159 soup_uri_free(uri);
10161 return (0);
10165 send_cmd_to_socket(char *cmd)
10167 int s, len, rv = 1;
10168 struct sockaddr_un sa;
10170 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10171 warnx("%s: socket", __func__);
10172 return (rv);
10175 sa.sun_family = AF_UNIX;
10176 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10177 work_dir, XT_SOCKET_FILE);
10178 len = SUN_LEN(&sa);
10180 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10181 warnx("%s: connect", __func__);
10182 goto done;
10185 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
10186 warnx("%s: send", __func__);
10187 goto done;
10190 rv = 0;
10191 done:
10192 close(s);
10193 return (rv);
10196 gboolean
10197 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
10199 int s, n;
10200 char str[XT_MAX_URL_LENGTH];
10201 socklen_t t = sizeof(struct sockaddr_un);
10202 struct sockaddr_un sa;
10203 struct passwd *p;
10204 uid_t uid;
10205 gid_t gid;
10206 struct tab *tt;
10207 gint fd = g_io_channel_unix_get_fd(source);
10209 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
10210 warn("accept");
10211 return (FALSE);
10214 if (getpeereid(s, &uid, &gid) == -1) {
10215 warn("getpeereid");
10216 return (FALSE);
10218 if (uid != getuid() || gid != getgid()) {
10219 warnx("unauthorized user");
10220 return (FALSE);
10223 p = getpwuid(uid);
10224 if (p == NULL) {
10225 warnx("not a valid user");
10226 return (FALSE);
10229 n = recv(s, str, sizeof(str), 0);
10230 if (n <= 0)
10231 return (TRUE);
10233 tt = TAILQ_LAST(&tabs, tab_list);
10234 cmd_execute(tt, str);
10235 return (TRUE);
10239 is_running(void)
10241 int s, len, rv = 1;
10242 struct sockaddr_un sa;
10244 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10245 warn("is_running: socket");
10246 return (-1);
10249 sa.sun_family = AF_UNIX;
10250 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10251 work_dir, XT_SOCKET_FILE);
10252 len = SUN_LEN(&sa);
10254 /* connect to see if there is a listener */
10255 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10256 rv = 0; /* not running */
10257 else
10258 rv = 1; /* already running */
10260 close(s);
10262 return (rv);
10266 build_socket(void)
10268 int s, len;
10269 struct sockaddr_un sa;
10271 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10272 warn("build_socket: socket");
10273 return (-1);
10276 sa.sun_family = AF_UNIX;
10277 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10278 work_dir, XT_SOCKET_FILE);
10279 len = SUN_LEN(&sa);
10281 /* connect to see if there is a listener */
10282 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10283 /* no listener so we will */
10284 unlink(sa.sun_path);
10286 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10287 warn("build_socket: bind");
10288 goto done;
10291 if (listen(s, 1) == -1) {
10292 warn("build_socket: listen");
10293 goto done;
10296 return (s);
10299 done:
10300 close(s);
10301 return (-1);
10304 gboolean
10305 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10306 GtkTreeIter *iter, struct tab *t)
10308 gchar *value;
10310 gtk_tree_model_get(model, iter, 0, &value, -1);
10311 load_uri(t, value);
10312 g_free(value);
10314 return (FALSE);
10317 gboolean
10318 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10319 GtkTreeIter *iter, struct tab *t)
10321 gchar *value;
10323 gtk_tree_model_get(model, iter, 0, &value, -1);
10324 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10325 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10326 g_free(value);
10328 return (TRUE);
10331 void
10332 completion_add_uri(const gchar *uri)
10334 GtkTreeIter iter;
10336 /* add uri to list_store */
10337 gtk_list_store_append(completion_model, &iter);
10338 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10341 gboolean
10342 completion_match(GtkEntryCompletion *completion, const gchar *key,
10343 GtkTreeIter *iter, gpointer user_data)
10345 gchar *value;
10346 gboolean match = FALSE;
10348 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10349 -1);
10351 if (value == NULL)
10352 return FALSE;
10354 match = match_uri(value, key);
10356 g_free(value);
10357 return (match);
10360 void
10361 completion_add(struct tab *t)
10363 /* enable completion for tab */
10364 t->completion = gtk_entry_completion_new();
10365 gtk_entry_completion_set_text_column(t->completion, 0);
10366 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10367 gtk_entry_completion_set_model(t->completion,
10368 GTK_TREE_MODEL(completion_model));
10369 gtk_entry_completion_set_match_func(t->completion, completion_match,
10370 NULL, NULL);
10371 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10372 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10373 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10374 G_CALLBACK(completion_select_cb), t);
10375 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10376 G_CALLBACK(completion_hover_cb), t);
10379 void
10380 xxx_dir(char *dir)
10382 struct stat sb;
10384 if (stat(dir, &sb)) {
10385 if (mkdir(dir, S_IRWXU) == -1)
10386 err(1, "mkdir %s", dir);
10387 if (stat(dir, &sb))
10388 err(1, "stat %s", dir);
10390 if (S_ISDIR(sb.st_mode) == 0)
10391 errx(1, "%s not a dir", dir);
10392 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10393 warnx("fixing invalid permissions on %s", dir);
10394 if (chmod(dir, S_IRWXU) == -1)
10395 err(1, "chmod %s", dir);
10399 void
10400 usage(void)
10402 fprintf(stderr,
10403 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10404 exit(0);
10407 GStaticRecMutex my_gdk_mtx = G_STATIC_REC_MUTEX_INIT;
10408 volatile int mtx_depth;
10409 int mtx_complain;
10412 * The linux flash plugin violates the gdk locking mechanism.
10413 * Work around the issue by using a recursive mutex with some match applied
10414 * to see if we hit a buggy condition.
10416 * The following code is painful so just don't read it. It really doesn't
10417 * make much sense but seems to work.
10419 void
10420 mtx_lock(void)
10422 g_static_rec_mutex_lock(&my_gdk_mtx);
10423 mtx_depth++;
10425 if (mtx_depth <= 0) {
10426 /* should not happen */
10427 show_oops(NULL, "negative mutex locking bug, trying to "
10428 "correct");
10429 fprintf(stderr, "negative mutex locking bug, trying to "
10430 "correct\n");
10431 g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10432 g_static_rec_mutex_lock(&my_gdk_mtx);
10433 mtx_depth = 1;
10434 return;
10437 if (mtx_depth != 1) {
10438 /* decrease mutext depth to 1 */
10439 do {
10440 g_static_rec_mutex_unlock(&my_gdk_mtx);
10441 mtx_depth--;
10442 } while (mtx_depth > 1);
10446 void
10447 mtx_unlock(void)
10449 guint x;
10451 /* if mutex depth isn't 1 then something went bad */
10452 if (mtx_depth != 1) {
10453 x = g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10454 if (x != 1) {
10455 /* should not happen */
10456 show_oops(NULL, "mutex unlocking bug, trying to "
10457 "correct");
10458 fprintf(stderr, "mutex unlocking bug, trying to "
10459 "correct\n");
10461 mtx_depth = 0;
10462 if (mtx_complain == 0) {
10463 show_oops(NULL, "buggy mutex implementation detected, "
10464 "work around implemented");
10465 fprintf(stderr, "buggy mutex implementation detected, "
10466 "work around implemented");
10467 mtx_complain = 1;
10469 return;
10472 mtx_depth--;
10473 g_static_rec_mutex_unlock(&my_gdk_mtx);
10477 main(int argc, char *argv[])
10479 struct stat sb;
10480 int c, s, optn = 0, opte = 0, focus = 1;
10481 char conf[PATH_MAX] = { '\0' };
10482 char file[PATH_MAX];
10483 char *env_proxy = NULL;
10484 char *cmd = NULL;
10485 FILE *f = NULL;
10486 struct karg a;
10487 struct sigaction sact;
10488 GIOChannel *channel;
10489 struct rlimit rlp;
10491 start_argv = argv;
10493 /* prepare gtk */
10494 #ifdef USE_THREADS
10495 g_thread_init(NULL);
10496 gdk_threads_set_lock_functions(mtx_lock, mtx_unlock);
10497 gdk_threads_init();
10498 gdk_threads_enter();
10500 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
10501 #endif
10502 gtk_init(&argc, &argv);
10504 gnutls_global_init();
10506 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10508 RB_INIT(&hl);
10509 RB_INIT(&js_wl);
10510 RB_INIT(&pl_wl);
10511 RB_INIT(&downloads);
10513 TAILQ_INIT(&sessions);
10514 TAILQ_INIT(&tabs);
10515 TAILQ_INIT(&mtl);
10516 TAILQ_INIT(&aliases);
10517 TAILQ_INIT(&undos);
10518 TAILQ_INIT(&kbl);
10519 TAILQ_INIT(&spl);
10520 TAILQ_INIT(&chl);
10521 TAILQ_INIT(&shl);
10523 /* fiddle with ulimits */
10524 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10525 warn("getrlimit");
10526 else {
10527 /* just use them all */
10528 rlp.rlim_cur = rlp.rlim_max;
10529 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10530 warn("setrlimit");
10531 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10532 warn("getrlimit");
10533 else if (rlp.rlim_cur <= 256)
10534 startpage_add("%s requires at least 256 file "
10535 "descriptors, currently it has up to %d available",
10536 __progname, rlp.rlim_cur);
10539 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10540 switch (c) {
10541 case 'S':
10542 show_url = 0;
10543 break;
10544 case 'T':
10545 show_tabs = 0;
10546 break;
10547 case 'V':
10548 errx(0 , "Version: %s", version);
10549 break;
10550 case 'f':
10551 strlcpy(conf, optarg, sizeof(conf));
10552 break;
10553 case 's':
10554 strlcpy(named_session, optarg, sizeof(named_session));
10555 break;
10556 case 't':
10557 tabless = 1;
10558 break;
10559 case 'n':
10560 optn = 1;
10561 break;
10562 case 'e':
10563 opte = 1;
10564 break;
10565 default:
10566 usage();
10567 /* NOTREACHED */
10570 argc -= optind;
10571 argv += optind;
10573 init_keybindings();
10575 /* generate session keys for xtp pages */
10576 generate_xtp_session_key(&dl_session_key);
10577 generate_xtp_session_key(&hl_session_key);
10578 generate_xtp_session_key(&cl_session_key);
10579 generate_xtp_session_key(&fl_session_key);
10581 /* signals */
10582 bzero(&sact, sizeof(sact));
10583 sigemptyset(&sact.sa_mask);
10584 sact.sa_handler = sigchild;
10585 sact.sa_flags = SA_NOCLDSTOP;
10586 sigaction(SIGCHLD, &sact, NULL);
10588 /* set download dir */
10589 pwd = getpwuid(getuid());
10590 if (pwd == NULL)
10591 errx(1, "invalid user %d", getuid());
10592 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10594 /* compile buffer command regexes */
10595 buffercmd_init();
10597 /* set default string settings */
10598 home = g_strdup("https://www.cyphertite.com");
10599 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10600 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10601 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10602 cmd_font_name = g_strdup("monospace normal 9");
10603 oops_font_name = g_strdup("monospace normal 9");
10604 statusbar_font_name = g_strdup("monospace normal 9");
10605 tabbar_font_name = g_strdup("monospace normal 9");
10606 statusbar_elems = g_strdup("BP");
10607 encoding = g_strdup("ISO-8859-1");
10609 /* read config file */
10610 if (strlen(conf) == 0)
10611 snprintf(conf, sizeof conf, "%s/.%s",
10612 pwd->pw_dir, XT_CONF_FILE);
10613 config_parse(conf, 0);
10615 /* init fonts */
10616 cmd_font = pango_font_description_from_string(cmd_font_name);
10617 oops_font = pango_font_description_from_string(oops_font_name);
10618 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10619 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10621 /* working directory */
10622 if (strlen(work_dir) == 0)
10623 snprintf(work_dir, sizeof work_dir, "%s/%s",
10624 pwd->pw_dir, XT_DIR);
10625 xxx_dir(work_dir);
10627 /* icon cache dir */
10628 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10629 xxx_dir(cache_dir);
10631 /* certs dir */
10632 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10633 xxx_dir(certs_dir);
10635 /* sessions dir */
10636 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10637 work_dir, XT_SESSIONS_DIR);
10638 xxx_dir(sessions_dir);
10640 /* runtime settings that can override config file */
10641 if (runtime_settings[0] != '\0')
10642 config_parse(runtime_settings, 1);
10644 /* download dir */
10645 if (!strcmp(download_dir, pwd->pw_dir))
10646 strlcat(download_dir, "/downloads", sizeof download_dir);
10647 xxx_dir(download_dir);
10649 /* favorites file */
10650 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10651 if (stat(file, &sb)) {
10652 warnx("favorites file doesn't exist, creating it");
10653 if ((f = fopen(file, "w")) == NULL)
10654 err(1, "favorites");
10655 fclose(f);
10658 /* quickmarks file */
10659 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10660 if (stat(file, &sb)) {
10661 warnx("quickmarks file doesn't exist, creating it");
10662 if ((f = fopen(file, "w")) == NULL)
10663 err(1, "quickmarks");
10664 fclose(f);
10667 /* search history */
10668 if (history_autosave) {
10669 snprintf(search_file, sizeof search_file, "%s/%s",
10670 work_dir, XT_SEARCH_FILE);
10671 if (stat(search_file, &sb)) {
10672 warnx("search history file doesn't exist, creating it");
10673 if ((f = fopen(search_file, "w")) == NULL)
10674 err(1, "search_history");
10675 fclose(f);
10677 history_read(&shl, search_file, &search_history_count);
10680 /* command history */
10681 if (history_autosave) {
10682 snprintf(command_file, sizeof command_file, "%s/%s",
10683 work_dir, XT_COMMAND_FILE);
10684 if (stat(command_file, &sb)) {
10685 warnx("command history file doesn't exist, creating it");
10686 if ((f = fopen(command_file, "w")) == NULL)
10687 err(1, "command_history");
10688 fclose(f);
10690 history_read(&chl, command_file, &cmd_history_count);
10693 /* cookies */
10694 session = webkit_get_default_session();
10695 setup_cookies();
10697 /* certs */
10698 if (ssl_ca_file) {
10699 if (stat(ssl_ca_file, &sb)) {
10700 warnx("no CA file: %s", ssl_ca_file);
10701 g_free(ssl_ca_file);
10702 ssl_ca_file = NULL;
10703 } else
10704 g_object_set(session,
10705 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10706 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10707 (void *)NULL);
10710 /* guess_search regex */
10711 if (url_regex == NULL)
10712 url_regex = g_strdup(XT_URL_REGEX);
10713 if (url_regex)
10714 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10715 startpage_add("invalid url regex %s", url_regex);
10717 /* proxy */
10718 env_proxy = getenv("http_proxy");
10719 if (env_proxy)
10720 setup_proxy(env_proxy);
10721 else
10722 setup_proxy(http_proxy);
10724 if (opte) {
10725 send_cmd_to_socket(argv[0]);
10726 exit(0);
10729 /* set some connection parameters */
10730 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10731 g_object_set(session, "max-conns-per-host", max_host_connections,
10732 (char *)NULL);
10734 /* see if there is already an xxxterm running */
10735 if (single_instance && is_running()) {
10736 optn = 1;
10737 warnx("already running");
10740 if (optn) {
10741 while (argc) {
10742 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10743 send_cmd_to_socket(cmd);
10744 if (cmd)
10745 g_free(cmd);
10747 argc--;
10748 argv++;
10750 exit(0);
10753 /* uri completion */
10754 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10756 /* buffers */
10757 buffers_store = gtk_list_store_new
10758 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10760 qmarks_load();
10762 /* go graphical */
10763 create_canvas();
10764 notebook_tab_set_visibility();
10766 if (save_global_history)
10767 restore_global_history();
10769 /* restore session list */
10770 restore_sessions_list();
10772 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10773 restore_saved_tabs();
10774 else {
10775 a.s = named_session;
10776 a.i = XT_SES_DONOTHING;
10777 open_tabs(NULL, &a);
10780 /* see if we have an exception */
10781 if (!TAILQ_EMPTY(&spl)) {
10782 create_new_tab("about:startpage", NULL, focus, -1);
10783 focus = 0;
10786 while (argc) {
10787 create_new_tab(argv[0], NULL, focus, -1);
10788 focus = 0;
10790 argc--;
10791 argv++;
10794 if (TAILQ_EMPTY(&tabs))
10795 create_new_tab(home, NULL, 1, -1);
10797 if (enable_socket)
10798 if ((s = build_socket()) != -1) {
10799 channel = g_io_channel_unix_new(s);
10800 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10803 gtk_main();
10805 #ifdef USE_THREADS
10806 gdk_threads_leave();
10807 g_static_rec_mutex_unlock_full(&my_gdk_mtx); /* just in case */
10808 #endif
10810 gnutls_global_deinit();
10812 if (url_regex)
10813 regfree(&url_re);
10815 return (0);