don't crash when toggling mode on page
[xxxterm.git] / xxxterm.c
blob7ba10748a796278443462e752621dd46b6d24c46
1 /*
2 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
3 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
4 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
5 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
6 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
7 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * create privacy browsing
25 * - encrypted local data
28 #include <ctype.h>
29 #include <dlfcn.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <libgen.h>
33 #include <pwd.h>
34 #include <regex.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <dirent.h>
42 #include <sys/types.h>
43 #include <sys/wait.h>
44 #if defined(__linux__)
45 #include "linux/util.h"
46 #include "linux/tree.h"
47 #include <bsd/stdlib.h>
48 # if !defined(sane_libbsd_headers)
49 void arc4random_buf(void *, size_t);
50 # endif
51 #elif defined(__FreeBSD__)
52 #include <libutil.h>
53 #include "freebsd/util.h"
54 #include <sys/tree.h>
55 #else /* OpenBSD */
56 #include <util.h>
57 #include <sys/tree.h>
58 #endif
59 #include <sys/queue.h>
60 #include <sys/resource.h>
61 #include <sys/socket.h>
62 #include <sys/stat.h>
63 #include <sys/time.h>
64 #include <sys/un.h>
66 #include <gtk/gtk.h>
67 #include <gdk/gdkkeysyms.h>
69 #if GTK_CHECK_VERSION(3,0,0)
70 /* we still use GDK_* instead of GDK_KEY_* */
71 #include <gdk/gdkkeysyms-compat.h>
72 #endif
74 #include <webkit/webkit.h>
75 #include <libsoup/soup.h>
76 #include <JavaScriptCore/JavaScript.h>
77 #include <gnutls/gnutls.h>
78 #include <gnutls/x509.h>
80 #include "version.h"
81 #include "javascript.h"
83 /* comment if you don't want to use threads */
84 #define USE_THREADS
86 #ifdef USE_THREADS
87 #include <gcrypt.h>
88 #include <pthread.h>
89 GCRY_THREAD_OPTION_PTHREAD_IMPL;
90 #endif
94 javascript.h borrowed from vimprobable2 under the following license:
96 Copyright (c) 2009 Leon Winter
97 Copyright (c) 2009 Hannes Schueller
98 Copyright (c) 2009 Matto Fransen
100 Permission is hereby granted, free of charge, to any person obtaining a copy
101 of this software and associated documentation files (the "Software"), to deal
102 in the Software without restriction, including without limitation the rights
103 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
104 copies of the Software, and to permit persons to whom the Software is
105 furnished to do so, subject to the following conditions:
107 The above copyright notice and this permission notice shall be included in
108 all copies or substantial portions of the Software.
110 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
111 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
112 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
113 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
114 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
115 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
116 THE SOFTWARE.
119 static char *version = XXXTERM_VERSION;
121 /* hooked functions */
122 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
123 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
124 SoupCookie *);
126 /*#define XT_DEBUG*/
127 #ifdef XT_DEBUG
128 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
129 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
130 #define XT_D_MOVE 0x0001
131 #define XT_D_KEY 0x0002
132 #define XT_D_TAB 0x0004
133 #define XT_D_URL 0x0008
134 #define XT_D_CMD 0x0010
135 #define XT_D_NAV 0x0020
136 #define XT_D_DOWNLOAD 0x0040
137 #define XT_D_CONFIG 0x0080
138 #define XT_D_JS 0x0100
139 #define XT_D_FAVORITE 0x0200
140 #define XT_D_PRINTING 0x0400
141 #define XT_D_COOKIE 0x0800
142 #define XT_D_KEYBINDING 0x1000
143 #define XT_D_CLIP 0x2000
144 #define XT_D_BUFFERCMD 0x4000
145 u_int32_t swm_debug = 0
146 | XT_D_MOVE
147 | XT_D_KEY
148 | XT_D_TAB
149 | XT_D_URL
150 | XT_D_CMD
151 | XT_D_NAV
152 | XT_D_DOWNLOAD
153 | XT_D_CONFIG
154 | XT_D_JS
155 | XT_D_FAVORITE
156 | XT_D_PRINTING
157 | XT_D_COOKIE
158 | XT_D_KEYBINDING
159 | XT_D_CLIP
160 | XT_D_BUFFERCMD
162 #else
163 #define DPRINTF(x...)
164 #define DNPRINTF(n,x...)
165 #endif
167 #define LENGTH(x) (sizeof x / sizeof x[0])
168 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
169 ~(GDK_BUTTON1_MASK) & \
170 ~(GDK_BUTTON2_MASK) & \
171 ~(GDK_BUTTON3_MASK) & \
172 ~(GDK_BUTTON4_MASK) & \
173 ~(GDK_BUTTON5_MASK))
175 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
177 char *icons[] = {
178 "xxxtermicon16.png",
179 "xxxtermicon32.png",
180 "xxxtermicon48.png",
181 "xxxtermicon64.png",
182 "xxxtermicon128.png"
185 struct tab {
186 TAILQ_ENTRY(tab) entry;
187 GtkWidget *vbox;
188 GtkWidget *tab_content;
189 struct {
190 GtkWidget *label;
191 GtkWidget *eventbox;
192 GtkWidget *box;
193 GtkWidget *sep;
194 } tab_elems;
195 GtkWidget *label;
196 GtkWidget *spinner;
197 GtkWidget *uri_entry;
198 GtkWidget *search_entry;
199 GtkWidget *toolbar;
200 GtkWidget *browser_win;
201 GtkWidget *statusbar_box;
202 struct {
203 GtkWidget *statusbar;
204 GtkWidget *buffercmd;
205 GtkWidget *zoom;
206 GtkWidget *position;
207 } sbe;
208 GtkWidget *cmd;
209 GtkWidget *buffers;
210 GtkWidget *oops;
211 GtkWidget *backward;
212 GtkWidget *forward;
213 GtkWidget *stop;
214 GtkWidget *gohome;
215 GtkWidget *js_toggle;
216 GtkEntryCompletion *completion;
217 guint tab_id;
218 WebKitWebView *wv;
220 WebKitWebHistoryItem *item;
221 WebKitWebBackForwardList *bfl;
223 /* favicon */
224 WebKitNetworkRequest *icon_request;
225 WebKitDownload *icon_download;
226 gchar *icon_dest_uri;
228 /* adjustments for browser */
229 GtkScrollbar *sb_h;
230 GtkScrollbar *sb_v;
231 GtkAdjustment *adjust_h;
232 GtkAdjustment *adjust_v;
234 /* flags */
235 int focus_wv;
236 int ctrl_click;
237 gchar *status;
238 int xtp_meaning; /* identifies dls/favorites */
239 gchar *tmp_uri;
240 int popup; /* 1 if cmd_entry has popup visible */
241 #ifdef USE_THREADS
242 /* https thread stuff */
243 GThread *thread;
244 #endif
245 /* hints */
246 int hints_on;
247 int hint_mode;
248 #define XT_HINT_NONE (0)
249 #define XT_HINT_NUMERICAL (1)
250 #define XT_HINT_ALPHANUM (2)
251 int new_tab;
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_HINT_NEWTAB (1<<0)
539 #define XT_TABS_NORMAL 0
540 #define XT_TABS_COMPACT 1
542 #define XT_BUFCMD_SZ (8)
544 /* mime types */
545 struct mime_type {
546 char *mt_type;
547 char *mt_action;
548 int mt_default;
549 int mt_download;
550 TAILQ_ENTRY(mime_type) entry;
552 TAILQ_HEAD(mime_type_list, mime_type);
554 /* uri aliases */
555 struct alias {
556 char *a_name;
557 char *a_uri;
558 TAILQ_ENTRY(alias) entry;
560 TAILQ_HEAD(alias_list, alias);
562 /* settings that require restart */
563 int tabless = 0; /* allow only 1 tab */
564 int enable_socket = 0;
565 int single_instance = 0; /* only allow one xxxterm to run */
566 int fancy_bar = 1; /* fancy toolbar */
567 int browser_mode = XT_BM_NORMAL;
568 int enable_localstorage = 1;
569 char *statusbar_elems = NULL;
571 /* runtime settings */
572 int show_tabs = 1; /* show tabs on notebook */
573 int tab_style = XT_TABS_NORMAL; /* tab bar style */
574 int show_url = 1; /* show url toolbar on notebook */
575 int show_statusbar = 0; /* vimperator style status bar */
576 int ctrl_click_focus = 0; /* ctrl click gets focus */
577 int cookies_enabled = 1; /* enable cookies */
578 int read_only_cookies = 0; /* enable to not write cookies */
579 int enable_scripts = 1;
580 int enable_plugins = 1;
581 gfloat default_zoom_level = 1.0;
582 char default_script[PATH_MAX];
583 int window_height = 768;
584 int window_width = 1024;
585 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
586 int refresh_interval = 10; /* download refresh interval */
587 int enable_plugin_whitelist = 0;
588 int enable_cookie_whitelist = 0;
589 int enable_js_whitelist = 0;
590 int session_timeout = 3600; /* cookie session timeout */
591 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
592 char *ssl_ca_file = NULL;
593 char *resource_dir = NULL;
594 gboolean ssl_strict_certs = FALSE;
595 int append_next = 1; /* append tab after current tab */
596 char *home = NULL;
597 char *search_string = NULL;
598 char *http_proxy = NULL;
599 char download_dir[PATH_MAX];
600 char runtime_settings[PATH_MAX]; /* override of settings */
601 int allow_volatile_cookies = 0;
602 int save_global_history = 0; /* save global history to disk */
603 char *user_agent = NULL;
604 int save_rejected_cookies = 0;
605 int session_autosave = 0;
606 int guess_search = 0;
607 int dns_prefetch = FALSE;
608 gint max_connections = 25;
609 gint max_host_connections = 5;
610 gint enable_spell_checking = 0;
611 char *spell_check_languages = NULL;
612 int xterm_workaround = 0;
613 char *url_regex = NULL;
614 int history_autosave = 0;
615 char search_file[PATH_MAX];
616 char command_file[PATH_MAX];
617 char *encoding = NULL;
619 char *cmd_font_name = NULL;
620 char *oops_font_name = NULL;
621 char *statusbar_font_name = NULL;
622 char *tabbar_font_name = NULL;
623 PangoFontDescription *cmd_font;
624 PangoFontDescription *oops_font;
625 PangoFontDescription *statusbar_font;
626 PangoFontDescription *tabbar_font;
627 char *qmarks[XT_NOMARKS];
629 int btn_down; /* M1 down in any wv */
630 regex_t url_re; /* guess_search regex */
632 struct settings;
633 struct key_binding;
634 int set_browser_mode(struct settings *, char *);
635 int set_cookie_policy(struct settings *, char *);
636 int set_download_dir(struct settings *, char *);
637 int set_default_script(struct settings *, char *);
638 int set_runtime_dir(struct settings *, char *);
639 int set_tab_style(struct settings *, char *);
640 int set_work_dir(struct settings *, char *);
641 int add_alias(struct settings *, char *);
642 int add_mime_type(struct settings *, char *);
643 int add_cookie_wl(struct settings *, char *);
644 int add_js_wl(struct settings *, char *);
645 int add_pl_wl(struct settings *, char *);
646 int add_kb(struct settings *, char *);
647 void button_set_stockid(GtkWidget *, char *);
648 GtkWidget * create_button(char *, char *, int);
650 char *get_browser_mode(struct settings *);
651 char *get_cookie_policy(struct settings *);
652 char *get_download_dir(struct settings *);
653 char *get_default_script(struct settings *);
654 char *get_runtime_dir(struct settings *);
655 char *get_tab_style(struct settings *);
656 char *get_work_dir(struct settings *);
657 void startpage_add(const char *, ...);
659 void walk_alias(struct settings *, void (*)(struct settings *,
660 char *, void *), void *);
661 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
662 char *, void *), void *);
663 void walk_js_wl(struct settings *, void (*)(struct settings *,
664 char *, void *), void *);
665 void walk_pl_wl(struct settings *, void (*)(struct settings *,
666 char *, void *), void *);
667 void walk_kb(struct settings *, void (*)(struct settings *, char *,
668 void *), void *);
669 void walk_mime_type(struct settings *, void (*)(struct settings *,
670 char *, void *), void *);
672 void recalc_tabs(void);
673 void recolor_compact_tabs(void);
674 void set_current_tab(int page_num);
675 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
676 void marks_clear(struct tab *t);
678 int set_http_proxy(char *);
680 struct special {
681 int (*set)(struct settings *, char *);
682 char *(*get)(struct settings *);
683 void (*walk)(struct settings *,
684 void (*cb)(struct settings *, char *, void *),
685 void *);
688 struct special s_browser_mode = {
689 set_browser_mode,
690 get_browser_mode,
691 NULL
694 struct special s_cookie = {
695 set_cookie_policy,
696 get_cookie_policy,
697 NULL
700 struct special s_alias = {
701 add_alias,
702 NULL,
703 walk_alias
706 struct special s_mime = {
707 add_mime_type,
708 NULL,
709 walk_mime_type
712 struct special s_js = {
713 add_js_wl,
714 NULL,
715 walk_js_wl
718 struct special s_pl = {
719 add_pl_wl,
720 NULL,
721 walk_pl_wl
724 struct special s_kb = {
725 add_kb,
726 NULL,
727 walk_kb
730 struct special s_cookie_wl = {
731 add_cookie_wl,
732 NULL,
733 walk_cookie_wl
736 struct special s_default_script = {
737 set_default_script,
738 get_default_script,
739 NULL
742 struct special s_download_dir = {
743 set_download_dir,
744 get_download_dir,
745 NULL
748 struct special s_work_dir = {
749 set_work_dir,
750 get_work_dir,
751 NULL
754 struct special s_tab_style = {
755 set_tab_style,
756 get_tab_style,
757 NULL
760 struct settings {
761 char *name;
762 int type;
763 #define XT_S_INVALID (0)
764 #define XT_S_INT (1)
765 #define XT_S_STR (2)
766 #define XT_S_FLOAT (3)
767 uint32_t flags;
768 #define XT_SF_RESTART (1<<0)
769 #define XT_SF_RUNTIME (1<<1)
770 int *ival;
771 char **sval;
772 struct special *s;
773 gfloat *fval;
774 int (*activate)(char *);
775 } rs[] = {
776 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
777 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
778 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
779 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
780 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
781 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
782 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
783 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
784 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
785 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
786 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
787 { "enable_plugin_whitelist", XT_S_INT, 0, &enable_plugin_whitelist, NULL, NULL },
788 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
789 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
790 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
791 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
792 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
793 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
794 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
795 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
796 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
797 { "home", XT_S_STR, 0, NULL, &home, NULL },
798 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
799 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
800 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
801 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
802 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
803 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
804 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
805 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
806 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
807 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
808 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
809 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
810 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
811 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
812 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
813 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
814 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
815 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
816 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
817 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
818 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
819 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
820 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
821 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
822 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
823 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
824 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
826 /* font settings */
827 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
828 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
829 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
830 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
832 /* runtime settings */
833 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
834 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
835 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
836 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
837 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
838 { "pl_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_pl },
841 int about(struct tab *, struct karg *);
842 int blank(struct tab *, struct karg *);
843 int ca_cmd(struct tab *, struct karg *);
844 int cookie_show_wl(struct tab *, struct karg *);
845 int js_show_wl(struct tab *, struct karg *);
846 int pl_show_wl(struct tab *, struct karg *);
847 int help(struct tab *, struct karg *);
848 int set(struct tab *, struct karg *);
849 int stats(struct tab *, struct karg *);
850 int marco(struct tab *, struct karg *);
851 int startpage(struct tab *, struct karg *);
852 const char * marco_message(int *);
853 int xtp_page_cl(struct tab *, struct karg *);
854 int xtp_page_dl(struct tab *, struct karg *);
855 int xtp_page_fl(struct tab *, struct karg *);
856 int xtp_page_hl(struct tab *, struct karg *);
857 void xt_icon_from_file(struct tab *, char *);
858 const gchar *get_uri(struct tab *);
859 const gchar *get_title(struct tab *, bool);
861 #define XT_URI_ABOUT ("about:")
862 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
863 #define XT_URI_ABOUT_ABOUT ("about")
864 #define XT_URI_ABOUT_BLANK ("blank")
865 #define XT_URI_ABOUT_CERTS ("certs")
866 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
867 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
868 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
869 #define XT_URI_ABOUT_FAVORITES ("favorites")
870 #define XT_URI_ABOUT_HELP ("help")
871 #define XT_URI_ABOUT_HISTORY ("history")
872 #define XT_URI_ABOUT_JSWL ("jswl")
873 #define XT_URI_ABOUT_PLUGINWL ("plwl")
874 #define XT_URI_ABOUT_SET ("set")
875 #define XT_URI_ABOUT_STATS ("stats")
876 #define XT_URI_ABOUT_MARCO ("marco")
877 #define XT_URI_ABOUT_STARTPAGE ("startpage")
879 struct about_type {
880 char *name;
881 int (*func)(struct tab *, struct karg *);
882 } about_list[] = {
883 { XT_URI_ABOUT_ABOUT, about },
884 { XT_URI_ABOUT_BLANK, blank },
885 { XT_URI_ABOUT_CERTS, ca_cmd },
886 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
887 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
888 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
889 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
890 { XT_URI_ABOUT_HELP, help },
891 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
892 { XT_URI_ABOUT_JSWL, js_show_wl },
893 { XT_URI_ABOUT_SET, set },
894 { XT_URI_ABOUT_STATS, stats },
895 { XT_URI_ABOUT_MARCO, marco },
896 { XT_URI_ABOUT_STARTPAGE, startpage },
897 { XT_URI_ABOUT_PLUGINWL, pl_show_wl },
900 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
901 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
902 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
903 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
904 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
905 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
906 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
908 /* globals */
909 extern char *__progname;
910 char **start_argv;
911 struct passwd *pwd;
912 GtkWidget *main_window;
913 GtkNotebook *notebook;
914 GtkWidget *tab_bar;
915 GtkWidget *arrow, *abtn;
916 struct tab_list tabs;
917 struct history_list hl;
918 struct session_list sessions;
919 struct download_list downloads;
920 struct domain_list c_wl;
921 struct domain_list js_wl;
922 struct domain_list pl_wl;
923 struct undo_tailq undos;
924 struct keybinding_list kbl;
925 struct sp_list spl;
926 struct command_list chl;
927 struct command_list shl;
928 struct command_entry *history_at;
929 struct command_entry *search_at;
930 int undo_count;
931 int updating_dl_tabs = 0;
932 int updating_hl_tabs = 0;
933 int updating_cl_tabs = 0;
934 int updating_fl_tabs = 0;
935 int cmd_history_count = 0;
936 int search_history_count = 0;
937 char *global_search;
938 long long unsigned int blocked_cookies = 0;
939 char named_session[PATH_MAX];
940 GtkListStore *completion_model;
941 GtkListStore *buffers_store;
943 void xxx_dir(char *);
944 int icon_size_map(int);
945 void completion_add(struct tab *);
946 void completion_add_uri(const gchar *);
947 void show_oops(struct tab *, const char *, ...);
949 void
950 history_delete(struct command_list *l, int *counter)
952 struct command_entry *c;
954 if (l == NULL || counter == NULL)
955 return;
957 c = TAILQ_LAST(l, command_list);
958 if (c == NULL)
959 return;
961 TAILQ_REMOVE(l, c, entry);
962 *counter -= 1;
963 g_free(c->line);
964 g_free(c);
967 void
968 history_add(struct command_list *list, char *file, char *l, int *counter)
970 struct command_entry *c;
971 FILE *f;
973 if (list == NULL || l == NULL || counter == NULL)
974 return;
976 /* don't add the same line */
977 c = TAILQ_FIRST(list);
978 if (c)
979 if (!strcmp(c->line + 1 /* skip space */, l))
980 return;
982 c = g_malloc0(sizeof *c);
983 c->line = g_strdup_printf(" %s", l);
985 *counter += 1;
986 TAILQ_INSERT_HEAD(list, c, entry);
988 if (*counter > 1000)
989 history_delete(list, counter);
991 if (history_autosave && file) {
992 f = fopen(file, "w");
993 if (f == NULL) {
994 show_oops(NULL, "couldn't write history %s", file);
995 return;
998 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
999 c->line[0] = ' ';
1000 fprintf(f, "%s\n", c->line);
1003 fclose(f);
1008 history_read(struct command_list *list, char *file, int *counter)
1010 FILE *f;
1011 char *s, line[65536];
1013 if (list == NULL || file == NULL)
1014 return (1);
1016 f = fopen(file, "r");
1017 if (f == NULL) {
1018 startpage_add("couldn't open history file %s", file);
1019 return (1);
1022 for (;;) {
1023 s = fgets(line, sizeof line, f);
1024 if (s == NULL || feof(f) || ferror(f))
1025 break;
1026 if ((s = strchr(line, '\n')) == NULL) {
1027 startpage_add("invalid history file %s", file);
1028 fclose(f);
1029 return (1);
1031 *s = '\0';
1033 history_add(list, NULL, line + 1, counter);
1036 fclose(f);
1038 return (0);
1041 /* marks and quickmarks array storage.
1042 * first a-z, then A-Z, then 0-9 */
1043 char
1044 indextomark(int i)
1046 if (i < 0)
1047 return (0);
1049 if (i >= 0 && i <= 'z' - 'a')
1050 return 'a' + i;
1052 i -= 'z' - 'a' + 1;
1053 if (i >= 0 && i <= 'Z' - 'A')
1054 return 'A' + i;
1056 i -= 'Z' - 'A' + 1;
1057 if (i >= 10)
1058 return (0);
1060 return i + '0';
1064 marktoindex(char m)
1066 int ret = 0;
1068 if (m >= 'a' && m <= 'z')
1069 return ret + m - 'a';
1071 ret += 'z' - 'a' + 1;
1072 if (m >= 'A' && m <= 'Z')
1073 return ret + m - 'A';
1075 ret += 'Z' - 'A' + 1;
1076 if (m >= '0' && m <= '9')
1077 return ret + m - '0';
1079 return (-1);
1083 void
1084 sigchild(int sig)
1086 int saved_errno, status;
1087 pid_t pid;
1089 saved_errno = errno;
1091 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1092 if (pid == -1) {
1093 if (errno == EINTR)
1094 continue;
1095 if (errno != ECHILD) {
1097 clog_warn("sigchild: waitpid:");
1100 break;
1103 if (WIFEXITED(status)) {
1104 if (WEXITSTATUS(status) != 0) {
1106 clog_warnx("sigchild: child exit status: %d",
1107 WEXITSTATUS(status));
1110 } else {
1112 clog_warnx("sigchild: child is terminated abnormally");
1117 errno = saved_errno;
1121 is_g_object_setting(GObject *o, char *str)
1123 guint n_props = 0, i;
1124 GParamSpec **proplist;
1126 if (! G_IS_OBJECT(o))
1127 return (0);
1129 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1130 &n_props);
1132 for (i=0; i < n_props; i++) {
1133 if (! strcmp(proplist[i]->name, str))
1134 return (1);
1136 return (0);
1139 gchar *
1140 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1142 gchar *r;
1144 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1145 "<head>\n"
1146 "<title>%s</title>\n"
1147 "%s"
1148 "%s"
1149 "</head>\n"
1150 "<body>\n"
1151 "<h1>%s</h1>\n"
1152 "%s\n</body>\n"
1153 "</html>",
1154 title,
1155 addstyles ? XT_PAGE_STYLE : "",
1156 head,
1157 title,
1158 body);
1160 return r;
1164 * Display a web page from a HTML string in memory, rather than from a URL
1166 void
1167 load_webkit_string(struct tab *t, const char *str, gchar *title)
1169 char file[PATH_MAX];
1170 int i;
1172 /* we set this to indicate we want to manually do navaction */
1173 if (t->bfl)
1174 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1176 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1177 if (title) {
1178 /* set t->xtp_meaning */
1179 for (i = 0; i < LENGTH(about_list); i++)
1180 if (!strcmp(title, about_list[i].name)) {
1181 t->xtp_meaning = i;
1182 break;
1185 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1186 "file://");
1187 #if GTK_CHECK_VERSION(2, 20, 0)
1188 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1189 gtk_widget_hide(t->spinner);
1190 #endif
1191 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1192 xt_icon_from_file(t, file);
1196 struct tab *
1197 get_current_tab(void)
1199 struct tab *t;
1201 TAILQ_FOREACH(t, &tabs, entry) {
1202 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1203 return (t);
1206 warnx("%s: no current tab", __func__);
1208 return (NULL);
1211 void
1212 set_status(struct tab *t, gchar *s, int status)
1214 gchar *type = NULL;
1216 if (s == NULL)
1217 return;
1219 switch (status) {
1220 case XT_STATUS_LOADING:
1221 type = g_strdup_printf("Loading: %s", s);
1222 s = type;
1223 break;
1224 case XT_STATUS_LINK:
1225 type = g_strdup_printf("Link: %s", s);
1226 if (!t->status)
1227 t->status = g_strdup(gtk_entry_get_text(
1228 GTK_ENTRY(t->sbe.statusbar)));
1229 s = type;
1230 break;
1231 case XT_STATUS_URI:
1232 type = g_strdup_printf("%s", s);
1233 if (!t->status) {
1234 t->status = g_strdup(type);
1236 s = type;
1237 if (!t->status)
1238 t->status = g_strdup(s);
1239 break;
1240 case XT_STATUS_NOTHING:
1241 /* FALL THROUGH */
1242 default:
1243 break;
1245 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1246 if (type)
1247 g_free(type);
1250 void
1251 hide_cmd(struct tab *t)
1253 history_at = NULL; /* just in case */
1254 search_at = NULL; /* just in case */
1255 gtk_widget_hide(t->cmd);
1258 void
1259 show_cmd(struct tab *t)
1261 history_at = NULL;
1262 search_at = NULL;
1263 gtk_widget_hide(t->oops);
1264 gtk_widget_show(t->cmd);
1267 void
1268 hide_buffers(struct tab *t)
1270 gtk_widget_hide(t->buffers);
1271 gtk_list_store_clear(buffers_store);
1274 enum {
1275 COL_ID = 0,
1276 COL_TITLE,
1277 NUM_COLS
1281 sort_tabs_by_page_num(struct tab ***stabs)
1283 int num_tabs = 0;
1284 struct tab *t;
1286 num_tabs = gtk_notebook_get_n_pages(notebook);
1288 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1290 TAILQ_FOREACH(t, &tabs, entry)
1291 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1293 return (num_tabs);
1296 void
1297 buffers_make_list(void)
1299 int i, num_tabs;
1300 const gchar *title = NULL;
1301 GtkTreeIter iter;
1302 struct tab **stabs = NULL;
1304 num_tabs = sort_tabs_by_page_num(&stabs);
1306 for (i = 0; i < num_tabs; i++)
1307 if (stabs[i]) {
1308 gtk_list_store_append(buffers_store, &iter);
1309 title = get_title(stabs[i], FALSE);
1310 gtk_list_store_set(buffers_store, &iter,
1311 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1312 * rather than 0. */
1313 COL_TITLE, title,
1314 -1);
1317 g_free(stabs);
1320 void
1321 show_buffers(struct tab *t)
1323 buffers_make_list();
1324 gtk_widget_show(t->buffers);
1325 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1328 void
1329 toggle_buffers(struct tab *t)
1331 if (gtk_widget_get_visible(t->buffers))
1332 hide_buffers(t);
1333 else
1334 show_buffers(t);
1338 buffers(struct tab *t, struct karg *args)
1340 show_buffers(t);
1342 return (0);
1345 void
1346 hide_oops(struct tab *t)
1348 gtk_widget_hide(t->oops);
1351 void
1352 show_oops(struct tab *at, const char *fmt, ...)
1354 va_list ap;
1355 char *msg = NULL;
1356 struct tab *t = NULL;
1358 if (fmt == NULL)
1359 return;
1361 if (at == NULL) {
1362 if ((t = get_current_tab()) == NULL)
1363 return;
1364 } else
1365 t = at;
1367 va_start(ap, fmt);
1368 if (vasprintf(&msg, fmt, ap) == -1)
1369 errx(1, "show_oops failed");
1370 va_end(ap);
1372 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1373 gtk_widget_hide(t->cmd);
1374 gtk_widget_show(t->oops);
1376 if (msg)
1377 free(msg);
1380 char *
1381 get_as_string(struct settings *s)
1383 char *r = NULL;
1385 if (s == NULL)
1386 return (NULL);
1388 if (s->s) {
1389 if (s->s->get)
1390 r = s->s->get(s);
1391 else
1392 warnx("get_as_string skip %s\n", s->name);
1393 } else if (s->type == XT_S_INT)
1394 r = g_strdup_printf("%d", *s->ival);
1395 else if (s->type == XT_S_STR)
1396 r = g_strdup(*s->sval);
1397 else if (s->type == XT_S_FLOAT)
1398 r = g_strdup_printf("%f", *s->fval);
1399 else
1400 r = g_strdup_printf("INVALID TYPE");
1402 return (r);
1405 void
1406 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1408 int i;
1409 char *s;
1411 for (i = 0; i < LENGTH(rs); i++) {
1412 if (rs[i].s && rs[i].s->walk)
1413 rs[i].s->walk(&rs[i], cb, cb_args);
1414 else {
1415 s = get_as_string(&rs[i]);
1416 cb(&rs[i], s, cb_args);
1417 g_free(s);
1423 set_browser_mode(struct settings *s, char *val)
1425 if (!strcmp(val, "whitelist")) {
1426 browser_mode = XT_BM_WHITELIST;
1427 allow_volatile_cookies = 0;
1428 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1429 cookies_enabled = 1;
1430 enable_cookie_whitelist = 1;
1431 enable_plugin_whitelist = 1;
1432 enable_plugins = 0;
1433 read_only_cookies = 0;
1434 save_rejected_cookies = 0;
1435 session_timeout = 3600;
1436 enable_scripts = 0;
1437 enable_js_whitelist = 1;
1438 enable_localstorage = 0;
1439 } else if (!strcmp(val, "normal")) {
1440 browser_mode = XT_BM_NORMAL;
1441 allow_volatile_cookies = 0;
1442 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1443 cookies_enabled = 1;
1444 enable_cookie_whitelist = 0;
1445 enable_plugin_whitelist = 0;
1446 enable_plugins = 1;
1447 read_only_cookies = 0;
1448 save_rejected_cookies = 0;
1449 session_timeout = 3600;
1450 enable_scripts = 1;
1451 enable_js_whitelist = 0;
1452 enable_localstorage = 1;
1453 } else if (!strcmp(val, "kiosk")) {
1454 browser_mode = XT_BM_KIOSK;
1455 allow_volatile_cookies = 0;
1456 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1457 cookies_enabled = 1;
1458 enable_cookie_whitelist = 0;
1459 enable_plugin_whitelist = 0;
1460 enable_plugins = 1;
1461 read_only_cookies = 0;
1462 save_rejected_cookies = 0;
1463 session_timeout = 3600;
1464 enable_scripts = 1;
1465 enable_js_whitelist = 0;
1466 enable_localstorage = 1;
1467 show_tabs = 0;
1468 tabless = 1;
1469 } else
1470 return (1);
1472 return (0);
1475 char *
1476 get_browser_mode(struct settings *s)
1478 char *r = NULL;
1480 if (browser_mode == XT_BM_WHITELIST)
1481 r = g_strdup("whitelist");
1482 else if (browser_mode == XT_BM_NORMAL)
1483 r = g_strdup("normal");
1484 else if (browser_mode == XT_BM_KIOSK)
1485 r = g_strdup("kiosk");
1486 else
1487 return (NULL);
1489 return (r);
1493 set_cookie_policy(struct settings *s, char *val)
1495 if (!strcmp(val, "no3rdparty"))
1496 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1497 else if (!strcmp(val, "accept"))
1498 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1499 else if (!strcmp(val, "reject"))
1500 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1501 else
1502 return (1);
1504 return (0);
1507 char *
1508 get_cookie_policy(struct settings *s)
1510 char *r = NULL;
1512 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1513 r = g_strdup("no3rdparty");
1514 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1515 r = g_strdup("accept");
1516 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1517 r = g_strdup("reject");
1518 else
1519 return (NULL);
1521 return (r);
1524 char *
1525 get_default_script(struct settings *s)
1527 if (default_script[0] == '\0')
1528 return (0);
1529 return (g_strdup(default_script));
1533 set_default_script(struct settings *s, char *val)
1535 if (val[0] == '~')
1536 snprintf(default_script, sizeof default_script, "%s/%s",
1537 pwd->pw_dir, &val[1]);
1538 else
1539 strlcpy(default_script, val, sizeof default_script);
1541 return (0);
1544 char *
1545 get_download_dir(struct settings *s)
1547 if (download_dir[0] == '\0')
1548 return (0);
1549 return (g_strdup(download_dir));
1553 set_download_dir(struct settings *s, char *val)
1555 if (val[0] == '~')
1556 snprintf(download_dir, sizeof download_dir, "%s/%s",
1557 pwd->pw_dir, &val[1]);
1558 else
1559 strlcpy(download_dir, val, sizeof download_dir);
1561 return (0);
1565 * Session IDs.
1566 * We use these to prevent people putting xxxt:// URLs on
1567 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1569 #define XT_XTP_SES_KEY_SZ 8
1570 #define XT_XTP_SES_KEY_HEX_FMT \
1571 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1572 char *dl_session_key; /* downloads */
1573 char *hl_session_key; /* history list */
1574 char *cl_session_key; /* cookie list */
1575 char *fl_session_key; /* favorites list */
1577 char work_dir[PATH_MAX];
1578 char certs_dir[PATH_MAX];
1579 char cache_dir[PATH_MAX];
1580 char sessions_dir[PATH_MAX];
1581 char cookie_file[PATH_MAX];
1582 SoupURI *proxy_uri = NULL;
1583 SoupSession *session;
1584 SoupCookieJar *s_cookiejar;
1585 SoupCookieJar *p_cookiejar;
1586 char rc_fname[PATH_MAX];
1588 struct mime_type_list mtl;
1589 struct alias_list aliases;
1591 /* protos */
1592 struct tab *create_new_tab(char *, struct undo *, int, int);
1593 void delete_tab(struct tab *);
1594 void setzoom_webkit(struct tab *, int);
1595 int run_script(struct tab *, char *);
1596 int download_rb_cmp(struct download *, struct download *);
1597 gboolean cmd_execute(struct tab *t, char *str);
1600 history_rb_cmp(struct history *h1, struct history *h2)
1602 return (strcmp(h1->uri, h2->uri));
1604 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1607 domain_rb_cmp(struct domain *d1, struct domain *d2)
1609 return (strcmp(d1->d, d2->d));
1611 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1613 char *
1614 get_work_dir(struct settings *s)
1616 if (work_dir[0] == '\0')
1617 return (0);
1618 return (g_strdup(work_dir));
1622 set_work_dir(struct settings *s, char *val)
1624 if (val[0] == '~')
1625 snprintf(work_dir, sizeof work_dir, "%s/%s",
1626 pwd->pw_dir, &val[1]);
1627 else
1628 strlcpy(work_dir, val, sizeof work_dir);
1630 return (0);
1633 char *
1634 get_tab_style(struct settings *s)
1636 if (tab_style == XT_TABS_NORMAL)
1637 return (g_strdup("normal"));
1638 else
1639 return (g_strdup("compact"));
1643 set_tab_style(struct settings *s, char *val)
1645 if (!strcmp(val, "normal"))
1646 tab_style = XT_TABS_NORMAL;
1647 else if (!strcmp(val, "compact"))
1648 tab_style = XT_TABS_COMPACT;
1649 else
1650 return (1);
1652 return (0);
1656 * generate a session key to secure xtp commands.
1657 * pass in a ptr to the key in question and it will
1658 * be modified in place.
1660 void
1661 generate_xtp_session_key(char **key)
1663 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1665 /* free old key */
1666 if (*key)
1667 g_free(*key);
1669 /* make a new one */
1670 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1671 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1672 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1673 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1675 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1679 * validate a xtp session key.
1680 * return (1) if OK
1683 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1685 if (strcmp(trusted, untrusted) != 0) {
1686 show_oops(t, "%s: xtp session key mismatch possible spoof",
1687 __func__);
1688 return (0);
1691 return (1);
1695 download_rb_cmp(struct download *e1, struct download *e2)
1697 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1699 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1701 struct valid_url_types {
1702 char *type;
1703 } vut[] = {
1704 { "http://" },
1705 { "https://" },
1706 { "ftp://" },
1707 { "file://" },
1708 { XT_XTP_STR },
1712 valid_url_type(char *url)
1714 int i;
1716 for (i = 0; i < LENGTH(vut); i++)
1717 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1718 return (0);
1720 return (1);
1723 void
1724 print_cookie(char *msg, SoupCookie *c)
1726 if (c == NULL)
1727 return;
1729 if (msg)
1730 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1731 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1732 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1733 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1734 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1735 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1736 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1737 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1738 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1739 DNPRINTF(XT_D_COOKIE, "====================================\n");
1742 void
1743 walk_alias(struct settings *s,
1744 void (*cb)(struct settings *, char *, void *), void *cb_args)
1746 struct alias *a;
1747 char *str;
1749 if (s == NULL || cb == NULL) {
1750 show_oops(NULL, "walk_alias invalid parameters");
1751 return;
1754 TAILQ_FOREACH(a, &aliases, entry) {
1755 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1756 cb(s, str, cb_args);
1757 g_free(str);
1761 char *
1762 match_alias(char *url_in)
1764 struct alias *a;
1765 char *arg;
1766 char *url_out = NULL, *search, *enc_arg;
1768 search = g_strdup(url_in);
1769 arg = search;
1770 if (strsep(&arg, " \t") == NULL) {
1771 show_oops(NULL, "match_alias: NULL URL");
1772 goto done;
1775 TAILQ_FOREACH(a, &aliases, entry) {
1776 if (!strcmp(search, a->a_name))
1777 break;
1780 if (a != NULL) {
1781 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1782 a->a_name);
1783 if (arg != NULL) {
1784 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1785 url_out = g_strdup_printf(a->a_uri, enc_arg);
1786 g_free(enc_arg);
1787 } else
1788 url_out = g_strdup_printf(a->a_uri, "");
1790 done:
1791 g_free(search);
1792 return (url_out);
1795 char *
1796 guess_url_type(char *url_in)
1798 struct stat sb;
1799 char *url_out = NULL, *enc_search = NULL;
1800 int i;
1802 /* substitute aliases */
1803 url_out = match_alias(url_in);
1804 if (url_out != NULL)
1805 return (url_out);
1807 /* see if we are an about page */
1808 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1809 for (i = 0; i < LENGTH(about_list); i++)
1810 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1811 about_list[i].name)) {
1812 url_out = g_strdup(url_in);
1813 goto done;
1816 if (guess_search && url_regex &&
1817 !(g_str_has_prefix(url_in, "http://") ||
1818 g_str_has_prefix(url_in, "https://"))) {
1819 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1820 /* invalid URI so search instead */
1821 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1822 url_out = g_strdup_printf(search_string, enc_search);
1823 g_free(enc_search);
1824 goto done;
1828 /* XXX not sure about this heuristic */
1829 if (stat(url_in, &sb) == 0)
1830 url_out = g_strdup_printf("file://%s", url_in);
1831 else
1832 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1833 done:
1834 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1836 return (url_out);
1839 void
1840 load_uri(struct tab *t, gchar *uri)
1842 struct karg args;
1843 gchar *newuri = NULL;
1844 int i;
1846 if (uri == NULL)
1847 return;
1849 /* Strip leading spaces. */
1850 while (*uri && isspace(*uri))
1851 uri++;
1853 if (strlen(uri) == 0) {
1854 blank(t, NULL);
1855 return;
1858 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1860 if (valid_url_type(uri)) {
1861 newuri = guess_url_type(uri);
1862 uri = newuri;
1865 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1866 for (i = 0; i < LENGTH(about_list); i++)
1867 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1868 bzero(&args, sizeof args);
1869 about_list[i].func(t, &args);
1870 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1871 FALSE);
1872 goto done;
1874 show_oops(t, "invalid about page");
1875 goto done;
1878 set_status(t, (char *)uri, XT_STATUS_LOADING);
1879 marks_clear(t);
1880 webkit_web_view_load_uri(t->wv, uri);
1881 done:
1882 if (newuri)
1883 g_free(newuri);
1886 const gchar *
1887 get_uri(struct tab *t)
1889 const gchar *uri = NULL;
1891 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1892 return NULL;
1893 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1894 uri = webkit_web_view_get_uri(t->wv);
1895 } else {
1896 /* use tmp_uri to make sure it is g_freed */
1897 if (t->tmp_uri)
1898 g_free(t->tmp_uri);
1899 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1900 about_list[t->xtp_meaning].name);
1901 uri = t->tmp_uri;
1903 return uri;
1906 const gchar *
1907 get_title(struct tab *t, bool window)
1909 const gchar *set = NULL, *title = NULL;
1910 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1912 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1913 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1914 goto notitle;
1916 title = webkit_web_view_get_title(t->wv);
1917 if ((set = title ? title : get_uri(t)))
1918 return set;
1920 notitle:
1921 set = window ? XT_NAME : "(untitled)";
1923 return set;
1927 add_alias(struct settings *s, char *line)
1929 char *l, *alias;
1930 struct alias *a = NULL;
1932 if (s == NULL || line == NULL) {
1933 show_oops(NULL, "add_alias invalid parameters");
1934 return (1);
1937 l = line;
1938 a = g_malloc(sizeof(*a));
1940 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1941 show_oops(NULL, "add_alias: incomplete alias definition");
1942 goto bad;
1944 if (strlen(alias) == 0 || strlen(l) == 0) {
1945 show_oops(NULL, "add_alias: invalid alias definition");
1946 goto bad;
1949 a->a_name = g_strdup(alias);
1950 a->a_uri = g_strdup(l);
1952 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1954 TAILQ_INSERT_TAIL(&aliases, a, entry);
1956 return (0);
1957 bad:
1958 if (a)
1959 g_free(a);
1960 return (1);
1964 add_mime_type(struct settings *s, char *line)
1966 char *mime_type;
1967 char *l;
1968 struct mime_type *m = NULL;
1969 int downloadfirst = 0;
1971 /* XXX this could be smarter */
1973 if (line == NULL || strlen(line) == 0) {
1974 show_oops(NULL, "add_mime_type invalid parameters");
1975 return (1);
1978 l = line;
1979 if (*l == '@') {
1980 downloadfirst = 1;
1981 l++;
1983 m = g_malloc(sizeof(*m));
1985 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1986 show_oops(NULL, "add_mime_type: invalid mime_type");
1987 goto bad;
1989 if (mime_type[strlen(mime_type) - 1] == '*') {
1990 mime_type[strlen(mime_type) - 1] = '\0';
1991 m->mt_default = 1;
1992 } else
1993 m->mt_default = 0;
1995 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1996 show_oops(NULL, "add_mime_type: invalid mime_type");
1997 goto bad;
2000 m->mt_type = g_strdup(mime_type);
2001 m->mt_action = g_strdup(l);
2002 m->mt_download = downloadfirst;
2004 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
2005 m->mt_type, m->mt_action, m->mt_default);
2007 TAILQ_INSERT_TAIL(&mtl, m, entry);
2009 return (0);
2010 bad:
2011 if (m)
2012 g_free(m);
2013 return (1);
2016 struct mime_type *
2017 find_mime_type(char *mime_type)
2019 struct mime_type *m, *def = NULL, *rv = NULL;
2021 TAILQ_FOREACH(m, &mtl, entry) {
2022 if (m->mt_default &&
2023 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
2024 def = m;
2026 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
2027 rv = m;
2028 break;
2032 if (rv == NULL)
2033 rv = def;
2035 return (rv);
2038 void
2039 walk_mime_type(struct settings *s,
2040 void (*cb)(struct settings *, char *, void *), void *cb_args)
2042 struct mime_type *m;
2043 char *str;
2045 if (s == NULL || cb == NULL) {
2046 show_oops(NULL, "walk_mime_type invalid parameters");
2047 return;
2050 TAILQ_FOREACH(m, &mtl, entry) {
2051 str = g_strdup_printf("%s%s --> %s",
2052 m->mt_type,
2053 m->mt_default ? "*" : "",
2054 m->mt_action);
2055 cb(s, str, cb_args);
2056 g_free(str);
2060 void
2061 wl_add(char *str, struct domain_list *wl, int handy)
2063 struct domain *d;
2064 int add_dot = 0;
2065 char *p;
2067 if (str == NULL || wl == NULL || strlen(str) < 2)
2068 return;
2070 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2072 /* treat *.moo.com the same as .moo.com */
2073 if (str[0] == '*' && str[1] == '.')
2074 str = &str[1];
2075 else if (str[0] == '.')
2076 str = &str[0];
2077 else
2078 add_dot = 1;
2080 /* slice off port number */
2081 p = g_strrstr(str, ":");
2082 if (p)
2083 *p = '\0';
2085 d = g_malloc(sizeof *d);
2086 if (add_dot)
2087 d->d = g_strdup_printf(".%s", str);
2088 else
2089 d->d = g_strdup(str);
2090 d->handy = handy;
2092 if (RB_INSERT(domain_list, wl, d))
2093 goto unwind;
2095 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2096 return;
2097 unwind:
2098 if (d) {
2099 if (d->d)
2100 g_free(d->d);
2101 g_free(d);
2106 add_cookie_wl(struct settings *s, char *entry)
2108 wl_add(entry, &c_wl, 1);
2109 return (0);
2112 void
2113 walk_cookie_wl(struct settings *s,
2114 void (*cb)(struct settings *, char *, void *), void *cb_args)
2116 struct domain *d;
2118 if (s == NULL || cb == NULL) {
2119 show_oops(NULL, "walk_cookie_wl invalid parameters");
2120 return;
2123 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2124 cb(s, d->d, cb_args);
2127 void
2128 walk_js_wl(struct settings *s,
2129 void (*cb)(struct settings *, char *, void *), void *cb_args)
2131 struct domain *d;
2133 if (s == NULL || cb == NULL) {
2134 show_oops(NULL, "walk_js_wl invalid parameters");
2135 return;
2138 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2139 cb(s, d->d, cb_args);
2143 add_js_wl(struct settings *s, char *entry)
2145 wl_add(entry, &js_wl, 1 /* persistent */);
2146 return (0);
2149 void
2150 walk_pl_wl(struct settings *s,
2151 void (*cb)(struct settings *, char *, void *), void *cb_args)
2153 struct domain *d;
2155 if (s == NULL || cb == NULL) {
2156 show_oops(NULL, "walk_pl_wl invalid parameters");
2157 return;
2160 RB_FOREACH_REVERSE(d, domain_list, &pl_wl)
2161 cb(s, d->d, cb_args);
2165 add_pl_wl(struct settings *s, char *entry)
2167 wl_add(entry, &pl_wl, 1 /* persistent */);
2168 return (0);
2171 struct domain *
2172 wl_find(const gchar *search, struct domain_list *wl)
2174 int i;
2175 struct domain *d = NULL, dfind;
2176 gchar *s = NULL;
2178 if (search == NULL || wl == NULL)
2179 return (NULL);
2180 if (strlen(search) < 2)
2181 return (NULL);
2183 if (search[0] != '.')
2184 s = g_strdup_printf(".%s", search);
2185 else
2186 s = g_strdup(search);
2188 for (i = strlen(s) - 1; i >= 0; i--) {
2189 if (s[i] == '.') {
2190 dfind.d = &s[i];
2191 d = RB_FIND(domain_list, wl, &dfind);
2192 if (d)
2193 goto done;
2197 done:
2198 if (s)
2199 g_free(s);
2201 return (d);
2204 struct domain *
2205 wl_find_uri(const gchar *s, struct domain_list *wl)
2207 int i;
2208 char *ss;
2209 struct domain *r;
2211 if (s == NULL || wl == NULL)
2212 return (NULL);
2214 if (!strncmp(s, "http://", strlen("http://")))
2215 s = &s[strlen("http://")];
2216 else if (!strncmp(s, "https://", strlen("https://")))
2217 s = &s[strlen("https://")];
2219 if (strlen(s) < 2)
2220 return (NULL);
2222 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2223 /* chop string at first slash */
2224 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2225 ss = g_strdup(s);
2226 ss[i] = '\0';
2227 r = wl_find(ss, wl);
2228 g_free(ss);
2229 return (r);
2232 return (NULL);
2236 settings_add(char *var, char *val)
2238 int i, rv, *p;
2239 gfloat *f;
2240 char **s;
2242 /* get settings */
2243 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2244 if (strcmp(var, rs[i].name))
2245 continue;
2247 if (rs[i].s) {
2248 if (rs[i].s->set(&rs[i], val))
2249 errx(1, "invalid value for %s: %s", var, val);
2250 rv = 1;
2251 break;
2252 } else
2253 switch (rs[i].type) {
2254 case XT_S_INT:
2255 p = rs[i].ival;
2256 *p = atoi(val);
2257 rv = 1;
2258 break;
2259 case XT_S_STR:
2260 s = rs[i].sval;
2261 if (s == NULL)
2262 errx(1, "invalid sval for %s",
2263 rs[i].name);
2264 if (*s)
2265 g_free(*s);
2266 *s = g_strdup(val);
2267 rv = 1;
2268 break;
2269 case XT_S_FLOAT:
2270 f = rs[i].fval;
2271 *f = atof(val);
2272 rv = 1;
2273 break;
2274 case XT_S_INVALID:
2275 default:
2276 errx(1, "invalid type for %s", var);
2278 break;
2280 return (rv);
2283 #define WS "\n= \t"
2284 void
2285 config_parse(char *filename, int runtime)
2287 FILE *config, *f;
2288 char *line, *cp, *var, *val;
2289 size_t len, lineno = 0;
2290 int handled;
2291 char file[PATH_MAX];
2292 struct stat sb;
2294 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2296 if (filename == NULL)
2297 return;
2299 if (runtime && runtime_settings[0] != '\0') {
2300 snprintf(file, sizeof file, "%s/%s",
2301 work_dir, runtime_settings);
2302 if (stat(file, &sb)) {
2303 warnx("runtime file doesn't exist, creating it");
2304 if ((f = fopen(file, "w")) == NULL)
2305 err(1, "runtime");
2306 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2307 fclose(f);
2309 } else
2310 strlcpy(file, filename, sizeof file);
2312 if ((config = fopen(file, "r")) == NULL) {
2313 warn("config_parse: cannot open %s", filename);
2314 return;
2317 for (;;) {
2318 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2319 if (feof(config) || ferror(config))
2320 break;
2322 cp = line;
2323 cp += (long)strspn(cp, WS);
2324 if (cp[0] == '\0') {
2325 /* empty line */
2326 free(line);
2327 continue;
2330 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2331 startpage_add("invalid configuration file entry: %s",
2332 line);
2333 else {
2334 cp += (long)strspn(cp, WS);
2336 if ((val = strsep(&cp, "\0")) == NULL)
2337 break;
2339 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",
2340 var, val);
2341 handled = settings_add(var, val);
2343 if (handled == 0)
2344 startpage_add("invalid configuration file entry"
2345 ": %s=%s", var, val);
2348 free(line);
2351 fclose(config);
2354 char *
2355 js_ref_to_string(JSContextRef context, JSValueRef ref)
2357 char *s = NULL;
2358 size_t l;
2359 JSStringRef jsref;
2361 jsref = JSValueToStringCopy(context, ref, NULL);
2362 if (jsref == NULL)
2363 return (NULL);
2365 l = JSStringGetMaximumUTF8CStringSize(jsref);
2366 s = g_malloc(l);
2367 if (s)
2368 JSStringGetUTF8CString(jsref, s, l);
2369 JSStringRelease(jsref);
2371 return (s);
2374 void
2375 disable_hints(struct tab *t)
2377 bzero(t->hint_buf, sizeof t->hint_buf);
2378 bzero(t->hint_num, sizeof t->hint_num);
2379 run_script(t, "vimprobable_clear()");
2380 t->hints_on = 0;
2381 t->hint_mode = XT_HINT_NONE;
2382 t->new_tab = 0;
2385 void
2386 enable_hints(struct tab *t)
2388 bzero(t->hint_buf, sizeof t->hint_buf);
2389 run_script(t, "vimprobable_show_hints()");
2390 t->hints_on = 1;
2391 t->hint_mode = XT_HINT_NONE;
2394 #define XT_JS_OPEN ("open;")
2395 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2396 #define XT_JS_FIRE ("fire;")
2397 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2398 #define XT_JS_FOUND ("found;")
2399 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2402 run_script(struct tab *t, char *s)
2404 JSGlobalContextRef ctx;
2405 WebKitWebFrame *frame;
2406 JSStringRef str;
2407 JSValueRef val, exception;
2408 char *es, buf[128];
2410 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2411 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2413 frame = webkit_web_view_get_main_frame(t->wv);
2414 ctx = webkit_web_frame_get_global_context(frame);
2416 str = JSStringCreateWithUTF8CString(s);
2417 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2418 NULL, 0, &exception);
2419 JSStringRelease(str);
2421 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2422 if (val == NULL) {
2423 es = js_ref_to_string(ctx, exception);
2424 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2425 g_free(es);
2426 return (1);
2427 } else {
2428 /* if set open in new tab */
2429 if (t->new_tab)
2430 t->ctrl_click = 1;
2432 es = js_ref_to_string(ctx, val);
2433 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2435 /* handle return value right here */
2436 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2437 disable_hints(t);
2438 marks_clear(t);
2439 load_uri(t, &es[XT_JS_OPEN_LEN]);
2442 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2443 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2444 &es[XT_JS_FIRE_LEN]);
2445 run_script(t, buf);
2446 disable_hints(t);
2449 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2450 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2451 disable_hints(t);
2454 g_free(es);
2457 return (0);
2461 hint(struct tab *t, struct karg *args)
2464 DNPRINTF(XT_D_JS, "hint: tab %d args %d\n", t->tab_id, args->i);
2466 if (args->i == XT_HINT_NEWTAB)
2467 t->new_tab = 1;
2469 if (t->hints_on == 0)
2470 enable_hints(t);
2471 else
2472 disable_hints(t);
2474 return (0);
2477 void
2478 apply_style(struct tab *t)
2480 g_object_set(G_OBJECT(t->settings),
2481 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2485 userstyle(struct tab *t, struct karg *args)
2487 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2489 if (t->styled) {
2490 t->styled = 0;
2491 g_object_set(G_OBJECT(t->settings),
2492 "user-stylesheet-uri", NULL, (char *)NULL);
2493 } else {
2494 t->styled = 1;
2495 apply_style(t);
2497 return (0);
2501 * Doesn't work fully, due to the following bug:
2502 * https://bugs.webkit.org/show_bug.cgi?id=51747
2505 restore_global_history(void)
2507 char file[PATH_MAX];
2508 FILE *f;
2509 struct history *h;
2510 gchar *uri;
2511 gchar *title;
2512 const char delim[3] = {'\\', '\\', '\0'};
2514 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2516 if ((f = fopen(file, "r")) == NULL) {
2517 warnx("%s: fopen", __func__);
2518 return (1);
2521 for (;;) {
2522 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2523 if (feof(f) || ferror(f))
2524 break;
2526 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2527 if (feof(f) || ferror(f)) {
2528 free(uri);
2529 warnx("%s: broken history file\n", __func__);
2530 return (1);
2533 if (uri && strlen(uri) && title && strlen(title)) {
2534 webkit_web_history_item_new_with_data(uri, title);
2535 h = g_malloc(sizeof(struct history));
2536 h->uri = g_strdup(uri);
2537 h->title = g_strdup(title);
2538 RB_INSERT(history_list, &hl, h);
2539 completion_add_uri(h->uri);
2540 } else {
2541 warnx("%s: failed to restore history\n", __func__);
2542 free(uri);
2543 free(title);
2544 return (1);
2547 free(uri);
2548 free(title);
2549 uri = NULL;
2550 title = NULL;
2553 return (0);
2557 save_global_history_to_disk(struct tab *t)
2559 char file[PATH_MAX];
2560 FILE *f;
2561 struct history *h;
2563 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2565 if ((f = fopen(file, "w")) == NULL) {
2566 show_oops(t, "%s: global history file: %s",
2567 __func__, strerror(errno));
2568 return (1);
2571 RB_FOREACH_REVERSE(h, history_list, &hl) {
2572 if (h->uri && h->title)
2573 fprintf(f, "%s\n%s\n", h->uri, h->title);
2576 fclose(f);
2578 return (0);
2582 quit(struct tab *t, struct karg *args)
2584 if (save_global_history)
2585 save_global_history_to_disk(t);
2587 gtk_main_quit();
2589 return (1);
2592 void
2593 restore_sessions_list(void)
2595 DIR *sdir = NULL;
2596 struct dirent *dp = NULL;
2597 struct session *s;
2599 sdir = opendir(sessions_dir);
2600 if (sdir) {
2601 while ((dp = readdir(sdir)) != NULL)
2602 if (dp->d_type == DT_REG) {
2603 s = g_malloc(sizeof(struct session));
2604 s->name = g_strdup(dp->d_name);
2605 TAILQ_INSERT_TAIL(&sessions, s, entry);
2607 closedir(sdir);
2612 open_tabs(struct tab *t, struct karg *a)
2614 char file[PATH_MAX];
2615 FILE *f = NULL;
2616 char *uri = NULL;
2617 int rv = 1;
2618 struct tab *ti, *tt;
2620 if (a == NULL)
2621 goto done;
2623 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2624 if ((f = fopen(file, "r")) == NULL)
2625 goto done;
2627 ti = TAILQ_LAST(&tabs, tab_list);
2629 for (;;) {
2630 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2631 if (feof(f) || ferror(f))
2632 break;
2634 /* retrieve session name */
2635 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2636 strlcpy(named_session,
2637 &uri[strlen(XT_SAVE_SESSION_ID)],
2638 sizeof named_session);
2639 continue;
2642 if (uri && strlen(uri))
2643 create_new_tab(uri, NULL, 1, -1);
2645 free(uri);
2646 uri = NULL;
2649 /* close open tabs */
2650 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2651 for (;;) {
2652 tt = TAILQ_FIRST(&tabs);
2653 if (tt != ti) {
2654 delete_tab(tt);
2655 continue;
2657 delete_tab(tt);
2658 break;
2660 recalc_tabs();
2663 rv = 0;
2664 done:
2665 if (f)
2666 fclose(f);
2668 return (rv);
2672 restore_saved_tabs(void)
2674 char file[PATH_MAX];
2675 int unlink_file = 0;
2676 struct stat sb;
2677 struct karg a;
2678 int rv = 0;
2680 snprintf(file, sizeof file, "%s/%s",
2681 sessions_dir, XT_RESTART_TABS_FILE);
2682 if (stat(file, &sb) == -1)
2683 a.s = XT_SAVED_TABS_FILE;
2684 else {
2685 unlink_file = 1;
2686 a.s = XT_RESTART_TABS_FILE;
2689 a.i = XT_SES_DONOTHING;
2690 rv = open_tabs(NULL, &a);
2692 if (unlink_file)
2693 unlink(file);
2695 return (rv);
2699 save_tabs(struct tab *t, struct karg *a)
2701 char file[PATH_MAX];
2702 FILE *f;
2703 int num_tabs = 0, i;
2704 struct tab **stabs = NULL;
2706 if (a == NULL)
2707 return (1);
2708 if (a->s == NULL)
2709 snprintf(file, sizeof file, "%s/%s",
2710 sessions_dir, named_session);
2711 else
2712 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2714 if ((f = fopen(file, "w")) == NULL) {
2715 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2716 return (1);
2719 /* save session name */
2720 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2722 /* Save tabs, in the order they are arranged in the notebook. */
2723 num_tabs = sort_tabs_by_page_num(&stabs);
2725 for (i = 0; i < num_tabs; i++)
2726 if (stabs[i]) {
2727 if (get_uri(stabs[i]) != NULL)
2728 fprintf(f, "%s\n", get_uri(stabs[i]));
2729 else if (gtk_entry_get_text(GTK_ENTRY(
2730 stabs[i]->uri_entry)))
2731 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2732 stabs[i]->uri_entry)));
2735 g_free(stabs);
2737 /* try and make sure this gets to disk NOW. XXX Backup first? */
2738 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2739 show_oops(t, "May not have managed to save session: %s",
2740 strerror(errno));
2743 fclose(f);
2745 return (0);
2749 save_tabs_and_quit(struct tab *t, struct karg *args)
2751 struct karg a;
2753 a.s = NULL;
2754 save_tabs(t, &a);
2755 quit(t, NULL);
2757 return (1);
2761 run_page_script(struct tab *t, struct karg *args)
2763 const gchar *uri;
2764 char *tmp, script[PATH_MAX];
2766 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2767 if (tmp[0] == '\0') {
2768 show_oops(t, "no script specified");
2769 return (1);
2772 if ((uri = get_uri(t)) == NULL) {
2773 show_oops(t, "tab is empty, not running script");
2774 return (1);
2777 if (tmp[0] == '~')
2778 snprintf(script, sizeof script, "%s/%s",
2779 pwd->pw_dir, &tmp[1]);
2780 else
2781 strlcpy(script, tmp, sizeof script);
2783 switch (fork()) {
2784 case -1:
2785 show_oops(t, "can't fork to run script");
2786 return (1);
2787 /* NOTREACHED */
2788 case 0:
2789 break;
2790 default:
2791 return (0);
2794 /* child */
2795 execlp(script, script, uri, (void *)NULL);
2797 _exit(0);
2799 /* NOTREACHED */
2801 return (0);
2805 yank_uri(struct tab *t, struct karg *args)
2807 const gchar *uri;
2808 GtkClipboard *clipboard;
2810 if ((uri = get_uri(t)) == NULL)
2811 return (1);
2813 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2814 gtk_clipboard_set_text(clipboard, uri, -1);
2816 return (0);
2820 paste_uri(struct tab *t, struct karg *args)
2822 GtkClipboard *clipboard;
2823 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2824 gint len;
2825 gchar *p = NULL, *uri;
2827 /* try primary clipboard first */
2828 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2829 p = gtk_clipboard_wait_for_text(clipboard);
2831 /* if it failed get whatever text is in cut_buffer0 */
2832 if (p == NULL && xterm_workaround)
2833 if (gdk_property_get(gdk_get_default_root_window(),
2834 atom,
2835 gdk_atom_intern("STRING", FALSE),
2837 1024 * 1024 /* picked out of my butt */,
2838 FALSE,
2839 NULL,
2840 NULL,
2841 &len,
2842 (guchar **)&p)) {
2843 /* yes sir, we need to NUL the string */
2844 p[len] = '\0';
2847 if (p) {
2848 uri = p;
2849 while (*uri && isspace(*uri))
2850 uri++;
2851 if (strlen(uri) == 0) {
2852 show_oops(t, "empty paste buffer");
2853 goto done;
2855 if (guess_search == 0 && valid_url_type(uri)) {
2856 /* we can be clever and paste this in search box */
2857 show_oops(t, "not a valid URL");
2858 goto done;
2861 if (args->i == XT_PASTE_CURRENT_TAB)
2862 load_uri(t, uri);
2863 else if (args->i == XT_PASTE_NEW_TAB)
2864 create_new_tab(uri, NULL, 1, -1);
2867 done:
2868 if (p)
2869 g_free(p);
2871 return (0);
2874 gchar *
2875 find_domain(const gchar *s, int toplevel)
2877 SoupURI *uri;
2878 gchar *ret, *p;
2880 if (s == NULL)
2881 return (NULL);
2883 uri = soup_uri_new(s);
2885 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2886 return (NULL);
2889 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2890 if ((p = strrchr(uri->host, '.')) != NULL) {
2891 while(--p >= uri->host && *p != '.');
2892 p++;
2893 } else
2894 p = uri->host;
2895 } else
2896 p = uri->host;
2898 ret = g_strdup_printf(".%s", p);
2900 soup_uri_free(uri);
2902 return (ret);
2906 toggle_cwl(struct tab *t, struct karg *args)
2908 struct domain *d;
2909 const gchar *uri;
2910 char *dom = NULL;
2911 int es;
2913 if (args == NULL)
2914 return (1);
2916 uri = get_uri(t);
2917 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2919 if (uri == NULL || dom == NULL ||
2920 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2921 show_oops(t, "Can't toggle domain in cookie white list");
2922 goto done;
2924 d = wl_find(dom, &c_wl);
2926 if (d == NULL)
2927 es = 0;
2928 else
2929 es = 1;
2931 if (args->i & XT_WL_TOGGLE)
2932 es = !es;
2933 else if ((args->i & XT_WL_ENABLE) && es != 1)
2934 es = 1;
2935 else if ((args->i & XT_WL_DISABLE) && es != 0)
2936 es = 0;
2938 if (es)
2939 /* enable cookies for domain */
2940 wl_add(dom, &c_wl, 0);
2941 else {
2942 /* disable cookies for domain */
2943 if (d)
2944 RB_REMOVE(domain_list, &c_wl, d);
2947 if (args->i & XT_WL_RELOAD)
2948 webkit_web_view_reload(t->wv);
2950 done:
2951 g_free(dom);
2952 return (0);
2956 toggle_js(struct tab *t, struct karg *args)
2958 int es;
2959 const gchar *uri;
2960 struct domain *d;
2961 char *dom = NULL;
2963 if (args == NULL)
2964 return (1);
2966 g_object_get(G_OBJECT(t->settings),
2967 "enable-scripts", &es, (char *)NULL);
2968 if (args->i & XT_WL_TOGGLE)
2969 es = !es;
2970 else if ((args->i & XT_WL_ENABLE) && es != 1)
2971 es = 1;
2972 else if ((args->i & XT_WL_DISABLE) && es != 0)
2973 es = 0;
2974 else
2975 return (1);
2977 uri = get_uri(t);
2978 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2980 if (uri == NULL || dom == NULL ||
2981 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2982 show_oops(t, "Can't toggle domain in JavaScript white list");
2983 goto done;
2986 if (es) {
2987 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2988 wl_add(dom, &js_wl, 0 /* session */);
2989 } else {
2990 d = wl_find(dom, &js_wl);
2991 if (d)
2992 RB_REMOVE(domain_list, &js_wl, d);
2993 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2995 g_object_set(G_OBJECT(t->settings),
2996 "enable-scripts", es, (char *)NULL);
2997 g_object_set(G_OBJECT(t->settings),
2998 "javascript-can-open-windows-automatically", es, (char *)NULL);
2999 webkit_web_view_set_settings(t->wv, t->settings);
3001 if (args->i & XT_WL_RELOAD)
3002 webkit_web_view_reload(t->wv);
3003 done:
3004 if (dom)
3005 g_free(dom);
3006 return (0);
3010 toggle_pl(struct tab *t, struct karg *args)
3012 int es;
3013 const gchar *uri;
3014 struct domain *d;
3015 char *dom = NULL;
3017 if (args == NULL)
3018 return (1);
3020 g_object_get(G_OBJECT(t->settings),
3021 "enable-plugins", &es, (char *)NULL);
3022 if (args->i & XT_WL_TOGGLE)
3023 es = !es;
3024 else if ((args->i & XT_WL_ENABLE) && es != 1)
3025 es = 1;
3026 else if ((args->i & XT_WL_DISABLE) && es != 0)
3027 es = 0;
3028 else
3029 return (1);
3031 uri = get_uri(t);
3032 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3034 if (uri == NULL || dom == NULL ||
3035 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3036 show_oops(t, "Can't toggle domain in plugins white list");
3037 goto done;
3040 if (es)
3041 wl_add(dom, &pl_wl, 0 /* session */);
3042 else {
3043 d = wl_find(dom, &pl_wl);
3044 if (d)
3045 RB_REMOVE(domain_list, &pl_wl, d);
3047 g_object_set(G_OBJECT(t->settings),
3048 "enable-plugins", es, (char *)NULL);
3049 webkit_web_view_set_settings(t->wv, t->settings);
3051 if (args->i & XT_WL_RELOAD)
3052 webkit_web_view_reload(t->wv);
3053 done:
3054 if (dom)
3055 g_free(dom);
3056 return (0);
3059 void
3060 js_toggle_cb(GtkWidget *w, struct tab *t)
3062 struct karg a;
3063 int es, set;
3065 g_object_get(G_OBJECT(t->settings),
3066 "enable-scripts", &es, (char *)NULL);
3067 es = !es;
3068 if (es)
3069 set = XT_WL_ENABLE;
3070 else
3071 set = XT_WL_DISABLE;
3073 a.i = set | XT_WL_TOPLEVEL;
3074 toggle_pl(t, &a);
3076 a.i = set | XT_WL_TOPLEVEL;
3077 toggle_cwl(t, &a);
3079 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
3080 toggle_js(t, &a);
3084 toggle_src(struct tab *t, struct karg *args)
3086 gboolean mode;
3088 if (t == NULL)
3089 return (0);
3091 mode = webkit_web_view_get_view_source_mode(t->wv);
3092 webkit_web_view_set_view_source_mode(t->wv, !mode);
3093 webkit_web_view_reload(t->wv);
3095 return (0);
3098 void
3099 focus_webview(struct tab *t)
3101 if (t == NULL)
3102 return;
3104 /* only grab focus if we are visible */
3105 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
3106 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
3110 focus(struct tab *t, struct karg *args)
3112 if (t == NULL || args == NULL)
3113 return (1);
3115 if (show_url == 0)
3116 return (0);
3118 if (args->i == XT_FOCUS_URI)
3119 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
3120 else if (args->i == XT_FOCUS_SEARCH)
3121 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
3123 return (0);
3127 stats(struct tab *t, struct karg *args)
3129 char *page, *body, *s, line[64 * 1024];
3130 long long unsigned int line_count = 0;
3131 FILE *r_cookie_f;
3133 if (t == NULL)
3134 show_oops(NULL, "stats invalid parameters");
3136 line[0] = '\0';
3137 if (save_rejected_cookies) {
3138 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3139 for (;;) {
3140 s = fgets(line, sizeof line, r_cookie_f);
3141 if (s == NULL || feof(r_cookie_f) ||
3142 ferror(r_cookie_f))
3143 break;
3144 line_count++;
3146 fclose(r_cookie_f);
3147 snprintf(line, sizeof line,
3148 "<br/>Cookies blocked(*) total: %llu", line_count);
3149 } else
3150 show_oops(t, "Can't open blocked cookies file: %s",
3151 strerror(errno));
3154 body = g_strdup_printf(
3155 "Cookies blocked(*) this session: %llu"
3156 "%s"
3157 "<p><small><b>*</b> results vary based on settings</small></p>",
3158 blocked_cookies,
3159 line);
3161 page = get_html_page("Statistics", body, "", 0);
3162 g_free(body);
3164 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3165 g_free(page);
3167 return (0);
3171 marco(struct tab *t, struct karg *args)
3173 char *page, line[64 * 1024];
3174 int len;
3176 if (t == NULL)
3177 show_oops(NULL, "marco invalid parameters");
3179 line[0] = '\0';
3180 snprintf(line, sizeof line, "%s", marco_message(&len));
3182 page = get_html_page("Marco Sez...", line, "", 0);
3184 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3185 g_free(page);
3187 return (0);
3191 blank(struct tab *t, struct karg *args)
3193 if (t == NULL)
3194 show_oops(NULL, "blank invalid parameters");
3196 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3198 return (0);
3202 about(struct tab *t, struct karg *args)
3204 char *page, *body;
3206 if (t == NULL)
3207 show_oops(NULL, "about invalid parameters");
3209 body = g_strdup_printf("<b>Version: %s</b>"
3210 #ifdef XXXTERM_BUILDSTR
3211 "<br><b>Build: %s</b>"
3212 #endif
3213 "<p>"
3214 "Authors:"
3215 "<ul>"
3216 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3217 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3218 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3219 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3220 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3221 "</ul>"
3222 "Copyrights and licenses can be found on the XXXTerm "
3223 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3224 "</p>",
3225 #ifdef XXXTERM_BUILDSTR
3226 version, XXXTERM_BUILDSTR
3227 #else
3228 version
3229 #endif
3232 page = get_html_page("About", body, "", 0);
3233 g_free(body);
3235 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3236 g_free(page);
3238 return (0);
3242 help(struct tab *t, struct karg *args)
3244 char *page, *head, *body;
3246 if (t == NULL)
3247 show_oops(NULL, "help invalid parameters");
3249 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3250 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3251 "</head>\n";
3252 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3253 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3254 "cgi-bin/man-cgi?xxxterm</a>";
3256 page = get_html_page(XT_NAME, body, head, FALSE);
3258 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3259 g_free(page);
3261 return (0);
3265 startpage(struct tab *t, struct karg *args)
3267 char *page, *body, *b;
3268 struct sp *s;
3270 if (t == NULL)
3271 show_oops(NULL, "startpage invalid parameters");
3273 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3275 TAILQ_FOREACH(s, &spl, entry) {
3276 b = body;
3277 body = g_strdup_printf("%s%s<br>", body, s->line);
3278 g_free(b);
3281 page = get_html_page("Startup Exception", body, "", 0);
3282 g_free(body);
3284 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3285 g_free(page);
3287 return (0);
3290 void
3291 startpage_add(const char *fmt, ...)
3293 va_list ap;
3294 char *msg;
3295 struct sp *s;
3297 if (fmt == NULL)
3298 return;
3300 va_start(ap, fmt);
3301 if (vasprintf(&msg, fmt, ap) == -1)
3302 errx(1, "startpage_add failed");
3303 va_end(ap);
3305 s = g_malloc0(sizeof *s);
3306 s->line = msg;
3308 TAILQ_INSERT_TAIL(&spl, s, entry);
3312 * update all favorite tabs apart from one. Pass NULL if
3313 * you want to update all.
3315 void
3316 update_favorite_tabs(struct tab *apart_from)
3318 struct tab *t;
3319 if (!updating_fl_tabs) {
3320 updating_fl_tabs = 1; /* stop infinite recursion */
3321 TAILQ_FOREACH(t, &tabs, entry)
3322 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3323 && (t != apart_from))
3324 xtp_page_fl(t, NULL);
3325 updating_fl_tabs = 0;
3329 /* show a list of favorites (bookmarks) */
3331 xtp_page_fl(struct tab *t, struct karg *args)
3333 char file[PATH_MAX];
3334 FILE *f;
3335 char *uri = NULL, *title = NULL;
3336 size_t len, lineno = 0;
3337 int i, failed = 0;
3338 char *body, *tmp, *page = NULL;
3339 const char delim[3] = {'\\', '\\', '\0'};
3341 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3343 if (t == NULL)
3344 warn("%s: bad param", __func__);
3346 /* new session key */
3347 if (!updating_fl_tabs)
3348 generate_xtp_session_key(&fl_session_key);
3350 /* open favorites */
3351 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3352 if ((f = fopen(file, "r")) == NULL) {
3353 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3354 return (1);
3357 /* body */
3358 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3359 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3360 "<th style='width: 40px'>Rm</th></tr>\n");
3362 for (i = 1;;) {
3363 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3364 break;
3365 if (strlen(title) == 0 || title[0] == '#') {
3366 free(title);
3367 title = NULL;
3368 continue;
3371 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3372 if (feof(f) || ferror(f)) {
3373 show_oops(t, "favorites file corrupt");
3374 failed = 1;
3375 break;
3378 tmp = body;
3379 body = g_strdup_printf("%s<tr>"
3380 "<td>%d</td>"
3381 "<td><a href='%s'>%s</a></td>"
3382 "<td style='text-align: center'>"
3383 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3384 "</tr>\n",
3385 body, i, uri, title,
3386 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3388 g_free(tmp);
3390 free(uri);
3391 uri = NULL;
3392 free(title);
3393 title = NULL;
3394 i++;
3396 fclose(f);
3398 /* if none, say so */
3399 if (i == 1) {
3400 tmp = body;
3401 body = g_strdup_printf("%s<tr>"
3402 "<td colspan='3' style='text-align: center'>"
3403 "No favorites - To add one use the 'favadd' command."
3404 "</td></tr>", body);
3405 g_free(tmp);
3408 tmp = body;
3409 body = g_strdup_printf("%s</table>", body);
3410 g_free(tmp);
3412 if (uri)
3413 free(uri);
3414 if (title)
3415 free(title);
3417 /* render */
3418 if (!failed) {
3419 page = get_html_page("Favorites", body, "", 1);
3420 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3421 g_free(page);
3424 update_favorite_tabs(t);
3426 if (body)
3427 g_free(body);
3429 return (failed);
3432 void
3433 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3434 size_t cert_count, char *title)
3436 gnutls_datum_t cinfo;
3437 char *tmp, *body;
3438 int i;
3440 body = g_strdup("");
3442 for (i = 0; i < cert_count; i++) {
3443 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3444 &cinfo))
3445 return;
3447 tmp = body;
3448 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3449 body, i, cinfo.data);
3450 gnutls_free(cinfo.data);
3451 g_free(tmp);
3454 tmp = get_html_page(title, body, "", 0);
3455 g_free(body);
3457 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3458 g_free(tmp);
3462 ca_cmd(struct tab *t, struct karg *args)
3464 FILE *f = NULL;
3465 int rv = 1, certs = 0, certs_read;
3466 struct stat sb;
3467 gnutls_datum_t dt;
3468 gnutls_x509_crt_t *c = NULL;
3469 char *certs_buf = NULL, *s;
3471 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3472 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3473 return (1);
3476 if (fstat(fileno(f), &sb) == -1) {
3477 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3478 goto done;
3481 certs_buf = g_malloc(sb.st_size + 1);
3482 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3483 show_oops(t, "Can't read CA file: %s", strerror(errno));
3484 goto done;
3486 certs_buf[sb.st_size] = '\0';
3488 s = certs_buf;
3489 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3490 certs++;
3491 s += strlen("BEGIN CERTIFICATE");
3494 bzero(&dt, sizeof dt);
3495 dt.data = (unsigned char *)certs_buf;
3496 dt.size = sb.st_size;
3497 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3498 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3499 GNUTLS_X509_FMT_PEM, 0);
3500 if (certs_read <= 0) {
3501 show_oops(t, "No cert(s) available");
3502 goto done;
3504 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3505 done:
3506 if (c)
3507 g_free(c);
3508 if (certs_buf)
3509 g_free(certs_buf);
3510 if (f)
3511 fclose(f);
3513 return (rv);
3517 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3518 size_t domain_sz)
3520 SoupURI *su = NULL;
3521 struct addrinfo hints, *res = NULL, *ai;
3522 int rv = -1, s = -1, on, error;
3523 char port[8];
3524 static gchar myerror[256]; /* this is not thread safe */
3526 myerror[0] = '\0';
3527 *error_str = myerror;
3528 if (uri && !g_str_has_prefix(uri, "https://")) {
3529 *error_str = "invalid URI";
3530 goto done;
3533 su = soup_uri_new(uri);
3534 if (su == NULL) {
3535 *error_str = "invalid soup URI";
3536 goto done;
3538 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3539 *error_str = "invalid HTTPS URI";
3540 goto done;
3543 snprintf(port, sizeof port, "%d", su->port);
3544 bzero(&hints, sizeof(struct addrinfo));
3545 hints.ai_flags = AI_CANONNAME;
3546 hints.ai_family = AF_UNSPEC;
3547 hints.ai_socktype = SOCK_STREAM;
3549 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3550 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3551 gai_strerror(errno));
3552 goto done;
3555 for (ai = res; ai; ai = ai->ai_next) {
3556 if (s != -1) {
3557 close(s);
3558 s = -1;
3561 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3562 continue;
3563 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3564 if (s == -1)
3565 continue;
3566 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3567 sizeof(on)) == -1)
3568 continue;
3569 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3570 break;
3572 if (s == -1) {
3573 snprintf(myerror, sizeof myerror,
3574 "could not obtain certificates from: %s",
3575 su->host);
3576 goto done;
3579 if (domain)
3580 strlcpy(domain, su->host, domain_sz);
3581 rv = s;
3582 done:
3583 if (su)
3584 soup_uri_free(su);
3585 if (res)
3586 freeaddrinfo(res);
3587 if (rv == -1 && s != -1)
3588 close(s);
3590 return (rv);
3594 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3596 if (gsession)
3597 gnutls_deinit(gsession);
3598 if (xcred)
3599 gnutls_certificate_free_credentials(xcred);
3601 return (0);
3605 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3606 gnutls_certificate_credentials_t *xc)
3608 gnutls_certificate_credentials_t xcred;
3609 gnutls_session_t gsession;
3610 int rv = 1;
3611 static gchar myerror[1024]; /* this is not thread safe */
3613 if (gs == NULL || xc == NULL)
3614 goto done;
3616 myerror[0] = '\0';
3617 *gs = NULL;
3618 *xc = NULL;
3620 gnutls_certificate_allocate_credentials(&xcred);
3621 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3622 GNUTLS_X509_FMT_PEM);
3624 gnutls_init(&gsession, GNUTLS_CLIENT);
3625 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3626 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3627 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3628 if ((rv = gnutls_handshake(gsession)) < 0) {
3629 snprintf(myerror, sizeof myerror,
3630 "gnutls_handshake failed %d fatal %d %s",
3632 gnutls_error_is_fatal(rv),
3633 gnutls_strerror_name(rv));
3634 stop_tls(gsession, xcred);
3635 goto done;
3638 gnutls_credentials_type_t cred;
3639 cred = gnutls_auth_get_type(gsession);
3640 if (cred != GNUTLS_CRD_CERTIFICATE) {
3641 snprintf(myerror, sizeof myerror,
3642 "gnutls_auth_get_type failed %d",
3643 (int)cred);
3644 stop_tls(gsession, xcred);
3645 goto done;
3648 *gs = gsession;
3649 *xc = xcred;
3650 rv = 0;
3651 done:
3652 *error_str = myerror;
3653 return (rv);
3657 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3658 size_t *cert_count)
3660 unsigned int len;
3661 const gnutls_datum_t *cl;
3662 gnutls_x509_crt_t *all_certs;
3663 int i, rv = 1;
3665 if (certs == NULL || cert_count == NULL)
3666 goto done;
3667 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3668 goto done;
3669 cl = gnutls_certificate_get_peers(gsession, &len);
3670 if (len == 0)
3671 goto done;
3673 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3674 for (i = 0; i < len; i++) {
3675 gnutls_x509_crt_init(&all_certs[i]);
3676 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3677 GNUTLS_X509_FMT_PEM < 0)) {
3678 g_free(all_certs);
3679 goto done;
3683 *certs = all_certs;
3684 *cert_count = len;
3685 rv = 0;
3686 done:
3687 return (rv);
3690 void
3691 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3693 int i;
3695 for (i = 0; i < cert_count; i++)
3696 gnutls_x509_crt_deinit(certs[i]);
3697 g_free(certs);
3700 void
3701 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3703 GdkColor c_text, c_base;
3705 gdk_color_parse(text, &c_text);
3706 gdk_color_parse(base, &c_base);
3708 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3709 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3710 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3711 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3713 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3714 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3715 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3716 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3719 void
3720 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3721 size_t cert_count, char *domain)
3723 size_t cert_buf_sz;
3724 char cert_buf[64 * 1024], file[PATH_MAX];
3725 int i;
3726 FILE *f;
3727 GdkColor color;
3729 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3730 return;
3732 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3733 if ((f = fopen(file, "w")) == NULL) {
3734 show_oops(t, "Can't create cert file %s %s",
3735 file, strerror(errno));
3736 return;
3739 for (i = 0; i < cert_count; i++) {
3740 cert_buf_sz = sizeof cert_buf;
3741 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3742 cert_buf, &cert_buf_sz)) {
3743 show_oops(t, "gnutls_x509_crt_export failed");
3744 goto done;
3746 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3747 show_oops(t, "Can't write certs: %s", strerror(errno));
3748 goto done;
3752 /* not the best spot but oh well */
3753 gdk_color_parse("lightblue", &color);
3754 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3755 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3756 done:
3757 fclose(f);
3760 enum cert_trust {
3761 CERT_LOCAL,
3762 CERT_TRUSTED,
3763 CERT_UNTRUSTED,
3764 CERT_BAD
3767 enum cert_trust
3768 load_compare_cert(const gchar *uri, const gchar **error_str)
3770 char domain[8182], file[PATH_MAX];
3771 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3772 int s = -1, i;
3773 unsigned int error = 0;
3774 FILE *f = NULL;
3775 size_t cert_buf_sz, cert_count;
3776 enum cert_trust rv = CERT_UNTRUSTED;
3777 static gchar serr[80]; /* this isn't thread safe */
3778 gnutls_session_t gsession;
3779 gnutls_x509_crt_t *certs;
3780 gnutls_certificate_credentials_t xcred;
3782 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3784 serr[0] = '\0';
3785 *error_str = serr;
3786 if ((s = connect_socket_from_uri(uri, error_str, domain,
3787 sizeof domain)) == -1)
3788 return (rv);
3790 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3792 /* go ssl/tls */
3793 if (start_tls(error_str, s, &gsession, &xcred))
3794 goto done;
3795 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3797 /* verify certs in case cert file doesn't exist */
3798 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3799 GNUTLS_E_SUCCESS) {
3800 *error_str = "Invalid certificates";
3801 goto done;
3804 /* get certs */
3805 if (get_connection_certs(gsession, &certs, &cert_count)) {
3806 *error_str = "Can't get connection certificates";
3807 goto done;
3810 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3811 if ((f = fopen(file, "r")) == NULL) {
3812 if (!error)
3813 rv = CERT_TRUSTED;
3814 goto freeit;
3817 for (i = 0; i < cert_count; i++) {
3818 cert_buf_sz = sizeof cert_buf;
3819 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3820 cert_buf, &cert_buf_sz)) {
3821 goto freeit;
3823 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3824 rv = CERT_BAD; /* critical */
3825 goto freeit;
3827 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3828 rv = CERT_BAD; /* critical */
3829 goto freeit;
3831 rv = CERT_LOCAL;
3834 freeit:
3835 if (f)
3836 fclose(f);
3837 free_connection_certs(certs, cert_count);
3838 done:
3839 /* we close the socket first for speed */
3840 if (s != -1)
3841 close(s);
3843 /* only complain if we didn't save it locally */
3844 if (error && rv != CERT_LOCAL) {
3845 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3846 if (error & GNUTLS_CERT_INVALID)
3847 strlcat(serr, "invalid, ", sizeof serr);
3848 if (error & GNUTLS_CERT_REVOKED)
3849 strlcat(serr, "revoked, ", sizeof serr);
3850 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3851 strlcat(serr, "signer not found, ", sizeof serr);
3852 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3853 strlcat(serr, "not signed by CA, ", sizeof serr);
3854 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3855 strlcat(serr, "insecure algorithm, ", sizeof serr);
3856 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3857 strlcat(serr, "not activated, ", sizeof serr);
3858 if (error & GNUTLS_CERT_EXPIRED)
3859 strlcat(serr, "expired, ", sizeof serr);
3860 for (i = strlen(serr) - 1; i > 0; i--)
3861 if (serr[i] == ',') {
3862 serr[i] = '\0';
3863 break;
3865 *error_str = serr;
3868 stop_tls(gsession, xcred);
3870 return (rv);
3874 cert_cmd(struct tab *t, struct karg *args)
3876 const gchar *uri, *error_str = NULL;
3877 char domain[8182];
3878 int s = -1;
3879 size_t cert_count;
3880 gnutls_session_t gsession;
3881 gnutls_x509_crt_t *certs;
3882 gnutls_certificate_credentials_t xcred;
3884 if (t == NULL)
3885 return (1);
3887 if (ssl_ca_file == NULL) {
3888 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3889 return (1);
3892 if ((uri = get_uri(t)) == NULL) {
3893 show_oops(t, "Invalid URI");
3894 return (1);
3897 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3898 sizeof domain)) == -1) {
3899 show_oops(t, "%s", error_str);
3900 return (1);
3903 /* go ssl/tls */
3904 if (start_tls(&error_str, s, &gsession, &xcred))
3905 goto done;
3907 /* get certs */
3908 if (get_connection_certs(gsession, &certs, &cert_count)) {
3909 show_oops(t, "get_connection_certs failed");
3910 goto done;
3913 if (args->i & XT_SHOW)
3914 show_certs(t, certs, cert_count, "Certificate Chain");
3915 else if (args->i & XT_SAVE)
3916 save_certs(t, certs, cert_count, domain);
3918 free_connection_certs(certs, cert_count);
3919 done:
3920 /* we close the socket first for speed */
3921 if (s != -1)
3922 close(s);
3923 stop_tls(gsession, xcred);
3924 if (error_str && strlen(error_str))
3925 show_oops(t, "%s", error_str);
3926 return (0);
3930 remove_cookie(int index)
3932 int i, rv = 1;
3933 GSList *cf;
3934 SoupCookie *c;
3936 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3938 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3940 for (i = 1; cf; cf = cf->next, i++) {
3941 if (i != index)
3942 continue;
3943 c = cf->data;
3944 print_cookie("remove cookie", c);
3945 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3946 rv = 0;
3947 break;
3950 soup_cookies_free(cf);
3952 return (rv);
3956 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3958 struct domain *d;
3959 char *tmp, *body;
3961 body = g_strdup("");
3963 /* p list */
3964 if (args->i & XT_WL_PERSISTENT) {
3965 tmp = body;
3966 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3967 g_free(tmp);
3968 RB_FOREACH(d, domain_list, wl) {
3969 if (d->handy == 0)
3970 continue;
3971 tmp = body;
3972 body = g_strdup_printf("%s%s<br/>", body, d->d);
3973 g_free(tmp);
3977 /* s list */
3978 if (args->i & XT_WL_SESSION) {
3979 tmp = body;
3980 body = g_strdup_printf("%s<h2>Session</h2>", body);
3981 g_free(tmp);
3982 RB_FOREACH(d, domain_list, wl) {
3983 if (d->handy == 1)
3984 continue;
3985 tmp = body;
3986 body = g_strdup_printf("%s%s<br/>", body, d->d);
3987 g_free(tmp);
3991 tmp = get_html_page(title, body, "", 0);
3992 g_free(body);
3993 if (wl == &js_wl)
3994 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3995 else if (wl == &c_wl)
3996 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3997 else
3998 load_webkit_string(t, tmp, XT_URI_ABOUT_PLUGINWL);
3999 g_free(tmp);
4000 return (0);
4003 #define XT_WL_INVALID (0)
4004 #define XT_WL_JAVASCRIPT (1)
4005 #define XT_WL_COOKIE (2)
4006 #define XT_WL_PLUGIN (3)
4008 wl_save(struct tab *t, struct karg *args, int list)
4010 char file[PATH_MAX], *lst_str = NULL;
4011 FILE *f;
4012 char *line = NULL, *lt = NULL, *dom = NULL;
4013 size_t linelen;
4014 const gchar *uri;
4015 struct karg a;
4016 struct domain *d;
4017 GSList *cf;
4018 SoupCookie *ci, *c;
4020 if (t == NULL || args == NULL)
4021 return (1);
4023 if (runtime_settings[0] == '\0')
4024 return (1);
4026 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
4027 if ((f = fopen(file, "r+")) == NULL)
4028 return (1);
4030 switch (list) {
4031 case XT_WL_JAVASCRIPT:
4032 lst_str = "JavaScript";
4033 lt = g_strdup_printf("js_wl=%s", dom);
4034 break;
4035 case XT_WL_COOKIE:
4036 lst_str = "Cookie";
4037 lt = g_strdup_printf("cookie_wl=%s", dom);
4038 break;
4039 case XT_WL_PLUGIN:
4040 lst_str = "Plugin";
4041 lt = g_strdup_printf("pl_wl=%s", dom);
4042 break;
4043 default:
4044 show_oops(t, "Invalid list id: %d", list);
4045 return (1);
4048 uri = get_uri(t);
4049 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
4050 if (uri == NULL || dom == NULL ||
4051 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
4052 show_oops(t, "Can't add domain to %s white list", lst_str);
4053 goto done;
4056 while (!feof(f)) {
4057 line = fparseln(f, &linelen, NULL, NULL, 0);
4058 if (line == NULL)
4059 continue;
4060 if (!strcmp(line, lt))
4061 goto done;
4062 free(line);
4063 line = NULL;
4066 fprintf(f, "%s\n", lt);
4068 a.i = XT_WL_ENABLE;
4069 a.i |= args->i;
4070 switch (list) {
4071 case XT_WL_JAVASCRIPT:
4072 d = wl_find(dom, &js_wl);
4073 if (!d) {
4074 settings_add("js_wl", dom);
4075 d = wl_find(dom, &js_wl);
4077 toggle_js(t, &a);
4078 break;
4080 case XT_WL_COOKIE:
4081 d = wl_find(dom, &c_wl);
4082 if (!d) {
4083 settings_add("cookie_wl", dom);
4084 d = wl_find(dom, &c_wl);
4086 toggle_cwl(t, &a);
4088 /* find and add to persistent jar */
4089 cf = soup_cookie_jar_all_cookies(s_cookiejar);
4090 for (;cf; cf = cf->next) {
4091 ci = cf->data;
4092 if (!strcmp(dom, ci->domain) ||
4093 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
4094 c = soup_cookie_copy(ci);
4095 _soup_cookie_jar_add_cookie(p_cookiejar, c);
4098 soup_cookies_free(cf);
4099 break;
4101 case XT_WL_PLUGIN:
4102 d = wl_find(dom, &pl_wl);
4103 if (!d) {
4104 settings_add("pl_wl", dom);
4105 d = wl_find(dom, &pl_wl);
4107 toggle_pl(t, &a);
4108 break;
4109 default:
4110 abort(); /* can't happen */
4112 if (d)
4113 d->handy = 1;
4115 done:
4116 if (line)
4117 free(line);
4118 if (dom)
4119 g_free(dom);
4120 if (lt)
4121 g_free(lt);
4122 fclose(f);
4124 return (0);
4128 js_show_wl(struct tab *t, struct karg *args)
4130 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
4131 wl_show(t, args, "JavaScript White List", &js_wl);
4133 return (0);
4137 cookie_show_wl(struct tab *t, struct karg *args)
4139 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
4140 wl_show(t, args, "Cookie White List", &c_wl);
4142 return (0);
4146 cookie_cmd(struct tab *t, struct karg *args)
4148 if (args->i & XT_SHOW)
4149 wl_show(t, args, "Cookie White List", &c_wl);
4150 else if (args->i & XT_WL_TOGGLE) {
4151 args->i |= XT_WL_RELOAD;
4152 toggle_cwl(t, args);
4153 } else if (args->i & XT_SAVE) {
4154 args->i |= XT_WL_RELOAD;
4155 wl_save(t, args, XT_WL_COOKIE);
4156 } else if (args->i & XT_DELETE)
4157 show_oops(t, "'cookie delete' currently unimplemented");
4159 return (0);
4163 js_cmd(struct tab *t, struct karg *args)
4165 if (args->i & XT_SHOW)
4166 wl_show(t, args, "JavaScript White List", &js_wl);
4167 else if (args->i & XT_SAVE) {
4168 args->i |= XT_WL_RELOAD;
4169 wl_save(t, args, XT_WL_JAVASCRIPT);
4170 } else if (args->i & XT_WL_TOGGLE) {
4171 args->i |= XT_WL_RELOAD;
4172 toggle_js(t, args);
4173 } else if (args->i & XT_DELETE)
4174 show_oops(t, "'js delete' currently unimplemented");
4176 return (0);
4180 pl_show_wl(struct tab *t, struct karg *args)
4182 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
4183 wl_show(t, args, "Plugin White List", &pl_wl);
4185 return (0);
4189 pl_cmd(struct tab *t, struct karg *args)
4191 if (args->i & XT_SHOW)
4192 wl_show(t, args, "Plugin White List", &pl_wl);
4193 else if (args->i & XT_SAVE) {
4194 args->i |= XT_WL_RELOAD;
4195 wl_save(t, args, XT_WL_PLUGIN);
4196 } else if (args->i & XT_WL_TOGGLE) {
4197 args->i |= XT_WL_RELOAD;
4198 toggle_pl(t, args);
4199 } else if (args->i & XT_DELETE)
4200 show_oops(t, "'plugin delete' currently unimplemented");
4202 return (0);
4206 toplevel_cmd(struct tab *t, struct karg *args)
4208 js_toggle_cb(t->js_toggle, t);
4210 return (0);
4214 add_favorite(struct tab *t, struct karg *args)
4216 char file[PATH_MAX];
4217 FILE *f;
4218 char *line = NULL;
4219 size_t urilen, linelen;
4220 const gchar *uri, *title;
4222 if (t == NULL)
4223 return (1);
4225 /* don't allow adding of xtp pages to favorites */
4226 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4227 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4228 return (1);
4231 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4232 if ((f = fopen(file, "r+")) == NULL) {
4233 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4234 return (1);
4237 title = get_title(t, FALSE);
4238 uri = get_uri(t);
4240 if (title == NULL || uri == NULL) {
4241 show_oops(t, "can't add page to favorites");
4242 goto done;
4245 urilen = strlen(uri);
4247 for (;;) {
4248 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4249 if (feof(f) || ferror(f))
4250 break;
4252 if (linelen == urilen && !strcmp(line, uri))
4253 goto done;
4255 free(line);
4256 line = NULL;
4259 fprintf(f, "\n%s\n%s", title, uri);
4260 done:
4261 if (line)
4262 free(line);
4263 fclose(f);
4265 update_favorite_tabs(NULL);
4267 return (0);
4271 can_go_back_for_real(struct tab *t)
4273 int i;
4274 WebKitWebHistoryItem *item;
4275 const gchar *uri;
4277 if (t == NULL)
4278 return (FALSE);
4280 /* rely on webkit to make sure we can go backward when on an about page */
4281 uri = get_uri(t);
4282 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4283 return (webkit_web_view_can_go_back(t->wv));
4285 /* the back/forwars list is stupid so help determine if we can go back */
4286 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4287 item != NULL;
4288 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4289 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4290 return (TRUE);
4293 return (FALSE);
4297 can_go_forward_for_real(struct tab *t)
4299 int i;
4300 WebKitWebHistoryItem *item;
4301 const gchar *uri;
4303 if (t == NULL)
4304 return (FALSE);
4306 /* rely on webkit to make sure we can go forward when on an about page */
4307 uri = get_uri(t);
4308 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4309 return (webkit_web_view_can_go_forward(t->wv));
4311 /* the back/forwars list is stupid so help selecting a different item */
4312 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4313 item != NULL;
4314 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4315 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4316 return (TRUE);
4319 return (FALSE);
4322 void
4323 go_back_for_real(struct tab *t)
4325 int i;
4326 WebKitWebHistoryItem *item;
4327 const gchar *uri;
4329 if (t == NULL)
4330 return;
4332 uri = get_uri(t);
4333 if (uri == NULL) {
4334 webkit_web_view_go_back(t->wv);
4335 return;
4337 /* the back/forwars list is stupid so help selecting a different item */
4338 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4339 item != NULL;
4340 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4341 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4342 webkit_web_view_go_to_back_forward_item(t->wv, item);
4343 break;
4348 void
4349 go_forward_for_real(struct tab *t)
4351 int i;
4352 WebKitWebHistoryItem *item;
4353 const gchar *uri;
4355 if (t == NULL)
4356 return;
4358 uri = get_uri(t);
4359 if (uri == NULL) {
4360 webkit_web_view_go_forward(t->wv);
4361 return;
4363 /* the back/forwars list is stupid so help selecting a different item */
4364 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4365 item != NULL;
4366 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4367 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4368 webkit_web_view_go_to_back_forward_item(t->wv, item);
4369 break;
4375 navaction(struct tab *t, struct karg *args)
4377 WebKitWebHistoryItem *item;
4378 WebKitWebFrame *frame;
4380 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4381 t->tab_id, args->i);
4383 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4384 if (t->item) {
4385 if (args->i == XT_NAV_BACK)
4386 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4387 else
4388 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4389 if (item == NULL)
4390 return (XT_CB_PASSTHROUGH);
4391 webkit_web_view_go_to_back_forward_item(t->wv, item);
4392 t->item = NULL;
4393 return (XT_CB_PASSTHROUGH);
4396 switch (args->i) {
4397 case XT_NAV_BACK:
4398 marks_clear(t);
4399 go_back_for_real(t);
4400 break;
4401 case XT_NAV_FORWARD:
4402 marks_clear(t);
4403 go_forward_for_real(t);
4404 break;
4405 case XT_NAV_RELOAD:
4406 frame = webkit_web_view_get_main_frame(t->wv);
4407 webkit_web_frame_reload(frame);
4408 break;
4410 return (XT_CB_PASSTHROUGH);
4414 move(struct tab *t, struct karg *args)
4416 GtkAdjustment *adjust;
4417 double pi, si, pos, ps, upper, lower, max;
4418 double percent;
4420 switch (args->i) {
4421 case XT_MOVE_DOWN:
4422 case XT_MOVE_UP:
4423 case XT_MOVE_BOTTOM:
4424 case XT_MOVE_TOP:
4425 case XT_MOVE_PAGEDOWN:
4426 case XT_MOVE_PAGEUP:
4427 case XT_MOVE_HALFDOWN:
4428 case XT_MOVE_HALFUP:
4429 case XT_MOVE_PERCENT:
4430 adjust = t->adjust_v;
4431 break;
4432 default:
4433 adjust = t->adjust_h;
4434 break;
4437 pos = gtk_adjustment_get_value(adjust);
4438 ps = gtk_adjustment_get_page_size(adjust);
4439 upper = gtk_adjustment_get_upper(adjust);
4440 lower = gtk_adjustment_get_lower(adjust);
4441 si = gtk_adjustment_get_step_increment(adjust);
4442 pi = gtk_adjustment_get_page_increment(adjust);
4443 max = upper - ps;
4445 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4446 "max %f si %f pi %f\n",
4447 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4448 pos, ps, upper, lower, max, si, pi);
4450 switch (args->i) {
4451 case XT_MOVE_DOWN:
4452 case XT_MOVE_RIGHT:
4453 pos += si;
4454 gtk_adjustment_set_value(adjust, MIN(pos, max));
4455 break;
4456 case XT_MOVE_UP:
4457 case XT_MOVE_LEFT:
4458 pos -= si;
4459 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4460 break;
4461 case XT_MOVE_BOTTOM:
4462 case XT_MOVE_FARRIGHT:
4463 gtk_adjustment_set_value(adjust, max);
4464 break;
4465 case XT_MOVE_TOP:
4466 case XT_MOVE_FARLEFT:
4467 gtk_adjustment_set_value(adjust, lower);
4468 break;
4469 case XT_MOVE_PAGEDOWN:
4470 pos += pi;
4471 gtk_adjustment_set_value(adjust, MIN(pos, max));
4472 break;
4473 case XT_MOVE_PAGEUP:
4474 pos -= pi;
4475 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4476 break;
4477 case XT_MOVE_HALFDOWN:
4478 pos += pi / 2;
4479 gtk_adjustment_set_value(adjust, MIN(pos, max));
4480 break;
4481 case XT_MOVE_HALFUP:
4482 pos -= pi / 2;
4483 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4484 break;
4485 case XT_MOVE_PERCENT:
4486 percent = atoi(args->s) / 100.0;
4487 pos = max * percent;
4488 if (pos < 0.0 || pos > max)
4489 break;
4490 gtk_adjustment_set_value(adjust, pos);
4491 break;
4492 default:
4493 return (XT_CB_PASSTHROUGH);
4496 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4498 return (XT_CB_HANDLED);
4501 void
4502 url_set_visibility(void)
4504 struct tab *t;
4506 TAILQ_FOREACH(t, &tabs, entry)
4507 if (show_url == 0) {
4508 gtk_widget_hide(t->toolbar);
4509 focus_webview(t);
4510 } else
4511 gtk_widget_show(t->toolbar);
4514 void
4515 notebook_tab_set_visibility(void)
4517 if (show_tabs == 0) {
4518 gtk_widget_hide(tab_bar);
4519 gtk_notebook_set_show_tabs(notebook, FALSE);
4520 } else {
4521 if (tab_style == XT_TABS_NORMAL) {
4522 gtk_widget_hide(tab_bar);
4523 gtk_notebook_set_show_tabs(notebook, TRUE);
4524 } else if (tab_style == XT_TABS_COMPACT) {
4525 gtk_widget_show(tab_bar);
4526 gtk_notebook_set_show_tabs(notebook, FALSE);
4531 void
4532 statusbar_set_visibility(void)
4534 struct tab *t;
4536 TAILQ_FOREACH(t, &tabs, entry)
4537 if (show_statusbar == 0) {
4538 gtk_widget_hide(t->statusbar_box);
4539 focus_webview(t);
4540 } else
4541 gtk_widget_show(t->statusbar_box);
4544 void
4545 url_set(struct tab *t, int enable_url_entry)
4547 GdkPixbuf *pixbuf;
4548 int progress;
4550 show_url = enable_url_entry;
4552 if (enable_url_entry) {
4553 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4554 GTK_ENTRY_ICON_PRIMARY, NULL);
4555 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4556 } else {
4557 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4558 GTK_ENTRY_ICON_PRIMARY);
4559 progress =
4560 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4561 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4562 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4563 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4564 progress);
4569 fullscreen(struct tab *t, struct karg *args)
4571 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4573 if (t == NULL)
4574 return (XT_CB_PASSTHROUGH);
4576 if (show_url == 0) {
4577 url_set(t, 1);
4578 show_tabs = 1;
4579 } else {
4580 url_set(t, 0);
4581 show_tabs = 0;
4584 url_set_visibility();
4585 notebook_tab_set_visibility();
4587 return (XT_CB_HANDLED);
4591 statustoggle(struct tab *t, struct karg *args)
4593 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4595 if (show_statusbar == 1) {
4596 show_statusbar = 0;
4597 statusbar_set_visibility();
4598 } else if (show_statusbar == 0) {
4599 show_statusbar = 1;
4600 statusbar_set_visibility();
4602 return (XT_CB_HANDLED);
4606 urlaction(struct tab *t, struct karg *args)
4608 int rv = XT_CB_HANDLED;
4610 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4612 if (t == NULL)
4613 return (XT_CB_PASSTHROUGH);
4615 switch (args->i) {
4616 case XT_URL_SHOW:
4617 if (show_url == 0) {
4618 url_set(t, 1);
4619 url_set_visibility();
4621 break;
4622 case XT_URL_HIDE:
4623 if (show_url == 1) {
4624 url_set(t, 0);
4625 url_set_visibility();
4627 break;
4629 return (rv);
4633 tabaction(struct tab *t, struct karg *args)
4635 int rv = XT_CB_HANDLED;
4636 char *url = args->s;
4637 struct undo *u;
4638 struct tab *tt;
4640 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4642 if (t == NULL)
4643 return (XT_CB_PASSTHROUGH);
4645 switch (args->i) {
4646 case XT_TAB_NEW:
4647 if (strlen(url) > 0)
4648 create_new_tab(url, NULL, 1, args->precount);
4649 else
4650 create_new_tab(NULL, NULL, 1, args->precount);
4651 break;
4652 case XT_TAB_DELETE:
4653 if (args->precount < 0)
4654 delete_tab(t);
4655 else
4656 TAILQ_FOREACH(tt, &tabs, entry)
4657 if (tt->tab_id == args->precount - 1) {
4658 delete_tab(tt);
4659 break;
4661 break;
4662 case XT_TAB_DELQUIT:
4663 if (gtk_notebook_get_n_pages(notebook) > 1)
4664 delete_tab(t);
4665 else
4666 quit(t, args);
4667 break;
4668 case XT_TAB_OPEN:
4669 if (strlen(url) > 0)
4671 else {
4672 rv = XT_CB_PASSTHROUGH;
4673 goto done;
4675 load_uri(t, url);
4676 break;
4677 case XT_TAB_SHOW:
4678 if (show_tabs == 0) {
4679 show_tabs = 1;
4680 notebook_tab_set_visibility();
4682 break;
4683 case XT_TAB_HIDE:
4684 if (show_tabs == 1) {
4685 show_tabs = 0;
4686 notebook_tab_set_visibility();
4688 break;
4689 case XT_TAB_NEXTSTYLE:
4690 if (tab_style == XT_TABS_NORMAL) {
4691 tab_style = XT_TABS_COMPACT;
4692 recolor_compact_tabs();
4694 else
4695 tab_style = XT_TABS_NORMAL;
4696 notebook_tab_set_visibility();
4697 break;
4698 case XT_TAB_UNDO_CLOSE:
4699 if (undo_count == 0) {
4700 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4701 __func__);
4702 goto done;
4703 } else {
4704 undo_count--;
4705 u = TAILQ_FIRST(&undos);
4706 create_new_tab(u->uri, u, 1, -1);
4708 TAILQ_REMOVE(&undos, u, entry);
4709 g_free(u->uri);
4710 /* u->history is freed in create_new_tab() */
4711 g_free(u);
4713 break;
4714 default:
4715 rv = XT_CB_PASSTHROUGH;
4716 goto done;
4719 done:
4720 if (args->s) {
4721 g_free(args->s);
4722 args->s = NULL;
4725 return (rv);
4729 resizetab(struct tab *t, struct karg *args)
4731 if (t == NULL || args == NULL) {
4732 show_oops(NULL, "resizetab invalid parameters");
4733 return (XT_CB_PASSTHROUGH);
4736 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4737 t->tab_id, args->i);
4739 setzoom_webkit(t, args->i);
4741 return (XT_CB_HANDLED);
4745 movetab(struct tab *t, struct karg *args)
4747 int n, dest;
4749 if (t == NULL || args == NULL) {
4750 show_oops(NULL, "movetab invalid parameters");
4751 return (XT_CB_PASSTHROUGH);
4754 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4755 t->tab_id, args->i);
4757 if (args->i >= XT_TAB_INVALID)
4758 return (XT_CB_PASSTHROUGH);
4760 if (TAILQ_EMPTY(&tabs))
4761 return (XT_CB_PASSTHROUGH);
4763 n = gtk_notebook_get_n_pages(notebook);
4764 dest = gtk_notebook_get_current_page(notebook);
4766 switch (args->i) {
4767 case XT_TAB_NEXT:
4768 if (args->precount < 0)
4769 dest = dest == n - 1 ? 0 : dest + 1;
4770 else
4771 dest = args->precount - 1;
4773 break;
4774 case XT_TAB_PREV:
4775 if (args->precount < 0)
4776 dest -= 1;
4777 else
4778 dest -= args->precount % n;
4780 if (dest < 0)
4781 dest += n;
4783 break;
4784 case XT_TAB_FIRST:
4785 dest = 0;
4786 break;
4787 case XT_TAB_LAST:
4788 dest = n - 1;
4789 break;
4790 default:
4791 return (XT_CB_PASSTHROUGH);
4794 if (dest < 0 || dest >= n)
4795 return (XT_CB_PASSTHROUGH);
4796 if (t->tab_id == dest) {
4797 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4798 return (XT_CB_HANDLED);
4801 set_current_tab(dest);
4803 return (XT_CB_HANDLED);
4806 int cmd_prefix = 0;
4809 command(struct tab *t, struct karg *args)
4811 char *s = NULL, *ss = NULL;
4812 GdkColor color;
4813 const gchar *uri;
4815 if (t == NULL || args == NULL) {
4816 show_oops(NULL, "command invalid parameters");
4817 return (XT_CB_PASSTHROUGH);
4820 switch (args->i) {
4821 case '/':
4822 s = "/";
4823 break;
4824 case '?':
4825 s = "?";
4826 break;
4827 case ':':
4828 if (cmd_prefix == 0)
4829 s = ":";
4830 else {
4831 ss = g_strdup_printf(":%d", cmd_prefix);
4832 s = ss;
4833 cmd_prefix = 0;
4835 break;
4836 case XT_CMD_OPEN:
4837 s = ":open ";
4838 break;
4839 case XT_CMD_TABNEW:
4840 s = ":tabnew ";
4841 break;
4842 case XT_CMD_OPEN_CURRENT:
4843 s = ":open ";
4844 /* FALL THROUGH */
4845 case XT_CMD_TABNEW_CURRENT:
4846 if (!s) /* FALL THROUGH? */
4847 s = ":tabnew ";
4848 if ((uri = get_uri(t)) != NULL) {
4849 ss = g_strdup_printf("%s%s", s, uri);
4850 s = ss;
4852 break;
4853 default:
4854 show_oops(t, "command: invalid opcode %d", args->i);
4855 return (XT_CB_PASSTHROUGH);
4858 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4860 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4861 gdk_color_parse(XT_COLOR_WHITE, &color);
4862 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4863 show_cmd(t);
4864 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4865 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4867 if (ss)
4868 g_free(ss);
4870 return (XT_CB_HANDLED);
4874 * Return a new string with a download row (in html)
4875 * appended. Old string is freed.
4877 char *
4878 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4881 WebKitDownloadStatus stat;
4882 char *status_html = NULL, *cmd_html = NULL, *new_html;
4883 gdouble progress;
4884 char cur_sz[FMT_SCALED_STRSIZE];
4885 char tot_sz[FMT_SCALED_STRSIZE];
4886 char *xtp_prefix;
4888 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4890 /* All actions wil take this form:
4891 * xxxt://class/seskey
4893 xtp_prefix = g_strdup_printf("%s%d/%s/",
4894 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4896 stat = webkit_download_get_status(dl->download);
4898 switch (stat) {
4899 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4900 status_html = g_strdup_printf("Finished");
4901 cmd_html = g_strdup_printf(
4902 "<a href='%s%d/%d'>Remove</a>",
4903 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4904 break;
4905 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4906 /* gather size info */
4907 progress = 100 * webkit_download_get_progress(dl->download);
4909 fmt_scaled(
4910 webkit_download_get_current_size(dl->download), cur_sz);
4911 fmt_scaled(
4912 webkit_download_get_total_size(dl->download), tot_sz);
4914 status_html = g_strdup_printf(
4915 "<div style='width: 100%%' align='center'>"
4916 "<div class='progress-outer'>"
4917 "<div class='progress-inner' style='width: %.2f%%'>"
4918 "</div></div></div>"
4919 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4920 progress, cur_sz, tot_sz, progress);
4922 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4923 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4925 break;
4926 /* LLL */
4927 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4928 status_html = g_strdup_printf("Cancelled");
4929 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4930 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4931 break;
4932 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4933 status_html = g_strdup_printf("Error!");
4934 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4935 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4936 break;
4937 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4938 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4939 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4940 status_html = g_strdup_printf("Starting");
4941 break;
4942 default:
4943 show_oops(t, "%s: unknown download status", __func__);
4946 new_html = g_strdup_printf(
4947 "%s\n<tr><td>%s</td><td>%s</td>"
4948 "<td style='text-align:center'>%s</td></tr>\n",
4949 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4950 status_html, cmd_html);
4951 g_free(html);
4953 if (status_html)
4954 g_free(status_html);
4956 if (cmd_html)
4957 g_free(cmd_html);
4959 g_free(xtp_prefix);
4961 return new_html;
4965 * update all download tabs apart from one. Pass NULL if
4966 * you want to update all.
4968 void
4969 update_download_tabs(struct tab *apart_from)
4971 struct tab *t;
4972 if (!updating_dl_tabs) {
4973 updating_dl_tabs = 1; /* stop infinite recursion */
4974 TAILQ_FOREACH(t, &tabs, entry)
4975 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4976 && (t != apart_from))
4977 xtp_page_dl(t, NULL);
4978 updating_dl_tabs = 0;
4983 * update all cookie tabs apart from one. Pass NULL if
4984 * you want to update all.
4986 void
4987 update_cookie_tabs(struct tab *apart_from)
4989 struct tab *t;
4990 if (!updating_cl_tabs) {
4991 updating_cl_tabs = 1; /* stop infinite recursion */
4992 TAILQ_FOREACH(t, &tabs, entry)
4993 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4994 && (t != apart_from))
4995 xtp_page_cl(t, NULL);
4996 updating_cl_tabs = 0;
5001 * update all history tabs apart from one. Pass NULL if
5002 * you want to update all.
5004 void
5005 update_history_tabs(struct tab *apart_from)
5007 struct tab *t;
5009 if (!updating_hl_tabs) {
5010 updating_hl_tabs = 1; /* stop infinite recursion */
5011 TAILQ_FOREACH(t, &tabs, entry)
5012 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
5013 && (t != apart_from))
5014 xtp_page_hl(t, NULL);
5015 updating_hl_tabs = 0;
5019 /* cookie management XTP page */
5021 xtp_page_cl(struct tab *t, struct karg *args)
5023 char *body, *page, *tmp;
5024 int i = 1; /* all ids start 1 */
5025 GSList *sc, *pc, *pc_start;
5026 SoupCookie *c;
5027 char *type, *table_headers, *last_domain;
5029 DNPRINTF(XT_D_CMD, "%s", __func__);
5031 if (t == NULL) {
5032 show_oops(NULL, "%s invalid parameters", __func__);
5033 return (1);
5036 /* Generate a new session key */
5037 if (!updating_cl_tabs)
5038 generate_xtp_session_key(&cl_session_key);
5040 /* table headers */
5041 table_headers = g_strdup_printf("<table><tr>"
5042 "<th>Type</th>"
5043 "<th>Name</th>"
5044 "<th style='width:200px'>Value</th>"
5045 "<th>Path</th>"
5046 "<th>Expires</th>"
5047 "<th>Secure</th>"
5048 "<th>HTTP<br />only</th>"
5049 "<th style='width:40px'>Rm</th></tr>\n");
5051 sc = soup_cookie_jar_all_cookies(s_cookiejar);
5052 pc = soup_cookie_jar_all_cookies(p_cookiejar);
5053 pc_start = pc;
5055 body = NULL;
5056 last_domain = strdup("");
5057 for (; sc; sc = sc->next) {
5058 c = sc->data;
5060 if (strcmp(last_domain, c->domain) != 0) {
5061 /* new domain */
5062 free(last_domain);
5063 last_domain = strdup(c->domain);
5065 if (body != NULL) {
5066 tmp = body;
5067 body = g_strdup_printf("%s</table>"
5068 "<h2>%s</h2>%s\n",
5069 body, c->domain, table_headers);
5070 g_free(tmp);
5071 } else {
5072 /* first domain */
5073 body = g_strdup_printf("<h2>%s</h2>%s\n",
5074 c->domain, table_headers);
5078 type = "Session";
5079 for (pc = pc_start; pc; pc = pc->next)
5080 if (soup_cookie_equal(pc->data, c)) {
5081 type = "Session + Persistent";
5082 break;
5085 tmp = body;
5086 body = g_strdup_printf(
5087 "%s\n<tr>"
5088 "<td>%s</td>"
5089 "<td style='word-wrap:normal'>%s</td>"
5090 "<td>"
5091 " <textarea rows='4'>%s</textarea>"
5092 "</td>"
5093 "<td>%s</td>"
5094 "<td>%s</td>"
5095 "<td>%d</td>"
5096 "<td>%d</td>"
5097 "<td style='text-align:center'>"
5098 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
5099 body,
5100 type,
5101 c->name,
5102 c->value,
5103 c->path,
5104 c->expires ?
5105 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
5106 c->secure,
5107 c->http_only,
5109 XT_XTP_STR,
5110 XT_XTP_CL,
5111 cl_session_key,
5112 XT_XTP_CL_REMOVE,
5116 g_free(tmp);
5117 i++;
5120 soup_cookies_free(sc);
5121 soup_cookies_free(pc);
5123 /* small message if there are none */
5124 if (i == 1) {
5125 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5126 "colspan='8'>No Cookies</td></tr>\n", table_headers);
5128 tmp = body;
5129 body = g_strdup_printf("%s</table>", body);
5130 g_free(tmp);
5132 page = get_html_page("Cookie Jar", body, "", TRUE);
5133 g_free(body);
5134 g_free(table_headers);
5135 g_free(last_domain);
5137 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
5138 update_cookie_tabs(t);
5140 g_free(page);
5142 return (0);
5146 xtp_page_hl(struct tab *t, struct karg *args)
5148 char *body, *page, *tmp;
5149 struct history *h;
5150 int i = 1; /* all ids start 1 */
5152 DNPRINTF(XT_D_CMD, "%s", __func__);
5154 if (t == NULL) {
5155 show_oops(NULL, "%s invalid parameters", __func__);
5156 return (1);
5159 /* Generate a new session key */
5160 if (!updating_hl_tabs)
5161 generate_xtp_session_key(&hl_session_key);
5163 /* body */
5164 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
5165 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
5167 RB_FOREACH_REVERSE(h, history_list, &hl) {
5168 tmp = body;
5169 body = g_strdup_printf(
5170 "%s\n<tr>"
5171 "<td><a href='%s'>%s</a></td>"
5172 "<td>%s</td>"
5173 "<td style='text-align: center'>"
5174 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
5175 body, h->uri, h->uri, h->title,
5176 XT_XTP_STR, XT_XTP_HL, hl_session_key,
5177 XT_XTP_HL_REMOVE, i);
5179 g_free(tmp);
5180 i++;
5183 /* small message if there are none */
5184 if (i == 1) {
5185 tmp = body;
5186 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5187 "colspan='3'>No History</td></tr>\n", body);
5188 g_free(tmp);
5191 tmp = body;
5192 body = g_strdup_printf("%s</table>", body);
5193 g_free(tmp);
5195 page = get_html_page("History", body, "", TRUE);
5196 g_free(body);
5199 * update all history manager tabs as the xtp session
5200 * key has now changed. No need to update the current tab.
5201 * Already did that above.
5203 update_history_tabs(t);
5205 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5206 g_free(page);
5208 return (0);
5212 * Generate a web page detailing the status of any downloads
5215 xtp_page_dl(struct tab *t, struct karg *args)
5217 struct download *dl;
5218 char *body, *page, *tmp;
5219 char *ref;
5220 int n_dl = 1;
5222 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5224 if (t == NULL) {
5225 show_oops(NULL, "%s invalid parameters", __func__);
5226 return (1);
5230 * Generate a new session key for next page instance.
5231 * This only happens for the top level call to xtp_page_dl()
5232 * in which case updating_dl_tabs is 0.
5234 if (!updating_dl_tabs)
5235 generate_xtp_session_key(&dl_session_key);
5237 /* header - with refresh so as to update */
5238 if (refresh_interval >= 1)
5239 ref = g_strdup_printf(
5240 "<meta http-equiv='refresh' content='%u"
5241 ";url=%s%d/%s/%d' />\n",
5242 refresh_interval,
5243 XT_XTP_STR,
5244 XT_XTP_DL,
5245 dl_session_key,
5246 XT_XTP_DL_LIST);
5247 else
5248 ref = g_strdup("");
5250 body = g_strdup_printf("<div align='center'>"
5251 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5252 "</p><table><tr><th style='width: 60%%'>"
5253 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5254 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5256 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5257 body = xtp_page_dl_row(t, body, dl);
5258 n_dl++;
5261 /* message if no downloads in list */
5262 if (n_dl == 1) {
5263 tmp = body;
5264 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5265 " style='text-align: center'>"
5266 "No downloads</td></tr>\n", body);
5267 g_free(tmp);
5270 tmp = body;
5271 body = g_strdup_printf("%s</table></div>", body);
5272 g_free(tmp);
5274 page = get_html_page("Downloads", body, ref, 1);
5275 g_free(ref);
5276 g_free(body);
5279 * update all download manager tabs as the xtp session
5280 * key has now changed. No need to update the current tab.
5281 * Already did that above.
5283 update_download_tabs(t);
5285 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5286 g_free(page);
5288 return (0);
5292 search(struct tab *t, struct karg *args)
5294 gboolean d;
5296 if (t == NULL || args == NULL) {
5297 show_oops(NULL, "search invalid parameters");
5298 return (1);
5301 switch (args->i) {
5302 case XT_SEARCH_NEXT:
5303 d = t->search_forward;
5304 break;
5305 case XT_SEARCH_PREV:
5306 d = !t->search_forward;
5307 break;
5308 default:
5309 return (XT_CB_PASSTHROUGH);
5312 if (t->search_text == NULL) {
5313 if (global_search == NULL)
5314 return (XT_CB_PASSTHROUGH);
5315 else {
5316 d = t->search_forward = TRUE;
5317 t->search_text = g_strdup(global_search);
5318 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5319 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5323 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5324 t->tab_id, args->i, t->search_forward, t->search_text);
5326 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5328 return (XT_CB_HANDLED);
5331 struct settings_args {
5332 char **body;
5333 int i;
5336 void
5337 print_setting(struct settings *s, char *val, void *cb_args)
5339 char *tmp, *color;
5340 struct settings_args *sa = cb_args;
5342 if (sa == NULL)
5343 return;
5345 if (s->flags & XT_SF_RUNTIME)
5346 color = "#22cc22";
5347 else
5348 color = "#cccccc";
5350 tmp = *sa->body;
5351 *sa->body = g_strdup_printf(
5352 "%s\n<tr>"
5353 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5354 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5355 *sa->body,
5356 color,
5357 s->name,
5358 color,
5361 g_free(tmp);
5362 sa->i++;
5366 set_show(struct tab *t, struct karg *args)
5368 char *body, *page, *tmp;
5369 int i = 1;
5370 struct settings_args sa;
5372 bzero(&sa, sizeof sa);
5373 sa.body = &body;
5375 /* body */
5376 body = g_strdup_printf("<div align='center'><table><tr>"
5377 "<th align='left'>Setting</th>"
5378 "<th align='left'>Value</th></tr>\n");
5380 settings_walk(print_setting, &sa);
5381 i = sa.i;
5383 /* small message if there are none */
5384 if (i == 1) {
5385 tmp = body;
5386 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5387 "colspan='2'>No settings</td></tr>\n", body);
5388 g_free(tmp);
5391 tmp = body;
5392 body = g_strdup_printf("%s</table></div>", body);
5393 g_free(tmp);
5395 page = get_html_page("Settings", body, "", 0);
5397 g_free(body);
5399 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5401 g_free(page);
5403 return (XT_CB_PASSTHROUGH);
5407 set(struct tab *t, struct karg *args)
5409 char *p, *val;
5410 int i;
5412 if (args == NULL || args->s == NULL)
5413 return (set_show(t, args));
5415 /* strip spaces */
5416 p = g_strstrip(args->s);
5418 if (strlen(p) == 0)
5419 return (set_show(t, args));
5421 /* we got some sort of string */
5422 val = g_strrstr(p, "=");
5423 if (val) {
5424 *val++ = '\0';
5425 val = g_strchomp(val);
5426 p = g_strchomp(p);
5428 for (i = 0; i < LENGTH(rs); i++) {
5429 if (strcmp(rs[i].name, p))
5430 continue;
5432 if (rs[i].activate) {
5433 if (rs[i].activate(val))
5434 show_oops(t, "%s invalid value %s",
5435 p, val);
5436 else
5437 show_oops(t, ":set %s = %s", p, val);
5438 goto done;
5439 } else {
5440 show_oops(t, "not a runtime option: %s", p);
5441 goto done;
5444 show_oops(t, "unknown option: %s", p);
5445 } else {
5446 p = g_strchomp(p);
5448 for (i = 0; i < LENGTH(rs); i++) {
5449 if (strcmp(rs[i].name, p))
5450 continue;
5452 /* XXX this could use some cleanup */
5453 switch (rs[i].type) {
5454 case XT_S_INT:
5455 if (rs[i].ival)
5456 show_oops(t, "%s = %d",
5457 rs[i].name, *rs[i].ival);
5458 else if (rs[i].s && rs[i].s->get)
5459 show_oops(t, "%s = %s",
5460 rs[i].name,
5461 rs[i].s->get(&rs[i]));
5462 else if (rs[i].s && rs[i].s->get == NULL)
5463 show_oops(t, "%s = ...", rs[i].name);
5464 else
5465 show_oops(t, "%s = ", rs[i].name);
5466 break;
5467 case XT_S_FLOAT:
5468 if (rs[i].fval)
5469 show_oops(t, "%s = %f",
5470 rs[i].name, *rs[i].fval);
5471 else if (rs[i].s && rs[i].s->get)
5472 show_oops(t, "%s = %s",
5473 rs[i].name,
5474 rs[i].s->get(&rs[i]));
5475 else if (rs[i].s && rs[i].s->get == NULL)
5476 show_oops(t, "%s = ...", rs[i].name);
5477 else
5478 show_oops(t, "%s = ", rs[i].name);
5479 break;
5480 case XT_S_STR:
5481 if (rs[i].sval && *rs[i].sval)
5482 show_oops(t, "%s = %s",
5483 rs[i].name, *rs[i].sval);
5484 else if (rs[i].s && rs[i].s->get)
5485 show_oops(t, "%s = %s",
5486 rs[i].name,
5487 rs[i].s->get(&rs[i]));
5488 else if (rs[i].s && rs[i].s->get == NULL)
5489 show_oops(t, "%s = ...", rs[i].name);
5490 else
5491 show_oops(t, "%s = ", rs[i].name);
5492 break;
5493 default:
5494 show_oops(t, "unknown type for %s", rs[i].name);
5495 goto done;
5498 goto done;
5500 show_oops(t, "unknown option: %s", p);
5502 done:
5503 return (XT_CB_PASSTHROUGH);
5507 session_save(struct tab *t, char *filename)
5509 struct karg a;
5510 int rv = 1;
5511 struct session *s;
5513 if (strlen(filename) == 0)
5514 goto done;
5516 if (filename[0] == '.' || filename[0] == '/')
5517 goto done;
5519 a.s = filename;
5520 if (save_tabs(t, &a))
5521 goto done;
5522 strlcpy(named_session, filename, sizeof named_session);
5524 /* add the new session to the list of sessions */
5525 s = g_malloc(sizeof(struct session));
5526 s->name = g_strdup(filename);
5527 TAILQ_INSERT_TAIL(&sessions, s, entry);
5529 rv = 0;
5530 done:
5531 return (rv);
5535 session_open(struct tab *t, char *filename)
5537 struct karg a;
5538 int rv = 1;
5540 if (strlen(filename) == 0)
5541 goto done;
5543 if (filename[0] == '.' || filename[0] == '/')
5544 goto done;
5546 a.s = filename;
5547 a.i = XT_SES_CLOSETABS;
5548 if (open_tabs(t, &a))
5549 goto done;
5551 strlcpy(named_session, filename, sizeof named_session);
5553 rv = 0;
5554 done:
5555 return (rv);
5559 session_delete(struct tab *t, char *filename)
5561 char file[PATH_MAX];
5562 int rv = 1;
5563 struct session *s;
5565 if (strlen(filename) == 0)
5566 goto done;
5568 if (filename[0] == '.' || filename[0] == '/')
5569 goto done;
5571 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5572 if (unlink(file))
5573 goto done;
5575 if (!strcmp(filename, named_session))
5576 strlcpy(named_session, XT_SAVED_TABS_FILE,
5577 sizeof named_session);
5579 /* remove session from sessions list */
5580 TAILQ_FOREACH(s, &sessions, entry) {
5581 if (!strcmp(s->name, filename))
5582 break;
5584 if (s == NULL)
5585 goto done;
5586 TAILQ_REMOVE(&sessions, s, entry);
5587 g_free((gpointer) s->name);
5588 g_free(s);
5590 rv = 0;
5591 done:
5592 return (rv);
5596 session_cmd(struct tab *t, struct karg *args)
5598 char *filename = args->s;
5600 if (t == NULL)
5601 return (1);
5603 if (args->i & XT_SHOW)
5604 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5605 XT_SAVED_TABS_FILE : named_session);
5606 else if (args->i & XT_SAVE) {
5607 if (session_save(t, filename)) {
5608 show_oops(t, "Can't save session: %s",
5609 filename ? filename : "INVALID");
5610 goto done;
5612 } else if (args->i & XT_OPEN) {
5613 if (session_open(t, filename)) {
5614 show_oops(t, "Can't open session: %s",
5615 filename ? filename : "INVALID");
5616 goto done;
5618 } else if (args->i & XT_DELETE) {
5619 if (session_delete(t, filename)) {
5620 show_oops(t, "Can't delete session: %s",
5621 filename ? filename : "INVALID");
5622 goto done;
5625 done:
5626 return (XT_CB_PASSTHROUGH);
5630 * Make a hardcopy of the page
5633 print_page(struct tab *t, struct karg *args)
5635 WebKitWebFrame *frame;
5636 GtkPageSetup *ps;
5637 GtkPrintOperation *op;
5638 GtkPrintOperationAction action;
5639 GtkPrintOperationResult print_res;
5640 GError *g_err = NULL;
5641 int marg_l, marg_r, marg_t, marg_b;
5643 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5645 ps = gtk_page_setup_new();
5646 op = gtk_print_operation_new();
5647 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5648 frame = webkit_web_view_get_main_frame(t->wv);
5650 /* the default margins are too small, so we will bump them */
5651 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5652 XT_PRINT_EXTRA_MARGIN;
5653 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5654 XT_PRINT_EXTRA_MARGIN;
5655 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5656 XT_PRINT_EXTRA_MARGIN;
5657 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5658 XT_PRINT_EXTRA_MARGIN;
5660 /* set margins */
5661 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5662 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5663 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5664 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5666 gtk_print_operation_set_default_page_setup(op, ps);
5668 /* this appears to free 'op' and 'ps' */
5669 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5671 /* check it worked */
5672 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5673 show_oops(NULL, "can't print: %s", g_err->message);
5674 g_error_free (g_err);
5675 return (1);
5678 return (0);
5682 go_home(struct tab *t, struct karg *args)
5684 load_uri(t, home);
5685 return (0);
5689 set_encoding(struct tab *t, struct karg *args)
5691 const gchar *e;
5693 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5694 e = webkit_web_view_get_custom_encoding(t->wv);
5695 if (e == NULL)
5696 e = webkit_web_view_get_encoding(t->wv);
5697 show_oops(t, "encoding: %s", e ? e : "N/A");
5698 } else
5699 webkit_web_view_set_custom_encoding(t->wv, args->s);
5701 return (0);
5705 restart(struct tab *t, struct karg *args)
5707 struct karg a;
5709 a.s = XT_RESTART_TABS_FILE;
5710 save_tabs(t, &a);
5711 execvp(start_argv[0], start_argv);
5712 /* NOTREACHED */
5714 return (0);
5717 #define CTRL GDK_CONTROL_MASK
5718 #define MOD1 GDK_MOD1_MASK
5719 #define SHFT GDK_SHIFT_MASK
5721 /* inherent to GTK not all keys will be caught at all times */
5722 /* XXX sort key bindings */
5723 struct key_binding {
5724 char *cmd;
5725 guint mask;
5726 guint use_in_entry;
5727 guint key;
5728 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5729 } keys[] = {
5730 { "cookiejar", MOD1, 0, GDK_j },
5731 { "downloadmgr", MOD1, 0, GDK_d },
5732 { "history", MOD1, 0, GDK_h },
5733 { "print", CTRL, 0, GDK_p },
5734 { "search", 0, 0, GDK_slash },
5735 { "searchb", 0, 0, GDK_question },
5736 { "statustoggle", CTRL, 0, GDK_n },
5737 { "command", 0, 0, GDK_colon },
5738 { "qa", CTRL, 0, GDK_q },
5739 { "restart", MOD1, 0, GDK_q },
5740 { "js toggle", CTRL, 0, GDK_j },
5741 { "cookie toggle", MOD1, 0, GDK_c },
5742 { "togglesrc", CTRL, 0, GDK_s },
5743 { "yankuri", 0, 0, GDK_y },
5744 { "pasteuricur", 0, 0, GDK_p },
5745 { "pasteurinew", 0, 0, GDK_P },
5746 { "toplevel toggle", 0, 0, GDK_F4 },
5747 { "help", 0, 0, GDK_F1 },
5748 { "run_script", MOD1, 0, GDK_r },
5750 /* search */
5751 { "searchnext", 0, 0, GDK_n },
5752 { "searchprevious", 0, 0, GDK_N },
5754 /* focus */
5755 { "focusaddress", 0, 0, GDK_F6 },
5756 { "focussearch", 0, 0, GDK_F7 },
5758 /* hinting */
5759 { "hinting", 0, 0, GDK_f },
5760 { "hinting_newtab", SHFT, 0, GDK_F },
5762 /* custom stylesheet */
5763 { "userstyle", 0, 0, GDK_i },
5765 /* navigation */
5766 { "goback", 0, 0, GDK_BackSpace },
5767 { "goback", MOD1, 0, GDK_Left },
5768 { "goforward", SHFT, 0, GDK_BackSpace },
5769 { "goforward", MOD1, 0, GDK_Right },
5770 { "reload", 0, 0, GDK_F5 },
5771 { "reload", CTRL, 0, GDK_r },
5772 { "reload", CTRL, 0, GDK_l },
5773 { "favorites", MOD1, 1, GDK_f },
5775 /* vertical movement */
5776 { "scrolldown", 0, 0, GDK_j },
5777 { "scrolldown", 0, 0, GDK_Down },
5778 { "scrollup", 0, 0, GDK_Up },
5779 { "scrollup", 0, 0, GDK_k },
5780 { "scrollbottom", 0, 0, GDK_G },
5781 { "scrollbottom", 0, 0, GDK_End },
5782 { "scrolltop", 0, 0, GDK_Home },
5783 { "scrollpagedown", 0, 0, GDK_space },
5784 { "scrollpagedown", CTRL, 0, GDK_f },
5785 { "scrollhalfdown", CTRL, 0, GDK_d },
5786 { "scrollpagedown", 0, 0, GDK_Page_Down },
5787 { "scrollpageup", 0, 0, GDK_Page_Up },
5788 { "scrollpageup", CTRL, 0, GDK_b },
5789 { "scrollhalfup", CTRL, 0, GDK_u },
5790 /* horizontal movement */
5791 { "scrollright", 0, 0, GDK_l },
5792 { "scrollright", 0, 0, GDK_Right },
5793 { "scrollleft", 0, 0, GDK_Left },
5794 { "scrollleft", 0, 0, GDK_h },
5795 { "scrollfarright", 0, 0, GDK_dollar },
5796 { "scrollfarleft", 0, 0, GDK_0 },
5798 /* tabs */
5799 { "tabnew", CTRL, 0, GDK_t },
5800 { "999tabnew", CTRL, 0, GDK_T },
5801 { "tabclose", CTRL, 1, GDK_w },
5802 { "tabundoclose", 0, 0, GDK_U },
5803 { "tabnext 1", CTRL, 0, GDK_1 },
5804 { "tabnext 2", CTRL, 0, GDK_2 },
5805 { "tabnext 3", CTRL, 0, GDK_3 },
5806 { "tabnext 4", CTRL, 0, GDK_4 },
5807 { "tabnext 5", CTRL, 0, GDK_5 },
5808 { "tabnext 6", CTRL, 0, GDK_6 },
5809 { "tabnext 7", CTRL, 0, GDK_7 },
5810 { "tabnext 8", CTRL, 0, GDK_8 },
5811 { "tabnext 9", CTRL, 0, GDK_9 },
5812 { "tabfirst", CTRL, 0, GDK_less },
5813 { "tablast", CTRL, 0, GDK_greater },
5814 { "tabprevious", CTRL, 0, GDK_Left },
5815 { "tabnext", CTRL, 0, GDK_Right },
5816 { "focusout", CTRL, 0, GDK_minus },
5817 { "focusin", CTRL, 0, GDK_plus },
5818 { "focusin", CTRL, 0, GDK_equal },
5819 { "focusreset", CTRL, 0, GDK_0 },
5821 /* command aliases (handy when -S flag is used) */
5822 { "promptopen", 0, 0, GDK_F9 },
5823 { "promptopencurrent", 0, 0, GDK_F10 },
5824 { "prompttabnew", 0, 0, GDK_F11 },
5825 { "prompttabnewcurrent",0, 0, GDK_F12 },
5827 TAILQ_HEAD(keybinding_list, key_binding);
5829 void
5830 walk_kb(struct settings *s,
5831 void (*cb)(struct settings *, char *, void *), void *cb_args)
5833 struct key_binding *k;
5834 char str[1024];
5836 if (s == NULL || cb == NULL) {
5837 show_oops(NULL, "walk_kb invalid parameters");
5838 return;
5841 TAILQ_FOREACH(k, &kbl, entry) {
5842 if (k->cmd == NULL)
5843 continue;
5844 str[0] = '\0';
5846 /* sanity */
5847 if (gdk_keyval_name(k->key) == NULL)
5848 continue;
5850 strlcat(str, k->cmd, sizeof str);
5851 strlcat(str, ",", sizeof str);
5853 if (k->mask & GDK_SHIFT_MASK)
5854 strlcat(str, "S-", sizeof str);
5855 if (k->mask & GDK_CONTROL_MASK)
5856 strlcat(str, "C-", sizeof str);
5857 if (k->mask & GDK_MOD1_MASK)
5858 strlcat(str, "M1-", sizeof str);
5859 if (k->mask & GDK_MOD2_MASK)
5860 strlcat(str, "M2-", sizeof str);
5861 if (k->mask & GDK_MOD3_MASK)
5862 strlcat(str, "M3-", sizeof str);
5863 if (k->mask & GDK_MOD4_MASK)
5864 strlcat(str, "M4-", sizeof str);
5865 if (k->mask & GDK_MOD5_MASK)
5866 strlcat(str, "M5-", sizeof str);
5868 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5869 cb(s, str, cb_args);
5873 void
5874 init_keybindings(void)
5876 int i;
5877 struct key_binding *k;
5879 for (i = 0; i < LENGTH(keys); i++) {
5880 k = g_malloc0(sizeof *k);
5881 k->cmd = keys[i].cmd;
5882 k->mask = keys[i].mask;
5883 k->use_in_entry = keys[i].use_in_entry;
5884 k->key = keys[i].key;
5885 TAILQ_INSERT_HEAD(&kbl, k, entry);
5887 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5888 k->cmd ? k->cmd : "unnamed key");
5892 void
5893 keybinding_clearall(void)
5895 struct key_binding *k, *next;
5897 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5898 next = TAILQ_NEXT(k, entry);
5899 if (k->cmd == NULL)
5900 continue;
5902 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5903 k->cmd ? k->cmd : "unnamed key");
5904 TAILQ_REMOVE(&kbl, k, entry);
5905 g_free(k);
5910 keybinding_add(char *cmd, char *key, int use_in_entry)
5912 struct key_binding *k;
5913 guint keyval, mask = 0;
5914 int i;
5916 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5918 /* Keys which are to be used in entry have been prefixed with an
5919 * exclamation mark. */
5920 if (use_in_entry)
5921 key++;
5923 /* find modifier keys */
5924 if (strstr(key, "S-"))
5925 mask |= GDK_SHIFT_MASK;
5926 if (strstr(key, "C-"))
5927 mask |= GDK_CONTROL_MASK;
5928 if (strstr(key, "M1-"))
5929 mask |= GDK_MOD1_MASK;
5930 if (strstr(key, "M2-"))
5931 mask |= GDK_MOD2_MASK;
5932 if (strstr(key, "M3-"))
5933 mask |= GDK_MOD3_MASK;
5934 if (strstr(key, "M4-"))
5935 mask |= GDK_MOD4_MASK;
5936 if (strstr(key, "M5-"))
5937 mask |= GDK_MOD5_MASK;
5939 /* find keyname */
5940 for (i = strlen(key) - 1; i > 0; i--)
5941 if (key[i] == '-')
5942 key = &key[i + 1];
5944 /* validate keyname */
5945 keyval = gdk_keyval_from_name(key);
5946 if (keyval == GDK_VoidSymbol) {
5947 warnx("invalid keybinding name %s", key);
5948 return (1);
5950 /* must run this test too, gtk+ doesn't handle 10 for example */
5951 if (gdk_keyval_name(keyval) == NULL) {
5952 warnx("invalid keybinding name %s", key);
5953 return (1);
5956 /* Remove eventual dupes. */
5957 TAILQ_FOREACH(k, &kbl, entry)
5958 if (k->key == keyval && k->mask == mask) {
5959 TAILQ_REMOVE(&kbl, k, entry);
5960 g_free(k);
5961 break;
5964 /* add keyname */
5965 k = g_malloc0(sizeof *k);
5966 k->cmd = g_strdup(cmd);
5967 k->mask = mask;
5968 k->use_in_entry = use_in_entry;
5969 k->key = keyval;
5971 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5972 k->cmd,
5973 k->mask,
5974 k->use_in_entry,
5975 k->key);
5976 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5977 k->cmd, gdk_keyval_name(keyval));
5979 TAILQ_INSERT_HEAD(&kbl, k, entry);
5981 return (0);
5985 add_kb(struct settings *s, char *entry)
5987 char *kb, *key;
5989 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5991 /* clearall is special */
5992 if (!strcmp(entry, "clearall")) {
5993 keybinding_clearall();
5994 return (0);
5997 kb = strstr(entry, ",");
5998 if (kb == NULL)
5999 return (1);
6000 *kb = '\0';
6001 key = kb + 1;
6003 return (keybinding_add(entry, key, key[0] == '!'));
6006 struct cmd {
6007 char *cmd;
6008 int level;
6009 int (*func)(struct tab *, struct karg *);
6010 int arg;
6011 int type;
6012 } cmds[] = {
6013 { "command", 0, command, ':', 0 },
6014 { "search", 0, command, '/', 0 },
6015 { "searchb", 0, command, '?', 0 },
6016 { "togglesrc", 0, toggle_src, 0, 0 },
6018 /* yanking and pasting */
6019 { "yankuri", 0, yank_uri, 0, 0 },
6020 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
6021 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
6022 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
6024 /* search */
6025 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
6026 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
6028 /* focus */
6029 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
6030 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
6032 /* hinting */
6033 { "hinting", 0, hint, 0, 0 },
6034 { "hinting_newtab", 0, hint, XT_HINT_NEWTAB, 0 },
6036 /* custom stylesheet */
6037 { "userstyle", 0, userstyle, 0, 0 },
6039 /* navigation */
6040 { "goback", 0, navaction, XT_NAV_BACK, 0 },
6041 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
6042 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
6044 /* vertical movement */
6045 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
6046 { "scrollup", 0, move, XT_MOVE_UP, 0 },
6047 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
6048 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
6049 { "1", 0, move, XT_MOVE_TOP, 0 },
6050 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
6051 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
6052 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
6053 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
6054 /* horizontal movement */
6055 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
6056 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
6057 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
6058 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
6060 { "favorites", 0, xtp_page_fl, 0, 0 },
6061 { "fav", 0, xtp_page_fl, 0, 0 },
6062 { "favadd", 0, add_favorite, 0, 0 },
6064 { "qall", 0, quit, 0, 0 },
6065 { "quitall", 0, quit, 0, 0 },
6066 { "w", 0, save_tabs, 0, 0 },
6067 { "wq", 0, save_tabs_and_quit, 0, 0 },
6068 { "help", 0, help, 0, 0 },
6069 { "about", 0, about, 0, 0 },
6070 { "stats", 0, stats, 0, 0 },
6071 { "version", 0, about, 0, 0 },
6073 /* js command */
6074 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6075 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6076 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
6077 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6078 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6079 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6080 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
6081 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6082 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6083 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6084 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6086 /* cookie command */
6087 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6088 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6089 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
6090 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6091 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6092 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6093 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
6094 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6095 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6096 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6097 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6099 /* plugin command */
6100 { "plugin", 0, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6101 { "save", 1, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6102 { "domain", 2, pl_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
6103 { "fqdn", 2, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
6104 { "show", 1, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6105 { "all", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
6106 { "persistent", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
6107 { "session", 2, pl_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6108 { "toggle", 1, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6109 { "domain", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6110 { "fqdn", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6112 /* toplevel (domain) command */
6113 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
6114 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
6116 /* cookie jar */
6117 { "cookiejar", 0, xtp_page_cl, 0, 0 },
6119 /* cert command */
6120 { "cert", 0, cert_cmd, XT_SHOW, 0 },
6121 { "save", 1, cert_cmd, XT_SAVE, 0 },
6122 { "show", 1, cert_cmd, XT_SHOW, 0 },
6124 { "ca", 0, ca_cmd, 0, 0 },
6125 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
6126 { "dl", 0, xtp_page_dl, 0, 0 },
6127 { "h", 0, xtp_page_hl, 0, 0 },
6128 { "history", 0, xtp_page_hl, 0, 0 },
6129 { "home", 0, go_home, 0, 0 },
6130 { "restart", 0, restart, 0, 0 },
6131 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
6132 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
6133 { "statustoggle", 0, statustoggle, 0, 0 },
6134 { "run_script", 0, run_page_script, 0, XT_USERARG },
6136 { "print", 0, print_page, 0, 0 },
6138 /* tabs */
6139 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
6140 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
6141 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
6142 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
6143 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
6144 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
6145 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
6146 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
6147 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
6148 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
6149 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
6150 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
6151 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
6152 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
6153 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
6154 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
6155 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
6156 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
6157 { "buffers", 0, buffers, 0, 0 },
6158 { "ls", 0, buffers, 0, 0 },
6159 { "tabs", 0, buffers, 0, 0 },
6160 { "encoding", 0, set_encoding, 0, XT_USERARG },
6162 /* command aliases (handy when -S flag is used) */
6163 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
6164 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
6165 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
6166 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
6168 /* settings */
6169 { "set", 0, set, 0, XT_SETARG },
6171 { "fullscreen", 0, fullscreen, 0, 0 },
6172 { "f", 0, fullscreen, 0, 0 },
6174 /* sessions */
6175 { "session", 0, session_cmd, XT_SHOW, 0 },
6176 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
6177 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
6178 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
6179 { "show", 1, session_cmd, XT_SHOW, 0 },
6182 struct {
6183 int index;
6184 int len;
6185 gchar *list[256];
6186 } cmd_status = {-1, 0};
6188 gboolean
6189 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6192 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
6193 btn_down = 0;
6195 return (FALSE);
6198 gboolean
6199 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6201 struct karg a;
6203 hide_oops(t);
6204 hide_buffers(t);
6206 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6207 btn_down = 1;
6208 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6209 /* go backward */
6210 a.i = XT_NAV_BACK;
6211 navaction(t, &a);
6213 return (TRUE);
6214 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6215 /* go forward */
6216 a.i = XT_NAV_FORWARD;
6217 navaction(t, &a);
6219 return (TRUE);
6222 return (FALSE);
6225 gboolean
6226 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6228 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6230 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6231 delete_tab(t);
6233 return (FALSE);
6237 * cancel, remove, etc. downloads
6239 void
6240 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6242 struct download find, *d = NULL;
6244 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6246 /* some commands require a valid download id */
6247 if (cmd != XT_XTP_DL_LIST) {
6248 /* lookup download in question */
6249 find.id = id;
6250 d = RB_FIND(download_list, &downloads, &find);
6252 if (d == NULL) {
6253 show_oops(t, "%s: no such download", __func__);
6254 return;
6258 /* decide what to do */
6259 switch (cmd) {
6260 case XT_XTP_DL_CANCEL:
6261 webkit_download_cancel(d->download);
6262 break;
6263 case XT_XTP_DL_REMOVE:
6264 webkit_download_cancel(d->download); /* just incase */
6265 g_object_unref(d->download);
6266 RB_REMOVE(download_list, &downloads, d);
6267 break;
6268 case XT_XTP_DL_LIST:
6269 /* Nothing */
6270 break;
6271 default:
6272 show_oops(t, "%s: unknown command", __func__);
6273 break;
6275 xtp_page_dl(t, NULL);
6279 * Actions on history, only does one thing for now, but
6280 * we provide the function for future actions
6282 void
6283 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6285 struct history *h, *next;
6286 int i = 1;
6288 switch (cmd) {
6289 case XT_XTP_HL_REMOVE:
6290 /* walk backwards, as listed in reverse */
6291 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6292 next = RB_PREV(history_list, &hl, h);
6293 if (id == i) {
6294 RB_REMOVE(history_list, &hl, h);
6295 g_free((gpointer) h->title);
6296 g_free((gpointer) h->uri);
6297 g_free(h);
6298 break;
6300 i++;
6302 break;
6303 case XT_XTP_HL_LIST:
6304 /* Nothing - just xtp_page_hl() below */
6305 break;
6306 default:
6307 show_oops(t, "%s: unknown command", __func__);
6308 break;
6311 xtp_page_hl(t, NULL);
6314 /* remove a favorite */
6315 void
6316 remove_favorite(struct tab *t, int index)
6318 char file[PATH_MAX], *title, *uri = NULL;
6319 char *new_favs, *tmp;
6320 FILE *f;
6321 int i;
6322 size_t len, lineno;
6324 /* open favorites */
6325 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6327 if ((f = fopen(file, "r")) == NULL) {
6328 show_oops(t, "%s: can't open favorites: %s",
6329 __func__, strerror(errno));
6330 return;
6333 /* build a string which will become the new favroites file */
6334 new_favs = g_strdup("");
6336 for (i = 1;;) {
6337 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6338 if (feof(f) || ferror(f))
6339 break;
6340 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6341 if (len == 0) {
6342 free(title);
6343 title = NULL;
6344 continue;
6347 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6348 if (feof(f) || ferror(f)) {
6349 show_oops(t, "%s: can't parse favorites %s",
6350 __func__, strerror(errno));
6351 goto clean;
6355 /* as long as this isn't the one we are deleting add to file */
6356 if (i != index) {
6357 tmp = new_favs;
6358 new_favs = g_strdup_printf("%s%s\n%s\n",
6359 new_favs, title, uri);
6360 g_free(tmp);
6363 free(uri);
6364 uri = NULL;
6365 free(title);
6366 title = NULL;
6367 i++;
6369 fclose(f);
6371 /* write back new favorites file */
6372 if ((f = fopen(file, "w")) == NULL) {
6373 show_oops(t, "%s: can't open favorites: %s",
6374 __func__, strerror(errno));
6375 goto clean;
6378 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
6379 show_oops(t, "%s: can't fwrite"); /* shut gcc up */
6380 fclose(f);
6382 clean:
6383 if (uri)
6384 free(uri);
6385 if (title)
6386 free(title);
6388 g_free(new_favs);
6391 void
6392 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6394 switch (cmd) {
6395 case XT_XTP_FL_LIST:
6396 /* nothing, just the below call to xtp_page_fl() */
6397 break;
6398 case XT_XTP_FL_REMOVE:
6399 remove_favorite(t, arg);
6400 break;
6401 default:
6402 show_oops(t, "%s: invalid favorites command", __func__);
6403 break;
6406 xtp_page_fl(t, NULL);
6409 void
6410 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6412 switch (cmd) {
6413 case XT_XTP_CL_LIST:
6414 /* nothing, just xtp_page_cl() */
6415 break;
6416 case XT_XTP_CL_REMOVE:
6417 remove_cookie(arg);
6418 break;
6419 default:
6420 show_oops(t, "%s: unknown cookie xtp command", __func__);
6421 break;
6424 xtp_page_cl(t, NULL);
6427 /* link an XTP class to it's session key and handler function */
6428 struct xtp_despatch {
6429 uint8_t xtp_class;
6430 char **session_key;
6431 void (*handle_func)(struct tab *, uint8_t, int);
6434 struct xtp_despatch xtp_despatches[] = {
6435 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6436 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6437 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6438 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6439 { XT_XTP_INVALID, NULL, NULL }
6443 * is the url xtp protocol? (xxxt://)
6444 * if so, parse and despatch correct bahvior
6447 parse_xtp_url(struct tab *t, const char *url)
6449 char *dup = NULL, *p, *last = NULL;
6450 uint8_t n_tokens = 0;
6451 char *tokens[4] = {NULL, NULL, NULL, ""};
6452 struct xtp_despatch *dsp, *dsp_match = NULL;
6453 uint8_t req_class;
6454 int ret = FALSE;
6457 * tokens array meaning:
6458 * tokens[0] = class
6459 * tokens[1] = session key
6460 * tokens[2] = action
6461 * tokens[3] = optional argument
6464 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6466 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6467 goto clean;
6469 dup = g_strdup(url + strlen(XT_XTP_STR));
6471 /* split out the url */
6472 for ((p = strtok_r(dup, "/", &last)); p;
6473 (p = strtok_r(NULL, "/", &last))) {
6474 if (n_tokens < 4)
6475 tokens[n_tokens++] = p;
6478 /* should be atleast three fields 'class/seskey/command/arg' */
6479 if (n_tokens < 3)
6480 goto clean;
6482 dsp = xtp_despatches;
6483 req_class = atoi(tokens[0]);
6484 while (dsp->xtp_class) {
6485 if (dsp->xtp_class == req_class) {
6486 dsp_match = dsp;
6487 break;
6489 dsp++;
6492 /* did we find one atall? */
6493 if (dsp_match == NULL) {
6494 show_oops(t, "%s: no matching xtp despatch found", __func__);
6495 goto clean;
6498 /* check session key and call despatch function */
6499 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6500 ret = TRUE; /* all is well, this was a valid xtp request */
6501 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6504 clean:
6505 if (dup)
6506 g_free(dup);
6508 return (ret);
6513 void
6514 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6516 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6518 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6520 if (t == NULL) {
6521 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6522 return;
6525 if (uri == NULL) {
6526 show_oops(t, "activate_uri_entry_cb no uri");
6527 return;
6530 uri += strspn(uri, "\t ");
6532 /* if xxxt:// treat specially */
6533 if (parse_xtp_url(t, uri))
6534 return;
6536 /* otherwise continue to load page normally */
6537 load_uri(t, (gchar *)uri);
6538 focus_webview(t);
6541 void
6542 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6544 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6545 char *newuri = NULL;
6546 gchar *enc_search;
6548 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6550 if (t == NULL) {
6551 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6552 return;
6555 if (search_string == NULL) {
6556 show_oops(t, "no search_string");
6557 return;
6560 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6562 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6563 newuri = g_strdup_printf(search_string, enc_search);
6564 g_free(enc_search);
6566 marks_clear(t);
6567 webkit_web_view_load_uri(t->wv, newuri);
6568 focus_webview(t);
6570 if (newuri)
6571 g_free(newuri);
6574 void
6575 check_and_set_cookie(const gchar *uri, struct tab *t)
6577 struct domain *d = NULL;
6578 int es = 0;
6580 if (uri == NULL || t == NULL)
6581 return;
6583 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6584 es = 0;
6585 else
6586 es = 1;
6588 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6589 es ? "enable" : "disable", uri);
6591 g_object_set(G_OBJECT(t->settings),
6592 "enable-html5-local-storage", es, (char *)NULL);
6593 webkit_web_view_set_settings(t->wv, t->settings);
6596 void
6597 check_and_set_js(const gchar *uri, struct tab *t)
6599 struct domain *d = NULL;
6600 int es = 0;
6602 if (uri == NULL || t == NULL)
6603 return;
6605 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6606 es = 0;
6607 else
6608 es = 1;
6610 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6611 es ? "enable" : "disable", uri);
6613 g_object_set(G_OBJECT(t->settings),
6614 "enable-scripts", es, (char *)NULL);
6615 g_object_set(G_OBJECT(t->settings),
6616 "javascript-can-open-windows-automatically", es, (char *)NULL);
6617 webkit_web_view_set_settings(t->wv, t->settings);
6619 button_set_stockid(t->js_toggle,
6620 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6623 void
6624 check_and_set_pl(const gchar *uri, struct tab *t)
6626 struct domain *d = NULL;
6627 int es = 0;
6629 if (uri == NULL || t == NULL)
6630 return;
6632 if ((d = wl_find_uri(uri, &pl_wl)) == NULL)
6633 es = 0;
6634 else
6635 es = 1;
6637 DNPRINTF(XT_D_JS, "check_and_set_pl: %s %s\n",
6638 es ? "enable" : "disable", uri);
6640 g_object_set(G_OBJECT(t->settings),
6641 "enable-plugins", es, (char *)NULL);
6642 webkit_web_view_set_settings(t->wv, t->settings);
6645 void
6646 color_address_bar(gpointer p)
6648 GdkColor color;
6649 struct tab *tt, *t = p;
6650 gchar *col_str = XT_COLOR_WHITE;
6651 const gchar *uri, *u = NULL, *error_str = NULL;
6653 #ifdef USE_THREADS
6654 gdk_threads_enter();
6655 #endif
6656 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6658 /* make sure t still exists */
6659 if (t == NULL)
6660 return;
6661 TAILQ_FOREACH(tt, &tabs, entry)
6662 if (t == tt)
6663 break;
6664 if (t != tt)
6665 goto done;
6667 if ((uri = get_uri(t)) == NULL)
6668 goto white;
6669 u = g_strdup(uri);
6671 #ifdef USE_THREADS
6672 gdk_threads_leave();
6673 #endif
6675 col_str = XT_COLOR_YELLOW;
6676 switch (load_compare_cert(u, &error_str)) {
6677 case CERT_LOCAL:
6678 col_str = XT_COLOR_BLUE;
6679 break;
6680 case CERT_TRUSTED:
6681 col_str = XT_COLOR_GREEN;
6682 break;
6683 case CERT_UNTRUSTED:
6684 col_str = XT_COLOR_YELLOW;
6685 break;
6686 case CERT_BAD:
6687 col_str = XT_COLOR_RED;
6688 break;
6691 #ifdef USE_THREADS
6692 gdk_threads_enter();
6693 #endif
6694 /* make sure t isn't deleted */
6695 TAILQ_FOREACH(tt, &tabs, entry)
6696 if (t == tt)
6697 break;
6698 if (t != tt)
6699 goto done;
6701 #ifdef USE_THREADS
6702 /* test to see if the user navigated away and canceled the thread */
6703 if (t->thread != g_thread_self())
6704 goto done;
6705 if ((uri = get_uri(t)) == NULL) {
6706 t->thread = NULL;
6707 goto done;
6709 if (strcmp(uri, u)) {
6710 /* make sure we are still the same url */
6711 t->thread = NULL;
6712 goto done;
6714 #endif
6715 white:
6716 gdk_color_parse(col_str, &color);
6717 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6719 if (!strcmp(col_str, XT_COLOR_WHITE))
6720 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6721 else
6722 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6724 if (error_str && error_str[0] != '\0')
6725 show_oops(t, "%s", error_str);
6726 #ifdef USE_THREADS
6727 t->thread = NULL;
6728 #endif
6729 done:
6730 /* t is invalid at this point */
6731 if (u)
6732 g_free((gpointer)u);
6733 #ifdef USE_THREADS
6734 gdk_threads_leave();
6735 #endif
6738 void
6739 show_ca_status(struct tab *t, const char *uri)
6741 GdkColor color;
6742 gchar *col_str = XT_COLOR_WHITE;
6744 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6745 ssl_strict_certs, ssl_ca_file, uri);
6747 if (t == NULL)
6748 return;
6750 if (uri == NULL)
6751 goto done;
6752 if (ssl_ca_file == NULL) {
6753 if (g_str_has_prefix(uri, "http://"))
6754 goto done;
6755 if (g_str_has_prefix(uri, "https://")) {
6756 col_str = XT_COLOR_RED;
6757 goto done;
6759 return;
6761 if (g_str_has_prefix(uri, "http://") ||
6762 !g_str_has_prefix(uri, "https://"))
6763 goto done;
6764 #ifdef USE_THREADS
6766 * It is not necessary to see if the thread is already running.
6767 * If the thread is in progress setting it to something else aborts it
6768 * on the way out.
6771 /* thread the coloring of the address bar */
6772 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6773 #else
6774 color_address_bar(t);
6775 #endif
6776 return;
6778 done:
6779 if (col_str) {
6780 gdk_color_parse(col_str, &color);
6781 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6783 if (!strcmp(col_str, XT_COLOR_WHITE))
6784 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6785 else
6786 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6790 void
6791 free_favicon(struct tab *t)
6793 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6794 __func__, t->icon_download, t->icon_request);
6796 if (t->icon_request)
6797 g_object_unref(t->icon_request);
6798 if (t->icon_dest_uri)
6799 g_free(t->icon_dest_uri);
6801 t->icon_request = NULL;
6802 t->icon_dest_uri = NULL;
6805 void
6806 xt_icon_from_name(struct tab *t, gchar *name)
6808 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6809 GTK_ENTRY_ICON_PRIMARY, "text-html");
6810 if (show_url == 0)
6811 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6812 GTK_ENTRY_ICON_PRIMARY, "text-html");
6813 else
6814 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6815 GTK_ENTRY_ICON_PRIMARY, NULL);
6818 void
6819 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6821 GdkPixbuf *pb_scaled;
6823 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6824 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6825 GDK_INTERP_BILINEAR);
6826 else
6827 pb_scaled = pb;
6829 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6830 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6831 if (show_url == 0)
6832 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6833 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6834 else
6835 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6836 GTK_ENTRY_ICON_PRIMARY, NULL);
6838 if (pb_scaled != pb)
6839 g_object_unref(pb_scaled);
6842 void
6843 xt_icon_from_file(struct tab *t, char *file)
6845 GdkPixbuf *pb;
6847 if (g_str_has_prefix(file, "file://"))
6848 file += strlen("file://");
6850 pb = gdk_pixbuf_new_from_file(file, NULL);
6851 if (pb) {
6852 xt_icon_from_pixbuf(t, pb);
6853 g_object_unref(pb);
6854 } else
6855 xt_icon_from_name(t, "text-html");
6858 gboolean
6859 is_valid_icon(char *file)
6861 gboolean valid = 0;
6862 const char *mime_type;
6863 GFileInfo *fi;
6864 GFile *gf;
6866 gf = g_file_new_for_path(file);
6867 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6868 NULL, NULL);
6869 mime_type = g_file_info_get_content_type(fi);
6870 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6871 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6872 g_strcmp0(mime_type, "image/png") == 0 ||
6873 g_strcmp0(mime_type, "image/gif") == 0 ||
6874 g_strcmp0(mime_type, "application/octet-stream") == 0;
6875 g_object_unref(fi);
6876 g_object_unref(gf);
6878 return (valid);
6881 void
6882 set_favicon_from_file(struct tab *t, char *file)
6884 struct stat sb;
6886 if (t == NULL || file == NULL)
6887 return;
6889 if (g_str_has_prefix(file, "file://"))
6890 file += strlen("file://");
6891 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6893 if (!stat(file, &sb)) {
6894 if (sb.st_size == 0 || !is_valid_icon(file)) {
6895 /* corrupt icon so trash it */
6896 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6897 __func__, file);
6898 unlink(file);
6899 /* no need to set icon to default here */
6900 return;
6903 xt_icon_from_file(t, file);
6906 void
6907 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6908 WebKitWebView *wv)
6910 WebKitDownloadStatus status = webkit_download_get_status(download);
6911 struct tab *tt = NULL, *t = NULL;
6914 * find the webview instead of passing in the tab as it could have been
6915 * deleted from underneath us.
6917 TAILQ_FOREACH(tt, &tabs, entry) {
6918 if (tt->wv == wv) {
6919 t = tt;
6920 break;
6923 if (t == NULL)
6924 return;
6926 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6927 __func__, t->tab_id, status);
6929 switch (status) {
6930 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6931 /* -1 */
6932 t->icon_download = NULL;
6933 free_favicon(t);
6934 break;
6935 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6936 /* 0 */
6937 break;
6938 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6939 /* 1 */
6940 break;
6941 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6942 /* 2 */
6943 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6944 __func__, t->tab_id);
6945 t->icon_download = NULL;
6946 free_favicon(t);
6947 break;
6948 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6949 /* 3 */
6951 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6952 __func__, t->icon_dest_uri);
6953 set_favicon_from_file(t, t->icon_dest_uri);
6954 /* these will be freed post callback */
6955 t->icon_request = NULL;
6956 t->icon_download = NULL;
6957 break;
6958 default:
6959 break;
6963 void
6964 abort_favicon_download(struct tab *t)
6966 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6968 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6969 if (t->icon_download) {
6970 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6971 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6972 webkit_download_cancel(t->icon_download);
6973 t->icon_download = NULL;
6974 } else
6975 free_favicon(t);
6976 #endif
6978 xt_icon_from_name(t, "text-html");
6981 void
6982 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6984 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6986 if (uri == NULL || t == NULL)
6987 return;
6989 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6990 /* take icon from WebKitIconDatabase */
6991 GdkPixbuf *pb;
6993 pb = webkit_web_view_get_icon_pixbuf(wv);
6994 if (pb) {
6995 xt_icon_from_pixbuf(t, pb);
6996 g_object_unref(pb);
6997 } else
6998 xt_icon_from_name(t, "text-html");
6999 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
7000 /* download icon to cache dir */
7001 gchar *name_hash, file[PATH_MAX];
7002 struct stat sb;
7004 if (t->icon_request) {
7005 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
7006 return;
7009 /* check to see if we got the icon in cache */
7010 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
7011 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
7012 g_free(name_hash);
7014 if (!stat(file, &sb)) {
7015 if (sb.st_size > 0) {
7016 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
7017 __func__, file);
7018 set_favicon_from_file(t, file);
7019 return;
7022 /* corrupt icon so trash it */
7023 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
7024 __func__, file);
7025 unlink(file);
7028 /* create download for icon */
7029 t->icon_request = webkit_network_request_new(uri);
7030 if (t->icon_request == NULL) {
7031 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
7032 __func__, uri);
7033 return;
7036 t->icon_download = webkit_download_new(t->icon_request);
7037 if (t->icon_download == NULL)
7038 return;
7040 /* we have to free icon_dest_uri later */
7041 t->icon_dest_uri = g_strdup_printf("file://%s", file);
7042 webkit_download_set_destination_uri(t->icon_download,
7043 t->icon_dest_uri);
7045 if (webkit_download_get_status(t->icon_download) ==
7046 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7047 g_object_unref(t->icon_request);
7048 g_free(t->icon_dest_uri);
7049 t->icon_request = NULL;
7050 t->icon_dest_uri = NULL;
7051 return;
7054 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
7055 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
7057 webkit_download_start(t->icon_download);
7058 #endif
7061 void
7062 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
7064 const gchar *uri = NULL, *title = NULL;
7065 struct history *h, find;
7066 struct karg a;
7067 GdkColor color;
7069 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
7070 webkit_web_view_get_load_status(wview),
7071 get_uri(t) ? get_uri(t) : "NOTHING");
7073 if (t == NULL) {
7074 show_oops(NULL, "notify_load_status_cb invalid parameters");
7075 return;
7078 switch (webkit_web_view_get_load_status(wview)) {
7079 case WEBKIT_LOAD_PROVISIONAL:
7080 /* 0 */
7081 abort_favicon_download(t);
7082 #if GTK_CHECK_VERSION(2, 20, 0)
7083 gtk_widget_show(t->spinner);
7084 gtk_spinner_start(GTK_SPINNER(t->spinner));
7085 #endif
7086 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
7088 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
7090 /* assume we are a new address */
7091 gdk_color_parse("white", &color);
7092 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
7093 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
7095 /* take focus if we are visible */
7096 focus_webview(t);
7097 t->focus_wv = 1;
7098 #ifdef USE_THREAD
7099 /* kill color thread */
7100 t->thread = NULL;
7101 #endif
7102 break;
7104 case WEBKIT_LOAD_COMMITTED:
7105 /* 1 */
7106 uri = get_uri(t);
7107 if (uri == NULL)
7108 return;
7109 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
7111 if (t->status) {
7112 g_free(t->status);
7113 t->status = NULL;
7115 set_status(t, (char *)uri, XT_STATUS_LOADING);
7117 /* check if js white listing is enabled */
7118 if (enable_plugin_whitelist)
7119 check_and_set_pl(uri, t);
7120 if (enable_cookie_whitelist)
7121 check_and_set_cookie(uri, t);
7122 if (enable_js_whitelist)
7123 check_and_set_js(uri, t);
7125 if (t->styled)
7126 apply_style(t);
7129 /* we know enough to autosave the session */
7130 if (session_autosave) {
7131 a.s = NULL;
7132 save_tabs(t, &a);
7135 show_ca_status(t, uri);
7136 break;
7138 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
7139 /* 3 */
7140 break;
7142 case WEBKIT_LOAD_FINISHED:
7143 /* 2 */
7144 uri = get_uri(t);
7145 if (uri == NULL)
7146 return;
7148 if (!strncmp(uri, "http://", strlen("http://")) ||
7149 !strncmp(uri, "https://", strlen("https://")) ||
7150 !strncmp(uri, "file://", strlen("file://"))) {
7151 find.uri = uri;
7152 h = RB_FIND(history_list, &hl, &find);
7153 if (!h) {
7154 title = get_title(t, FALSE);
7155 h = g_malloc(sizeof *h);
7156 h->uri = g_strdup(uri);
7157 h->title = g_strdup(title);
7158 RB_INSERT(history_list, &hl, h);
7159 completion_add_uri(h->uri);
7160 update_history_tabs(NULL);
7164 set_status(t, (char *)uri, XT_STATUS_URI);
7165 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7166 case WEBKIT_LOAD_FAILED:
7167 /* 4 */
7168 #endif
7169 #if GTK_CHECK_VERSION(2, 20, 0)
7170 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7171 gtk_widget_hide(t->spinner);
7172 #endif
7173 default:
7174 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
7175 break;
7178 if (t->item)
7179 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
7180 else
7181 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
7182 can_go_back_for_real(t));
7184 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
7185 can_go_forward_for_real(t));
7188 void
7189 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
7191 const gchar *title = NULL, *win_title = NULL;
7193 title = get_title(t, FALSE);
7194 win_title = get_title(t, TRUE);
7195 gtk_label_set_text(GTK_LABEL(t->label), title);
7196 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
7197 if (t->tab_id == gtk_notebook_get_current_page(notebook))
7198 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
7201 void
7202 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7204 run_script(t, JS_HINTING);
7207 void
7208 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
7210 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
7211 progress == 100 ? 0 : (double)progress / 100);
7212 if (show_url == 0) {
7213 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
7214 progress == 100 ? 0 : (double)progress / 100);
7217 update_statusbar_position(NULL, NULL);
7221 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
7222 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
7223 WebKitWebPolicyDecision *pd, struct tab *t)
7225 char *uri;
7226 WebKitWebNavigationReason reason;
7227 struct domain *d = NULL;
7229 if (t == NULL) {
7230 show_oops(NULL, "webview_npd_cb invalid parameters");
7231 return (FALSE);
7234 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
7235 t->ctrl_click,
7236 webkit_network_request_get_uri(request));
7238 uri = (char *)webkit_network_request_get_uri(request);
7240 /* if this is an xtp url, we don't load anything else */
7241 if (parse_xtp_url(t, uri))
7242 return (TRUE);
7244 if (t->ctrl_click) {
7245 t->ctrl_click = 0;
7246 create_new_tab(uri, NULL, ctrl_click_focus, -1);
7247 webkit_web_policy_decision_ignore(pd);
7248 return (TRUE); /* we made the decission */
7252 * This is a little hairy but it comes down to this:
7253 * when we run in whitelist mode we have to assist the browser in
7254 * opening the URL that it would have opened in a new tab.
7256 reason = webkit_web_navigation_action_get_reason(na);
7257 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7258 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7259 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7260 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7261 load_uri(t, uri);
7262 webkit_web_policy_decision_use(pd);
7263 return (TRUE); /* we made the decision */
7266 return (FALSE);
7269 WebKitWebView *
7270 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7272 struct tab *tt;
7273 struct domain *d = NULL;
7274 const gchar *uri;
7275 WebKitWebView *webview = NULL;
7277 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7278 webkit_web_view_get_uri(wv));
7280 if (tabless) {
7281 /* open in current tab */
7282 webview = t->wv;
7283 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7284 uri = webkit_web_view_get_uri(wv);
7285 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7286 return (NULL);
7288 tt = create_new_tab(NULL, NULL, 1, -1);
7289 webview = tt->wv;
7290 } else if (enable_scripts == 1) {
7291 tt = create_new_tab(NULL, NULL, 1, -1);
7292 webview = tt->wv;
7295 return (webview);
7298 gboolean
7299 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7301 const gchar *uri;
7302 struct domain *d = NULL;
7304 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7306 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7307 uri = webkit_web_view_get_uri(wv);
7308 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7309 return (FALSE);
7311 delete_tab(t);
7312 } else if (enable_scripts == 1)
7313 delete_tab(t);
7315 return (TRUE);
7319 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7321 /* we can not eat the event without throwing gtk off so defer it */
7323 /* catch middle click */
7324 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7325 t->ctrl_click = 1;
7326 goto done;
7329 /* catch ctrl click */
7330 if (e->type == GDK_BUTTON_RELEASE &&
7331 CLEAN(e->state) == GDK_CONTROL_MASK)
7332 t->ctrl_click = 1;
7333 else
7334 t->ctrl_click = 0;
7335 done:
7336 return (XT_CB_PASSTHROUGH);
7340 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7342 struct mime_type *m;
7344 m = find_mime_type(mime_type);
7345 if (m == NULL)
7346 return (1);
7347 if (m->mt_download)
7348 return (1);
7350 switch (fork()) {
7351 case -1:
7352 show_oops(t, "can't fork mime handler");
7353 return (1);
7354 /* NOTREACHED */
7355 case 0:
7356 break;
7357 default:
7358 return (0);
7361 /* child */
7362 execlp(m->mt_action, m->mt_action,
7363 webkit_network_request_get_uri(request), (void *)NULL);
7365 _exit(0);
7367 /* NOTREACHED */
7368 return (0);
7371 char *
7372 get_mime_type(const char *file)
7374 const gchar *m;
7375 char *mime_type = NULL;
7376 GFileInfo *fi;
7377 GFile *gf;
7379 if (g_str_has_prefix(file, "file://"))
7380 file += strlen("file://");
7382 gf = g_file_new_for_path(file);
7383 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7384 NULL, NULL);
7385 if ((m = g_file_info_get_content_type(fi)) != NULL)
7386 mime_type = g_strdup(m);
7387 g_object_unref(fi);
7388 g_object_unref(gf);
7390 return (mime_type);
7394 run_download_mimehandler(char *mime_type, char *file)
7396 struct mime_type *m;
7398 m = find_mime_type(mime_type);
7399 if (m == NULL)
7400 return (1);
7402 switch (fork()) {
7403 case -1:
7404 show_oops(NULL, "can't fork download mime handler");
7405 return (1);
7406 /* NOTREACHED */
7407 case 0:
7408 break;
7409 default:
7410 return (0);
7413 /* child */
7414 if (g_str_has_prefix(file, "file://"))
7415 file += strlen("file://");
7416 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7418 _exit(0);
7420 /* NOTREACHED */
7421 return (0);
7424 void
7425 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7426 WebKitWebView *wv)
7428 WebKitDownloadStatus status;
7429 const char *file = NULL;
7430 char *mime = NULL;
7432 if (download == NULL)
7433 return;
7434 status = webkit_download_get_status(download);
7435 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7436 return;
7438 file = webkit_download_get_destination_uri(download);
7439 if (file == NULL)
7440 return;
7441 mime = get_mime_type(file);
7442 if (mime == NULL)
7443 return;
7445 run_download_mimehandler((char *)mime, (char *)file);
7446 g_free(mime);
7450 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7451 WebKitNetworkRequest *request, char *mime_type,
7452 WebKitWebPolicyDecision *decision, struct tab *t)
7454 if (t == NULL) {
7455 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7456 return (FALSE);
7459 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7460 t->tab_id, mime_type);
7462 if (run_mimehandler(t, mime_type, request) == 0) {
7463 webkit_web_policy_decision_ignore(decision);
7464 focus_webview(t);
7465 return (TRUE);
7468 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7469 webkit_web_policy_decision_download(decision);
7470 return (TRUE);
7473 return (FALSE);
7477 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7478 struct tab *t)
7480 struct stat sb;
7481 const gchar *suggested_name;
7482 gchar *filename = NULL;
7483 char *uri = NULL;
7484 struct download *download_entry;
7485 int i, ret = TRUE;
7487 if (wk_download == NULL || t == NULL) {
7488 show_oops(NULL, "%s invalid parameters", __func__);
7489 return (FALSE);
7492 suggested_name = webkit_download_get_suggested_filename(wk_download);
7493 if (suggested_name == NULL)
7494 return (FALSE); /* abort download */
7496 i = 0;
7497 do {
7498 if (filename) {
7499 g_free(filename);
7500 filename = NULL;
7502 if (i) {
7503 g_free(uri);
7504 uri = NULL;
7505 filename = g_strdup_printf("%d%s", i, suggested_name);
7507 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7508 filename : suggested_name);
7509 i++;
7510 } while (!stat(uri + strlen("file://"), &sb));
7512 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7513 "local %s\n", __func__, t->tab_id, filename, uri);
7515 webkit_download_set_destination_uri(wk_download, uri);
7517 if (webkit_download_get_status(wk_download) ==
7518 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7519 show_oops(t, "%s: download failed to start", __func__);
7520 ret = FALSE;
7521 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7522 } else {
7523 /* connect "download first" mime handler */
7524 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7525 G_CALLBACK(download_status_changed_cb), NULL);
7527 download_entry = g_malloc(sizeof(struct download));
7528 download_entry->download = wk_download;
7529 download_entry->tab = t;
7530 download_entry->id = next_download_id++;
7531 RB_INSERT(download_list, &downloads, download_entry);
7532 /* get from history */
7533 g_object_ref(wk_download);
7534 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7535 show_oops(t, "Download of '%s' started...",
7536 basename((char *)webkit_download_get_destination_uri(wk_download)));
7539 if (uri)
7540 g_free(uri);
7542 if (filename)
7543 g_free(filename);
7545 /* sync other download manager tabs */
7546 update_download_tabs(NULL);
7549 * NOTE: never redirect/render the current tab before this
7550 * function returns. This will cause the download to never start.
7552 return (ret); /* start download */
7555 void
7556 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7558 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7560 if (t == NULL) {
7561 show_oops(NULL, "webview_hover_cb");
7562 return;
7565 if (uri)
7566 set_status(t, uri, XT_STATUS_LINK);
7567 else {
7568 if (t->status)
7569 set_status(t, t->status, XT_STATUS_NOTHING);
7574 mark(struct tab *t, struct karg *arg)
7576 char mark;
7577 int index;
7579 mark = arg->s[1];
7580 if ((index = marktoindex(mark)) == -1)
7581 return (-1);
7583 if (arg->i == XT_MARK_SET)
7584 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7585 else if (arg->i == XT_MARK_GOTO) {
7586 if (t->mark[index] == XT_INVALID_MARK) {
7587 show_oops(t, "mark '%c' does not exist", mark);
7588 return (-1);
7590 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7591 something changes the document size */
7592 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7595 return (0);
7598 void
7599 marks_clear(struct tab *t)
7601 int i;
7603 for (i = 0; i < LENGTH(t->mark); i++)
7604 t->mark[i] = XT_INVALID_MARK;
7608 qmarks_load(void)
7610 char file[PATH_MAX];
7611 char *line = NULL, *p;
7612 int index, i;
7613 FILE *f;
7614 size_t linelen;
7616 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7617 if ((f = fopen(file, "r+")) == NULL) {
7618 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7619 return (1);
7622 for (i = 1; ; i++) {
7623 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7624 break;
7625 if (strlen(line) == 0 || line[0] == '#') {
7626 free(line);
7627 line = NULL;
7628 continue;
7631 p = strtok(line, " \t");
7633 if (p == NULL || strlen(p) != 1 ||
7634 (index = marktoindex(*p)) == -1) {
7635 warnx("corrupt quickmarks file, line %d", i);
7636 break;
7639 p = strtok(NULL, " \t");
7640 if (qmarks[index] != NULL)
7641 g_free(qmarks[index]);
7642 qmarks[index] = g_strdup(p);
7645 fclose(f);
7647 return (0);
7651 qmarks_save(void)
7653 char file[PATH_MAX];
7654 int i;
7655 FILE *f;
7657 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7658 if ((f = fopen(file, "r+")) == NULL) {
7659 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7660 return (1);
7663 for (i = 0; i < XT_NOMARKS; i++)
7664 if (qmarks[i] != NULL)
7665 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7667 fclose(f);
7669 return (0);
7673 qmark(struct tab *t, struct karg *arg)
7675 char mark;
7676 int index;
7678 mark = arg->s[strlen(arg->s)-1];
7679 index = marktoindex(mark);
7680 if (index == -1)
7681 return (-1);
7683 switch (arg->i) {
7684 case XT_QMARK_SET:
7685 if (qmarks[index] != NULL)
7686 g_free(qmarks[index]);
7688 qmarks_load(); /* sync if multiple instances */
7689 qmarks[index] = g_strdup(get_uri(t));
7690 qmarks_save();
7691 break;
7692 case XT_QMARK_OPEN:
7693 if (qmarks[index] != NULL)
7694 load_uri(t, qmarks[index]);
7695 else {
7696 show_oops(t, "quickmark \"%c\" does not exist",
7697 mark);
7698 return (-1);
7700 break;
7701 case XT_QMARK_TAB:
7702 if (qmarks[index] != NULL)
7703 create_new_tab(qmarks[index], NULL, 1, -1);
7704 else {
7705 show_oops(t, "quickmark \"%c\" does not exist",
7706 mark);
7707 return (-1);
7709 break;
7712 return (0);
7716 go_up(struct tab *t, struct karg *args)
7718 int levels;
7719 char *uri;
7720 char *tmp;
7722 levels = atoi(args->s);
7723 if (levels == 0)
7724 levels = 1;
7726 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7727 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7728 return (1);
7729 tmp += strlen(XT_PROTO_DELIM);
7731 /* if an uri starts with a slash, leave it alone (for file:///) */
7732 if (tmp[0] == '/')
7733 tmp++;
7735 while (levels--) {
7736 char *p;
7738 p = strrchr(tmp, '/');
7739 if (p != NULL)
7740 *p = '\0';
7741 else
7742 break;
7745 load_uri(t, uri);
7746 g_free(uri);
7748 return (0);
7752 gototab(struct tab *t, struct karg *args)
7754 int tab;
7755 struct karg arg = {0, NULL, -1};
7757 tab = atoi(args->s);
7759 arg.i = XT_TAB_NEXT;
7760 arg.precount = tab;
7762 movetab(t, &arg);
7764 return (0);
7768 zoom_amount(struct tab *t, struct karg *arg)
7770 struct karg narg = {0, NULL, -1};
7772 narg.i = atoi(arg->s);
7773 resizetab(t, &narg);
7775 return (0);
7779 flip_colon(struct tab *t, struct karg *arg)
7781 struct karg narg = {0, NULL, -1};
7782 char *p;
7784 if (t == NULL || arg == NULL)
7785 return (1);
7787 p = strstr(arg->s, ":");
7788 if (p == NULL)
7789 return (1);
7790 *p = '\0';
7792 narg.i = ':';
7793 narg.s = arg->s;
7794 command(t, &narg);
7796 return (0);
7799 /* buffer commands receive the regex that triggered them in arg.s */
7800 char bcmd[XT_BUFCMD_SZ];
7801 struct buffercmd {
7802 char *regex;
7803 int precount;
7804 #define XT_PRE_NO (0)
7805 #define XT_PRE_YES (1)
7806 #define XT_PRE_MAYBE (2)
7807 char *cmd;
7808 int (*func)(struct tab *, struct karg *);
7809 int arg;
7810 regex_t cregex;
7811 } buffercmds[] = {
7812 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7813 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7814 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7815 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7816 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7817 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7818 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7819 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7820 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7821 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7822 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7823 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7824 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7825 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7826 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7827 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7828 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7829 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7832 void
7833 buffercmd_init(void)
7835 int i;
7837 for (i = 0; i < LENGTH(buffercmds); i++)
7838 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7839 REG_EXTENDED | REG_NOSUB))
7840 startpage_add("invalid buffercmd regex %s",
7841 buffercmds[i].regex);
7844 void
7845 buffercmd_abort(struct tab *t)
7847 int i;
7849 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7850 for (i = 0; i < LENGTH(bcmd); i++)
7851 bcmd[i] = '\0';
7853 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7854 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7857 void
7858 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7860 struct karg arg = {0, NULL, -1};
7862 arg.i = cmd->arg;
7863 arg.s = g_strdup(bcmd);
7865 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7866 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7867 cmd->func(t, &arg);
7869 if (arg.s)
7870 g_free(arg.s);
7872 buffercmd_abort(t);
7875 gboolean
7876 buffercmd_addkey(struct tab *t, guint keyval)
7878 int i, c, match ;
7879 char s[XT_BUFCMD_SZ];
7881 if (keyval == GDK_Escape) {
7882 buffercmd_abort(t);
7883 return (XT_CB_HANDLED);
7886 /* key with modifier or non-ascii character */
7887 if (!isascii(keyval))
7888 return (XT_CB_PASSTHROUGH);
7890 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7891 "to buffer \"%s\"\n", keyval, bcmd);
7893 for (i = 0; i < LENGTH(bcmd); i++)
7894 if (bcmd[i] == '\0') {
7895 bcmd[i] = keyval;
7896 break;
7899 /* buffer full, ignore input */
7900 if (i >= LENGTH(bcmd) -1) {
7901 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7902 buffercmd_abort(t);
7903 return (XT_CB_HANDLED);
7906 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7908 /* find exact match */
7909 for (i = 0; i < LENGTH(buffercmds); i++)
7910 if (regexec(&buffercmds[i].cregex, bcmd,
7911 (size_t) 0, NULL, 0) == 0) {
7912 buffercmd_execute(t, &buffercmds[i]);
7913 goto done;
7916 /* find non exact matches to see if we need to abort ot not */
7917 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7918 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7919 c = -1;
7920 s[0] = '\0';
7921 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7922 if (isdigit(bcmd[0])) {
7923 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7924 continue;
7925 } else {
7926 c = 0;
7927 if (sscanf(bcmd, "%s", s) == 0)
7928 continue;
7930 } else if (buffercmds[i].precount == XT_PRE_YES) {
7931 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7932 continue;
7933 } else {
7934 if (sscanf(bcmd, "%s", s) == 0)
7935 continue;
7937 if (c == -1 && buffercmds[i].precount)
7938 continue;
7939 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7940 match++;
7942 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7943 i, match, buffercmds[i].cmd, c, s);
7945 if (match == 0) {
7946 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7947 buffercmd_abort(t);
7950 done:
7951 return (XT_CB_HANDLED);
7954 gboolean
7955 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7957 struct key_binding *k;
7959 /* handle keybindings if buffercmd is empty.
7960 if not empty, allow commands like C-n */
7961 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7962 TAILQ_FOREACH(k, &kbl, entry)
7963 if (e->keyval == k->key
7964 && (entry ? k->use_in_entry : 1)) {
7965 if (k->mask == 0) {
7966 if ((e->state & (CTRL | MOD1)) == 0)
7967 return (cmd_execute(t, k->cmd));
7968 } else if ((e->state & k->mask) == k->mask) {
7969 return (cmd_execute(t, k->cmd));
7973 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7974 return buffercmd_addkey(t, e->keyval);
7976 return (XT_CB_PASSTHROUGH);
7980 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7982 char s[2], buf[128];
7983 const char *errstr = NULL;
7985 /* don't use w directly; use t->whatever instead */
7987 if (t == NULL) {
7988 show_oops(NULL, "wv_keypress_after_cb");
7989 return (XT_CB_PASSTHROUGH);
7992 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7993 e->keyval, e->state, t);
7995 if (t->hints_on) {
7996 /* ESC */
7997 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7998 disable_hints(t);
7999 return (XT_CB_HANDLED);
8002 /* RETURN */
8003 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
8004 if (errstr) {
8005 /* we have a string */
8006 } else {
8007 /* we have a number */
8008 snprintf(buf, sizeof buf,
8009 "vimprobable_fire(%s)", t->hint_num);
8010 run_script(t, buf);
8012 disable_hints(t);
8015 /* BACKSPACE */
8016 /* XXX unfuck this */
8017 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
8018 if (t->hint_mode == XT_HINT_NUMERICAL) {
8019 /* last input was numerical */
8020 int l;
8021 l = strlen(t->hint_num);
8022 if (l > 0) {
8023 l--;
8024 if (l == 0) {
8025 disable_hints(t);
8026 enable_hints(t);
8027 } else {
8028 t->hint_num[l] = '\0';
8029 goto num;
8032 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
8033 /* last input was alphanumerical */
8034 int l;
8035 l = strlen(t->hint_buf);
8036 if (l > 0) {
8037 l--;
8038 if (l == 0) {
8039 disable_hints(t);
8040 enable_hints(t);
8041 } else {
8042 t->hint_buf[l] = '\0';
8043 goto anum;
8046 } else {
8047 /* bogus */
8048 disable_hints(t);
8052 /* numerical input */
8053 if (CLEAN(e->state) == 0 &&
8054 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
8055 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
8056 snprintf(s, sizeof s, "%c", e->keyval);
8057 strlcat(t->hint_num, s, sizeof t->hint_num);
8058 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
8059 t->hint_num);
8060 num:
8061 if (errstr) {
8062 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
8063 "invalid link number\n");
8064 disable_hints(t);
8065 } else {
8066 snprintf(buf, sizeof buf,
8067 "vimprobable_update_hints(%s)",
8068 t->hint_num);
8069 t->hint_mode = XT_HINT_NUMERICAL;
8070 run_script(t, buf);
8073 /* empty the counter buffer */
8074 bzero(t->hint_buf, sizeof t->hint_buf);
8075 return (XT_CB_HANDLED);
8078 /* alphanumerical input */
8079 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
8080 e->keyval <= GDK_z) ||
8081 (CLEAN(e->state) == GDK_SHIFT_MASK &&
8082 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
8083 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
8084 e->keyval <= GDK_9) ||
8085 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
8086 (t->hint_mode != XT_HINT_NUMERICAL))))) {
8087 snprintf(s, sizeof s, "%c", e->keyval);
8088 strlcat(t->hint_buf, s, sizeof t->hint_buf);
8089 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
8090 " %s\n", t->hint_buf);
8091 anum:
8092 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
8093 run_script(t, buf);
8095 snprintf(buf, sizeof buf,
8096 "vimprobable_show_hints('%s')", t->hint_buf);
8097 t->hint_mode = XT_HINT_ALPHANUM;
8098 run_script(t, buf);
8100 /* empty the counter buffer */
8101 bzero(t->hint_num, sizeof t->hint_num);
8102 return (XT_CB_HANDLED);
8105 return (XT_CB_HANDLED);
8106 } else {
8107 /* prefix input*/
8108 snprintf(s, sizeof s, "%c", e->keyval);
8109 if (CLEAN(e->state) == 0 && isdigit(s[0]))
8110 cmd_prefix = 10 * cmd_prefix + atoi(s);
8113 return (handle_keypress(t, e, 0));
8117 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8119 hide_oops(t);
8121 /* Hide buffers, if they are visible, with escape. */
8122 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
8123 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
8124 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8125 hide_buffers(t);
8126 return (XT_CB_HANDLED);
8129 return (XT_CB_PASSTHROUGH);
8132 gboolean
8133 search_continue(struct tab *t)
8135 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
8136 gboolean rv = FALSE;
8138 if (c[0] == ':')
8139 goto done;
8140 if (strlen(c) == 1) {
8141 webkit_web_view_unmark_text_matches(t->wv);
8142 goto done;
8145 if (c[0] == '/')
8146 t->search_forward = TRUE;
8147 else if (c[0] == '?')
8148 t->search_forward = FALSE;
8149 else
8150 goto done;
8152 rv = TRUE;
8153 done:
8154 return (rv);
8157 gboolean
8158 search_cb(struct tab *t)
8160 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
8161 GdkColor color;
8163 if (search_continue(t) == FALSE)
8164 goto done;
8166 /* search */
8167 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
8168 TRUE) == FALSE) {
8169 /* not found, mark red */
8170 gdk_color_parse(XT_COLOR_RED, &color);
8171 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
8172 /* unmark and remove selection */
8173 webkit_web_view_unmark_text_matches(t->wv);
8174 /* my kingdom for a way to unselect text in webview */
8175 } else {
8176 /* found, highlight all */
8177 webkit_web_view_unmark_text_matches(t->wv);
8178 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
8179 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
8180 gdk_color_parse(XT_COLOR_WHITE, &color);
8181 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
8183 done:
8184 t->search_id = 0;
8185 return (FALSE);
8189 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8191 const gchar *c = gtk_entry_get_text(w);
8193 if (t == NULL) {
8194 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
8195 return (XT_CB_PASSTHROUGH);
8198 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
8199 e->keyval, e->state, t);
8201 if (search_continue(t) == FALSE)
8202 goto done;
8204 /* if search length is > 4 then no longer play timeout games */
8205 if (strlen(c) > 4) {
8206 if (t->search_id) {
8207 g_source_remove(t->search_id);
8208 t->search_id = 0;
8210 search_cb(t);
8211 goto done;
8214 /* reestablish a new timer if the user types fast */
8215 if (t->search_id)
8216 g_source_remove(t->search_id);
8217 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
8219 done:
8220 return (XT_CB_PASSTHROUGH);
8223 gboolean
8224 match_uri(const gchar *uri, const gchar *key) {
8225 gchar *voffset;
8226 size_t len;
8227 gboolean match = FALSE;
8229 len = strlen(key);
8231 if (!strncmp(key, uri, len))
8232 match = TRUE;
8233 else {
8234 voffset = strstr(uri, "/") + 2;
8235 if (!strncmp(key, voffset, len))
8236 match = TRUE;
8237 else if (g_str_has_prefix(voffset, "www.")) {
8238 voffset = voffset + strlen("www.");
8239 if (!strncmp(key, voffset, len))
8240 match = TRUE;
8244 return (match);
8247 gboolean
8248 match_session(const gchar *name, const gchar *key) {
8249 char *sub;
8251 sub = strcasestr(name, key);
8253 return sub == name;
8256 void
8257 cmd_getlist(int id, char *key)
8259 int i, dep, c = 0;
8260 struct history *h;
8261 struct session *s;
8263 if (id >= 0) {
8264 if (cmds[id].type & XT_URLARG) {
8265 RB_FOREACH_REVERSE(h, history_list, &hl)
8266 if (match_uri(h->uri, key)) {
8267 cmd_status.list[c] = (char *)h->uri;
8268 if (++c > 255)
8269 break;
8271 cmd_status.len = c;
8272 return;
8273 } else if (cmds[id].type & XT_SESSARG) {
8274 TAILQ_FOREACH(s, &sessions, entry)
8275 if (match_session(s->name, key)) {
8276 cmd_status.list[c] = (char *)s->name;
8277 if (++c > 255)
8278 break;
8280 cmd_status.len = c;
8281 return;
8282 } else if (cmds[id].type & XT_SETARG) {
8283 for (i = 0; i < LENGTH(rs); i++)
8284 if(!strncmp(key, rs[i].name, strlen(key)))
8285 cmd_status.list[c++] = rs[i].name;
8286 cmd_status.len = c;
8287 return;
8291 dep = (id == -1) ? 0 : cmds[id].level + 1;
8293 for (i = id + 1; i < LENGTH(cmds); i++) {
8294 if (cmds[i].level < dep)
8295 break;
8296 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8297 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8298 cmd_status.list[c++] = cmds[i].cmd;
8302 cmd_status.len = c;
8305 char *
8306 cmd_getnext(int dir)
8308 cmd_status.index += dir;
8310 if (cmd_status.index < 0)
8311 cmd_status.index = cmd_status.len - 1;
8312 else if (cmd_status.index >= cmd_status.len)
8313 cmd_status.index = 0;
8315 return cmd_status.list[cmd_status.index];
8319 cmd_tokenize(char *s, char *tokens[])
8321 int i = 0;
8322 char *tok, *last = NULL;
8323 size_t len = strlen(s);
8324 bool blank;
8326 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8327 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8328 tok = strtok_r(NULL, " ", &last), i++)
8329 tokens[i] = tok;
8331 if (blank && i < 3)
8332 tokens[i++] = "";
8334 return (i);
8337 void
8338 cmd_complete(struct tab *t, char *str, int dir)
8340 GtkEntry *w = GTK_ENTRY(t->cmd);
8341 int i, j, levels, c = 0, dep = 0, parent = -1;
8342 int matchcount = 0;
8343 char *tok, *match, *s = g_strdup(str);
8344 char *tokens[3];
8345 char res[XT_MAX_URL_LENGTH + 32] = ":";
8346 char *sc = s;
8348 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8350 /* copy prefix*/
8351 for (i = 0; isdigit(s[i]); i++)
8352 res[i + 1] = s[i];
8354 for (; isspace(s[i]); i++)
8355 res[i + 1] = s[i];
8357 s += i;
8359 levels = cmd_tokenize(s, tokens);
8361 for (i = 0; i < levels - 1; i++) {
8362 tok = tokens[i];
8363 matchcount = 0;
8364 for (j = c; j < LENGTH(cmds); j++) {
8365 if (cmds[j].level < dep)
8366 break;
8367 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8368 strlen(tok))) {
8369 matchcount++;
8370 c = j + 1;
8371 if (strlen(tok) == strlen(cmds[j].cmd)) {
8372 matchcount = 1;
8373 break;
8378 if (matchcount == 1) {
8379 strlcat(res, tok, sizeof res);
8380 strlcat(res, " ", sizeof res);
8381 dep++;
8382 } else {
8383 g_free(sc);
8384 return;
8387 parent = c - 1;
8390 if (cmd_status.index == -1)
8391 cmd_getlist(parent, tokens[i]);
8393 if (cmd_status.len > 0) {
8394 match = cmd_getnext(dir);
8395 strlcat(res, match, sizeof res);
8396 gtk_entry_set_text(w, res);
8397 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8400 g_free(sc);
8403 gboolean
8404 cmd_execute(struct tab *t, char *str)
8406 struct cmd *cmd = NULL;
8407 char *tok, *last = NULL, *s = g_strdup(str), *sc;
8408 char prefixstr[4];
8409 int j, len, c = 0, dep = 0, matchcount = 0;
8410 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8411 struct karg arg = {0, NULL, -1};
8413 sc = s;
8415 /* copy prefix*/
8416 for (j = 0; j<3 && isdigit(s[j]); j++)
8417 prefixstr[j]=s[j];
8419 prefixstr[j]='\0';
8421 s += j;
8422 while (isspace(s[0]))
8423 s++;
8425 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8426 prefix = atoi(prefixstr);
8427 else
8428 s = sc;
8430 for (tok = strtok_r(s, " ", &last); tok;
8431 tok = strtok_r(NULL, " ", &last)) {
8432 matchcount = 0;
8433 for (j = c; j < LENGTH(cmds); j++) {
8434 if (cmds[j].level < dep)
8435 break;
8436 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8437 strlen(tok);
8438 if (cmds[j].level == dep &&
8439 !strncmp(tok, cmds[j].cmd, len)) {
8440 matchcount++;
8441 c = j + 1;
8442 cmd = &cmds[j];
8443 if (len == strlen(cmds[j].cmd)) {
8444 matchcount = 1;
8445 break;
8449 if (matchcount == 1) {
8450 if (cmd->type > 0)
8451 goto execute_cmd;
8452 dep++;
8453 } else {
8454 show_oops(t, "Invalid command: %s", str);
8455 goto done;
8458 execute_cmd:
8459 if (cmd == NULL) {
8460 show_oops(t, "Empty command");
8461 goto done;
8463 arg.i = cmd->arg;
8465 if (prefix != -1)
8466 arg.precount = prefix;
8467 else if (cmd_prefix > 0)
8468 arg.precount = cmd_prefix;
8470 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8471 show_oops(t, "No prefix allowed: %s", str);
8472 goto done;
8474 if (cmd->type > 1)
8475 arg.s = last ? g_strdup(last) : g_strdup("");
8476 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8477 if (arg.s == NULL) {
8478 show_oops(t, "Invalid command");
8479 goto done;
8481 arg.precount = atoi(arg.s);
8482 if (arg.precount <= 0) {
8483 if (arg.s[0] == '0')
8484 show_oops(t, "Zero count");
8485 else
8486 show_oops(t, "Trailing characters");
8487 goto done;
8491 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8492 __func__, arg.precount, arg.s);
8494 cmd->func(t, &arg);
8496 rv = XT_CB_HANDLED;
8497 done:
8498 if (j > 0)
8499 cmd_prefix = 0;
8500 g_free(sc);
8501 if (arg.s)
8502 g_free(arg.s);
8504 return (rv);
8508 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8510 if (t == NULL) {
8511 show_oops(NULL, "entry_key_cb invalid parameters");
8512 return (XT_CB_PASSTHROUGH);
8515 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8516 e->keyval, e->state, t);
8518 hide_oops(t);
8520 if (e->keyval == GDK_Escape) {
8521 /* don't use focus_webview(t) because we want to type :cmds */
8522 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8525 return (handle_keypress(t, e, 1));
8528 struct command_entry *
8529 history_prev(struct command_list *l, struct command_entry *at)
8531 if (at == NULL)
8532 at = TAILQ_LAST(l, command_list);
8533 else {
8534 at = TAILQ_PREV(at, command_list, entry);
8535 if (at == NULL)
8536 at = TAILQ_LAST(l, command_list);
8539 return (at);
8542 struct command_entry *
8543 history_next(struct command_list *l, struct command_entry *at)
8545 if (at == NULL)
8546 at = TAILQ_FIRST(l);
8547 else {
8548 at = TAILQ_NEXT(at, entry);
8549 if (at == NULL)
8550 at = TAILQ_FIRST(l);
8553 return (at);
8557 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8559 int rv = XT_CB_HANDLED;
8560 const gchar *c = gtk_entry_get_text(w);
8562 if (t == NULL) {
8563 show_oops(NULL, "cmd_keypress_cb parameters");
8564 return (XT_CB_PASSTHROUGH);
8567 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8568 e->keyval, e->state, t);
8570 /* sanity */
8571 if (c == NULL)
8572 e->keyval = GDK_Escape;
8573 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8574 e->keyval = GDK_Escape;
8576 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8577 e->keyval != GDK_ISO_Left_Tab)
8578 cmd_status.index = -1;
8580 switch (e->keyval) {
8581 case GDK_Tab:
8582 if (c[0] == ':')
8583 cmd_complete(t, (char *)&c[1], 1);
8584 goto done;
8585 case GDK_ISO_Left_Tab:
8586 if (c[0] == ':')
8587 cmd_complete(t, (char *)&c[1], -1);
8589 goto done;
8590 case GDK_Down:
8591 if (c[0] != ':') {
8592 if ((search_at = history_next(&shl, search_at))) {
8593 search_at->line[0] = c[0];
8594 gtk_entry_set_text(w, search_at->line);
8595 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8597 } else {
8598 if ((history_at = history_prev(&chl, history_at))) {
8599 history_at->line[0] = c[0];
8600 gtk_entry_set_text(w, history_at->line);
8601 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8605 goto done;
8606 case GDK_Up:
8607 if (c[0] != ':') {
8608 if ((search_at = history_next(&shl, search_at))) {
8609 search_at->line[0] = c[0];
8610 gtk_entry_set_text(w, search_at->line);
8611 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8613 } else {
8614 if ((history_at = history_next(&chl, history_at))) {
8615 history_at->line[0] = c[0];
8616 gtk_entry_set_text(w, history_at->line);
8617 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8621 goto done;
8622 case GDK_BackSpace:
8623 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8624 break;
8625 /* FALLTHROUGH */
8626 case GDK_Escape:
8627 hide_cmd(t);
8628 focus_webview(t);
8630 /* cancel search */
8631 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8632 webkit_web_view_unmark_text_matches(t->wv);
8633 goto done;
8636 rv = XT_CB_PASSTHROUGH;
8637 done:
8638 return (rv);
8641 void
8642 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8644 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8647 void
8648 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8650 /* popup menu enabled */
8651 t->popup = 1;
8655 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8657 if (t == NULL) {
8658 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8659 return (XT_CB_PASSTHROUGH);
8662 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8663 t->tab_id, t->popup);
8665 /* if popup is enabled don't lose focus */
8666 if (t->popup) {
8667 t->popup = 0;
8668 return (XT_CB_PASSTHROUGH);
8671 hide_cmd(t);
8672 hide_oops(t);
8674 if (show_url == 0 || t->focus_wv)
8675 focus_webview(t);
8676 else
8677 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8679 return (XT_CB_PASSTHROUGH);
8682 void
8683 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8685 char *s;
8686 const gchar *c = gtk_entry_get_text(entry);
8688 if (t == NULL) {
8689 show_oops(NULL, "cmd_activate_cb invalid parameters");
8690 return;
8693 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8695 hide_cmd(t);
8697 /* sanity */
8698 if (c == NULL)
8699 goto done;
8700 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8701 goto done;
8702 if (strlen(c) < 2)
8703 goto done;
8704 s = (char *)&c[1];
8706 if (c[0] == '/' || c[0] == '?') {
8707 /* see if there is a timer pending */
8708 if (t->search_id) {
8709 g_source_remove(t->search_id);
8710 t->search_id = 0;
8711 search_cb(t);
8714 if (t->search_text) {
8715 g_free(t->search_text);
8716 t->search_text = NULL;
8719 t->search_text = g_strdup(s);
8720 if (global_search)
8721 g_free(global_search);
8722 global_search = g_strdup(s);
8723 t->search_forward = c[0] == '/';
8725 history_add(&shl, search_file, s, &search_history_count);
8726 goto done;
8729 history_add(&chl, command_file, s, &cmd_history_count);
8730 cmd_execute(t, s);
8731 done:
8732 return;
8735 void
8736 backward_cb(GtkWidget *w, struct tab *t)
8738 struct karg a;
8740 if (t == NULL) {
8741 show_oops(NULL, "backward_cb invalid parameters");
8742 return;
8745 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8747 a.i = XT_NAV_BACK;
8748 navaction(t, &a);
8751 void
8752 forward_cb(GtkWidget *w, struct tab *t)
8754 struct karg a;
8756 if (t == NULL) {
8757 show_oops(NULL, "forward_cb invalid parameters");
8758 return;
8761 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8763 a.i = XT_NAV_FORWARD;
8764 navaction(t, &a);
8767 void
8768 home_cb(GtkWidget *w, struct tab *t)
8770 if (t == NULL) {
8771 show_oops(NULL, "home_cb invalid parameters");
8772 return;
8775 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8777 load_uri(t, home);
8780 void
8781 stop_cb(GtkWidget *w, struct tab *t)
8783 WebKitWebFrame *frame;
8785 if (t == NULL) {
8786 show_oops(NULL, "stop_cb invalid parameters");
8787 return;
8790 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8792 frame = webkit_web_view_get_main_frame(t->wv);
8793 if (frame == NULL) {
8794 show_oops(t, "stop_cb: no frame");
8795 return;
8798 webkit_web_frame_stop_loading(frame);
8799 abort_favicon_download(t);
8802 void
8803 setup_webkit(struct tab *t)
8805 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8806 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8807 FALSE, (char *)NULL);
8808 else
8809 warnx("webkit does not have \"enable-dns-prefetching\" property");
8810 g_object_set(G_OBJECT(t->settings),
8811 "user-agent", t->user_agent, (char *)NULL);
8812 g_object_set(G_OBJECT(t->settings),
8813 "enable-scripts", enable_scripts, (char *)NULL);
8814 g_object_set(G_OBJECT(t->settings),
8815 "enable-plugins", enable_plugins, (char *)NULL);
8816 g_object_set(G_OBJECT(t->settings),
8817 "javascript-can-open-windows-automatically", enable_scripts,
8818 (char *)NULL);
8819 g_object_set(G_OBJECT(t->settings),
8820 "enable-html5-database", FALSE, (char *)NULL);
8821 g_object_set(G_OBJECT(t->settings),
8822 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8823 g_object_set(G_OBJECT(t->settings),
8824 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8825 g_object_set(G_OBJECT(t->settings),
8826 "spell_checking_languages", spell_check_languages, (char *)NULL);
8827 g_object_set(G_OBJECT(t->wv),
8828 "full-content-zoom", TRUE, (char *)NULL);
8830 webkit_web_view_set_settings(t->wv, t->settings);
8833 gboolean
8834 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8836 struct tab *ti, *t = NULL;
8837 gdouble view_size, value, max;
8838 gchar *position;
8840 TAILQ_FOREACH(ti, &tabs, entry)
8841 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8842 t = ti;
8843 break;
8846 if (t == NULL)
8847 return FALSE;
8849 if (adjustment == NULL)
8850 adjustment = gtk_scrolled_window_get_vadjustment(
8851 GTK_SCROLLED_WINDOW(t->browser_win));
8853 view_size = gtk_adjustment_get_page_size(adjustment);
8854 value = gtk_adjustment_get_value(adjustment);
8855 max = gtk_adjustment_get_upper(adjustment) - view_size;
8857 if (max == 0)
8858 position = g_strdup("All");
8859 else if (value == max)
8860 position = g_strdup("Bot");
8861 else if (value == 0)
8862 position = g_strdup("Top");
8863 else
8864 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8866 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8867 g_free(position);
8869 return (TRUE);
8872 GtkWidget *
8873 create_browser(struct tab *t)
8875 GtkWidget *w;
8876 gchar *strval;
8877 GtkAdjustment *adjustment;
8879 if (t == NULL) {
8880 show_oops(NULL, "create_browser invalid parameters");
8881 return (NULL);
8884 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8885 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8886 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8887 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8889 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8890 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8891 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8893 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8894 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8896 /* set defaults */
8897 t->settings = webkit_web_settings_new();
8899 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8901 if (user_agent == NULL) {
8902 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8903 (char *)NULL);
8904 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8905 g_free(strval);
8906 } else
8907 t->user_agent = g_strdup(user_agent);
8909 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8911 adjustment =
8912 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8913 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8914 G_CALLBACK(update_statusbar_position), NULL);
8916 setup_webkit(t);
8918 return (w);
8921 GtkWidget *
8922 create_window(void)
8924 GtkWidget *w;
8926 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8927 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8928 gtk_widget_set_name(w, "xxxterm");
8929 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8930 g_signal_connect(G_OBJECT(w), "delete_event",
8931 G_CALLBACK (gtk_main_quit), NULL);
8933 return (w);
8936 GtkWidget *
8937 create_kiosk_toolbar(struct tab *t)
8939 GtkWidget *toolbar = NULL, *b;
8941 b = gtk_hbox_new(FALSE, 0);
8942 toolbar = b;
8943 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8945 /* backward button */
8946 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8947 gtk_widget_set_sensitive(t->backward, FALSE);
8948 g_signal_connect(G_OBJECT(t->backward), "clicked",
8949 G_CALLBACK(backward_cb), t);
8950 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8952 /* forward button */
8953 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8954 gtk_widget_set_sensitive(t->forward, FALSE);
8955 g_signal_connect(G_OBJECT(t->forward), "clicked",
8956 G_CALLBACK(forward_cb), t);
8957 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8959 /* home button */
8960 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8961 gtk_widget_set_sensitive(t->gohome, true);
8962 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8963 G_CALLBACK(home_cb), t);
8964 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8966 /* create widgets but don't use them */
8967 t->uri_entry = gtk_entry_new();
8968 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8969 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8970 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8972 return (toolbar);
8975 GtkWidget *
8976 create_toolbar(struct tab *t)
8978 GtkWidget *toolbar = NULL, *b, *eb1;
8980 b = gtk_hbox_new(FALSE, 0);
8981 toolbar = b;
8982 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8984 /* backward button */
8985 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8986 gtk_widget_set_sensitive(t->backward, FALSE);
8987 g_signal_connect(G_OBJECT(t->backward), "clicked",
8988 G_CALLBACK(backward_cb), t);
8989 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8991 /* forward button */
8992 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8993 gtk_widget_set_sensitive(t->forward, FALSE);
8994 g_signal_connect(G_OBJECT(t->forward), "clicked",
8995 G_CALLBACK(forward_cb), t);
8996 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8997 FALSE, 0);
8999 /* stop button */
9000 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
9001 gtk_widget_set_sensitive(t->stop, FALSE);
9002 g_signal_connect(G_OBJECT(t->stop), "clicked",
9003 G_CALLBACK(stop_cb), t);
9004 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
9005 FALSE, 0);
9007 /* JS button */
9008 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
9009 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
9010 gtk_widget_set_sensitive(t->js_toggle, TRUE);
9011 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
9012 G_CALLBACK(js_toggle_cb), t);
9013 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
9015 t->uri_entry = gtk_entry_new();
9016 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
9017 G_CALLBACK(activate_uri_entry_cb), t);
9018 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
9019 G_CALLBACK(entry_key_cb), t);
9020 completion_add(t);
9021 eb1 = gtk_hbox_new(FALSE, 0);
9022 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
9023 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
9024 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
9026 /* search entry */
9027 if (search_string) {
9028 GtkWidget *eb2;
9029 t->search_entry = gtk_entry_new();
9030 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
9031 g_signal_connect(G_OBJECT(t->search_entry), "activate",
9032 G_CALLBACK(activate_search_entry_cb), t);
9033 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
9034 G_CALLBACK(entry_key_cb), t);
9035 gtk_widget_set_size_request(t->search_entry, -1, -1);
9036 eb2 = gtk_hbox_new(FALSE, 0);
9037 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
9038 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
9040 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
9043 return (toolbar);
9046 GtkWidget *
9047 create_buffers(struct tab *t)
9049 GtkCellRenderer *renderer;
9050 GtkWidget *view;
9052 view = gtk_tree_view_new();
9054 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
9056 renderer = gtk_cell_renderer_text_new();
9057 gtk_tree_view_insert_column_with_attributes
9058 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
9060 renderer = gtk_cell_renderer_text_new();
9061 gtk_tree_view_insert_column_with_attributes
9062 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
9063 (char *)NULL);
9065 gtk_tree_view_set_model
9066 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
9068 return view;
9071 void
9072 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
9073 GtkTreeViewColumn *col, struct tab *t)
9075 GtkTreeIter iter;
9076 guint id;
9078 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9080 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
9081 path)) {
9082 gtk_tree_model_get
9083 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
9084 set_current_tab(id - 1);
9087 hide_buffers(t);
9090 /* after tab reordering/creation/removal */
9091 void
9092 recalc_tabs(void)
9094 struct tab *t;
9095 int maxid = 0;
9097 TAILQ_FOREACH(t, &tabs, entry) {
9098 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
9099 if (t->tab_id > maxid)
9100 maxid = t->tab_id;
9102 gtk_widget_show(t->tab_elems.sep);
9105 TAILQ_FOREACH(t, &tabs, entry) {
9106 if (t->tab_id == maxid) {
9107 gtk_widget_hide(t->tab_elems.sep);
9108 break;
9113 /* after active tab change */
9114 void
9115 recolor_compact_tabs(void)
9117 struct tab *t;
9118 int curid = 0;
9119 GdkColor color;
9121 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9122 TAILQ_FOREACH(t, &tabs, entry)
9123 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
9124 &color);
9126 curid = gtk_notebook_get_current_page(notebook);
9127 TAILQ_FOREACH(t, &tabs, entry)
9128 if (t->tab_id == curid) {
9129 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
9130 gtk_widget_modify_fg(t->tab_elems.label,
9131 GTK_STATE_NORMAL, &color);
9132 break;
9136 void
9137 set_current_tab(int page_num)
9139 buffercmd_abort(get_current_tab());
9140 gtk_notebook_set_current_page(notebook, page_num);
9141 recolor_compact_tabs();
9145 undo_close_tab_save(struct tab *t)
9147 int m, n;
9148 const gchar *uri;
9149 struct undo *u1, *u2;
9150 GList *items;
9151 WebKitWebHistoryItem *item;
9153 if ((uri = get_uri(t)) == NULL)
9154 return (1);
9156 u1 = g_malloc0(sizeof(struct undo));
9157 u1->uri = g_strdup(uri);
9159 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9161 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
9162 n = webkit_web_back_forward_list_get_back_length(t->bfl);
9163 u1->back = n;
9165 /* forward history */
9166 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
9168 while (items) {
9169 item = items->data;
9170 u1->history = g_list_prepend(u1->history,
9171 webkit_web_history_item_copy(item));
9172 items = g_list_next(items);
9175 /* current item */
9176 if (m) {
9177 item = webkit_web_back_forward_list_get_current_item(t->bfl);
9178 u1->history = g_list_prepend(u1->history,
9179 webkit_web_history_item_copy(item));
9182 /* back history */
9183 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
9185 while (items) {
9186 item = items->data;
9187 u1->history = g_list_prepend(u1->history,
9188 webkit_web_history_item_copy(item));
9189 items = g_list_next(items);
9192 TAILQ_INSERT_HEAD(&undos, u1, entry);
9194 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
9195 u2 = TAILQ_LAST(&undos, undo_tailq);
9196 TAILQ_REMOVE(&undos, u2, entry);
9197 g_free(u2->uri);
9198 g_list_free(u2->history);
9199 g_free(u2);
9200 } else
9201 undo_count++;
9203 return (0);
9206 void
9207 delete_tab(struct tab *t)
9209 struct karg a;
9211 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
9213 if (t == NULL)
9214 return;
9217 * no need to join thread here because it won't access t on completion
9220 buffercmd_abort(t);
9221 TAILQ_REMOVE(&tabs, t, entry);
9223 /* Halt all webkit activity. */
9224 abort_favicon_download(t);
9225 webkit_web_view_stop_loading(t->wv);
9227 /* Save the tab, so we can undo the close. */
9228 undo_close_tab_save(t);
9230 if (browser_mode == XT_BM_KIOSK) {
9231 gtk_widget_destroy(t->uri_entry);
9232 gtk_widget_destroy(t->stop);
9233 gtk_widget_destroy(t->js_toggle);
9236 gtk_widget_destroy(t->tab_elems.eventbox);
9237 gtk_widget_destroy(t->vbox);
9239 /* just in case */
9240 if (t->search_id)
9241 g_source_remove(t->search_id);
9243 g_free(t->user_agent);
9244 g_free(t->stylesheet);
9245 g_free(t->tmp_uri);
9246 g_free(t);
9248 if (TAILQ_EMPTY(&tabs)) {
9249 if (browser_mode == XT_BM_KIOSK)
9250 create_new_tab(home, NULL, 1, -1);
9251 else
9252 create_new_tab(NULL, NULL, 1, -1);
9255 /* recreate session */
9256 if (session_autosave) {
9257 a.s = NULL;
9258 save_tabs(t, &a);
9261 recalc_tabs();
9262 recolor_compact_tabs();
9265 void
9266 update_statusbar_zoom(struct tab *t)
9268 gfloat zoom;
9269 char s[16] = { '\0' };
9271 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9272 if ((zoom <= 0.99 || zoom >= 1.01))
9273 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9274 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9277 void
9278 setzoom_webkit(struct tab *t, int adjust)
9280 #define XT_ZOOMPERCENT 0.04
9282 gfloat zoom;
9284 if (t == NULL) {
9285 show_oops(NULL, "setzoom_webkit invalid parameters");
9286 return;
9289 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9290 if (adjust == XT_ZOOM_IN)
9291 zoom += XT_ZOOMPERCENT;
9292 else if (adjust == XT_ZOOM_OUT)
9293 zoom -= XT_ZOOMPERCENT;
9294 else if (adjust > 0)
9295 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9296 else {
9297 show_oops(t, "setzoom_webkit invalid zoom value");
9298 return;
9301 if (zoom < XT_ZOOMPERCENT)
9302 zoom = XT_ZOOMPERCENT;
9303 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9304 update_statusbar_zoom(t);
9307 gboolean
9308 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9310 struct tab *t = (struct tab *) data;
9312 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9314 switch (event->button) {
9315 case 1:
9316 set_current_tab(t->tab_id);
9317 break;
9318 case 2:
9319 delete_tab(t);
9320 break;
9323 return TRUE;
9326 void
9327 append_tab(struct tab *t)
9329 if (t == NULL)
9330 return;
9332 TAILQ_INSERT_TAIL(&tabs, t, entry);
9333 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9336 GtkWidget *
9337 create_sbe(int width)
9339 GtkWidget *sbe;
9341 sbe = gtk_entry_new();
9342 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9343 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9344 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9345 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9346 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9347 gtk_widget_set_size_request(sbe, width, -1);
9349 return sbe;
9352 struct tab *
9353 create_new_tab(char *title, struct undo *u, int focus, int position)
9355 struct tab *t;
9356 int load = 1, id;
9357 GtkWidget *b, *bb;
9358 WebKitWebHistoryItem *item;
9359 GList *items;
9360 GdkColor color;
9361 char *p;
9362 int sbe_p = 0, sbe_b = 0,
9363 sbe_z = 0;
9365 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9367 if (tabless && !TAILQ_EMPTY(&tabs)) {
9368 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9369 return (NULL);
9372 t = g_malloc0(sizeof *t);
9374 if (title == NULL) {
9375 title = "(untitled)";
9376 load = 0;
9379 t->vbox = gtk_vbox_new(FALSE, 0);
9381 /* label + button for tab */
9382 b = gtk_hbox_new(FALSE, 0);
9383 t->tab_content = b;
9385 #if GTK_CHECK_VERSION(2, 20, 0)
9386 t->spinner = gtk_spinner_new();
9387 #endif
9388 t->label = gtk_label_new(title);
9389 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9390 gtk_widget_set_size_request(t->label, 100, 0);
9391 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9392 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9393 gtk_widget_set_size_request(b, 130, 0);
9395 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9396 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9397 #if GTK_CHECK_VERSION(2, 20, 0)
9398 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9399 #endif
9401 /* toolbar */
9402 if (browser_mode == XT_BM_KIOSK) {
9403 t->toolbar = create_kiosk_toolbar(t);
9404 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9406 } else {
9407 t->toolbar = create_toolbar(t);
9408 if (fancy_bar)
9409 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9410 FALSE, 0);
9413 /* marks */
9414 marks_clear(t);
9416 /* browser */
9417 t->browser_win = create_browser(t);
9418 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9420 /* oops message for user feedback */
9421 t->oops = gtk_entry_new();
9422 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9423 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9424 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9425 gdk_color_parse(XT_COLOR_RED, &color);
9426 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9427 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9428 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9430 /* command entry */
9431 t->cmd = gtk_entry_new();
9432 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9433 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9434 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9435 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9437 /* status bar */
9438 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9440 t->sbe.statusbar = gtk_entry_new();
9441 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9442 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9443 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9444 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9446 /* create these widgets only if specified in statusbar_elems */
9448 t->sbe.position = create_sbe(40);
9449 t->sbe.zoom = create_sbe(40);
9450 t->sbe.buffercmd = create_sbe(60);
9452 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9454 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9455 TRUE, FALSE);
9457 /* gtk widgets cannot be added to a box twice. sbe_* variables
9458 make sure of this */
9459 for (p = statusbar_elems; *p != '\0'; p++) {
9460 switch (*p) {
9461 case '|':
9463 GtkWidget *sep = gtk_vseparator_new();
9465 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9466 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9467 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9468 FALSE, FALSE, FALSE);
9469 break;
9471 case 'P':
9472 if (sbe_p) {
9473 warnx("flag \"%c\" specified more than "
9474 "once in statusbar_elems\n", *p);
9475 break;
9477 sbe_p = 1;
9478 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9479 t->sbe.position, FALSE, FALSE, FALSE);
9480 break;
9481 case 'B':
9482 if (sbe_b) {
9483 warnx("flag \"%c\" specified more than "
9484 "once in statusbar_elems\n", *p);
9485 break;
9487 sbe_b = 1;
9488 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9489 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9490 break;
9491 case 'Z':
9492 if (sbe_z) {
9493 warnx("flag \"%c\" specified more than "
9494 "once in statusbar_elems\n", *p);
9495 break;
9497 sbe_z = 1;
9498 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9499 t->sbe.zoom, FALSE, FALSE, FALSE);
9500 break;
9501 default:
9502 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9503 break;
9507 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9509 /* buffer list */
9510 t->buffers = create_buffers(t);
9511 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9513 /* xtp meaning is normal by default */
9514 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9516 /* set empty favicon */
9517 xt_icon_from_name(t, "text-html");
9519 /* and show it all */
9520 gtk_widget_show_all(b);
9521 gtk_widget_show_all(t->vbox);
9523 /* compact tab bar */
9524 t->tab_elems.label = gtk_label_new(title);
9525 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9526 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9527 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9528 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9530 t->tab_elems.eventbox = gtk_event_box_new();
9531 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9532 t->tab_elems.sep = gtk_vseparator_new();
9534 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9535 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9536 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9537 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9538 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9539 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9541 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9542 TRUE, 0);
9543 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9544 FALSE, 0);
9545 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9546 t->tab_elems.box);
9548 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9549 TRUE, 0);
9550 gtk_widget_show_all(t->tab_elems.eventbox);
9552 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9553 append_tab(t);
9554 else {
9555 id = position >= 0 ? position :
9556 gtk_notebook_get_current_page(notebook) + 1;
9557 if (id > gtk_notebook_get_n_pages(notebook))
9558 append_tab(t);
9559 else {
9560 TAILQ_INSERT_TAIL(&tabs, t, entry);
9561 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9562 gtk_box_reorder_child(GTK_BOX(tab_bar),
9563 t->tab_elems.eventbox, id);
9564 recalc_tabs();
9568 #if GTK_CHECK_VERSION(2, 20, 0)
9569 /* turn spinner off if we are a new tab without uri */
9570 if (!load) {
9571 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9572 gtk_widget_hide(t->spinner);
9574 #endif
9575 /* make notebook tabs reorderable */
9576 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9578 /* compact tabs clickable */
9579 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9580 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9582 g_object_connect(G_OBJECT(t->cmd),
9583 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9584 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9585 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9586 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9587 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9588 (char *)NULL);
9590 /* reuse wv_button_cb to hide oops */
9591 g_object_connect(G_OBJECT(t->oops),
9592 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9593 (char *)NULL);
9595 g_signal_connect(t->buffers,
9596 "row-activated", G_CALLBACK(row_activated_cb), t);
9597 g_object_connect(G_OBJECT(t->buffers),
9598 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9600 g_object_connect(G_OBJECT(t->wv),
9601 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9602 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9603 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9604 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9605 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9606 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9607 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9608 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9609 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9610 "signal::event", G_CALLBACK(webview_event_cb), t,
9611 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9612 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9613 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9614 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9615 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9616 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9617 (char *)NULL);
9618 g_signal_connect(t->wv,
9619 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9620 g_signal_connect(t->wv,
9621 "notify::title", G_CALLBACK(notify_title_cb), t);
9623 /* hijack the unused keys as if we were the browser */
9624 g_object_connect(G_OBJECT(t->toolbar),
9625 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9626 (char *)NULL);
9628 g_signal_connect(G_OBJECT(bb), "button_press_event",
9629 G_CALLBACK(tab_close_cb), t);
9631 /* setup history */
9632 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9633 /* restore the tab's history */
9634 if (u && u->history) {
9635 items = u->history;
9636 while (items) {
9637 item = items->data;
9638 webkit_web_back_forward_list_add_item(t->bfl, item);
9639 items = g_list_next(items);
9642 item = g_list_nth_data(u->history, u->back);
9643 if (item)
9644 webkit_web_view_go_to_back_forward_item(t->wv, item);
9646 g_list_free(items);
9647 g_list_free(u->history);
9648 } else
9649 webkit_web_back_forward_list_clear(t->bfl);
9651 /* hide stuff */
9652 hide_cmd(t);
9653 hide_oops(t);
9654 hide_buffers(t);
9655 url_set_visibility();
9656 statusbar_set_visibility();
9658 if (focus) {
9659 set_current_tab(t->tab_id);
9660 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9661 t->tab_id);
9663 if (load) {
9664 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9665 load_uri(t, title);
9666 } else {
9667 if (show_url == 1)
9668 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9669 else
9670 focus_webview(t);
9672 } else if (load)
9673 load_uri(t, title);
9675 recolor_compact_tabs();
9676 setzoom_webkit(t, XT_ZOOM_NORMAL);
9677 return (t);
9680 void
9681 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9682 gpointer *udata)
9684 struct tab *t;
9685 const gchar *uri;
9687 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9689 if (gtk_notebook_get_current_page(notebook) == -1)
9690 recalc_tabs();
9692 TAILQ_FOREACH(t, &tabs, entry) {
9693 if (t->tab_id == pn) {
9694 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9695 "%d\n", pn);
9697 uri = get_title(t, TRUE);
9698 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9700 hide_cmd(t);
9701 hide_oops(t);
9703 if (t->focus_wv) {
9704 /* can't use focus_webview here */
9705 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9711 void
9712 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9713 gpointer *udata)
9715 struct tab *t = NULL, *tt;
9717 recalc_tabs();
9719 TAILQ_FOREACH(tt, &tabs, entry)
9720 if (tt->tab_id == pn) {
9721 t = tt;
9722 break;
9724 if (t == NULL)
9725 return;
9726 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9728 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9729 t->tab_id);
9732 void
9733 menuitem_response(struct tab *t)
9735 gtk_notebook_set_current_page(notebook, t->tab_id);
9738 gboolean
9739 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9741 GtkWidget *menu, *menu_items;
9742 GdkEventButton *bevent;
9743 const gchar *uri;
9744 struct tab *ti;
9746 if (event->type == GDK_BUTTON_PRESS) {
9747 bevent = (GdkEventButton *) event;
9748 menu = gtk_menu_new();
9750 TAILQ_FOREACH(ti, &tabs, entry) {
9751 if ((uri = get_uri(ti)) == NULL)
9752 /* XXX make sure there is something to print */
9753 /* XXX add gui pages in here to look purdy */
9754 uri = "(untitled)";
9755 menu_items = gtk_menu_item_new_with_label(uri);
9756 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9757 gtk_widget_show(menu_items);
9759 g_signal_connect_swapped((menu_items),
9760 "activate", G_CALLBACK(menuitem_response),
9761 (gpointer)ti);
9764 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9765 bevent->button, bevent->time);
9767 /* unref object so it'll free itself when popped down */
9768 #if !GTK_CHECK_VERSION(3, 0, 0)
9769 /* XXX does not need unref with gtk+3? */
9770 g_object_ref_sink(menu);
9771 g_object_unref(menu);
9772 #endif
9774 return (TRUE /* eat event */);
9777 return (FALSE /* propagate */);
9781 icon_size_map(int icon_size)
9783 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9784 icon_size > GTK_ICON_SIZE_DIALOG)
9785 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9787 return (icon_size);
9790 GtkWidget *
9791 create_button(char *name, char *stockid, int size)
9793 GtkWidget *button, *image;
9794 gchar *rcstring;
9795 int gtk_icon_size;
9797 rcstring = g_strdup_printf(
9798 "style \"%s-style\"\n"
9799 "{\n"
9800 " GtkWidget::focus-padding = 0\n"
9801 " GtkWidget::focus-line-width = 0\n"
9802 " xthickness = 0\n"
9803 " ythickness = 0\n"
9804 "}\n"
9805 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9806 gtk_rc_parse_string(rcstring);
9807 g_free(rcstring);
9808 button = gtk_button_new();
9809 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9810 gtk_icon_size = icon_size_map(size ? size : icon_size);
9812 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9813 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9814 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9815 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9816 gtk_widget_set_name(button, name);
9817 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9819 return (button);
9822 void
9823 button_set_stockid(GtkWidget *button, char *stockid)
9825 GtkWidget *image;
9827 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9828 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9829 gtk_button_set_image(GTK_BUTTON(button), image);
9832 void
9833 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9835 gchar *p = NULL;
9836 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9837 gint len;
9839 if (xterm_workaround == 0)
9840 return;
9843 * xterm doesn't play nice with clipboards because it clears the
9844 * primary when clicked. We rely on primary being set to properly
9845 * handle middle mouse button clicks (paste). So when someone clears
9846 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9847 * other application behavior (as in DON'T clear primary).
9850 p = gtk_clipboard_wait_for_text(primary);
9851 if (p == NULL) {
9852 if (gdk_property_get(gdk_get_default_root_window(),
9853 atom,
9854 gdk_atom_intern("STRING", FALSE),
9856 1024 * 1024 /* picked out of my butt */,
9857 FALSE,
9858 NULL,
9859 NULL,
9860 &len,
9861 (guchar **)&p)) {
9862 /* yes sir, we need to NUL the string */
9863 p[len] = '\0';
9864 gtk_clipboard_set_text(primary, p, -1);
9868 if (p)
9869 g_free(p);
9872 void
9873 create_canvas(void)
9875 GtkWidget *vbox;
9876 GList *l = NULL;
9877 GdkPixbuf *pb;
9878 char file[PATH_MAX];
9879 int i;
9881 vbox = gtk_vbox_new(FALSE, 0);
9882 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9883 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9884 #if !GTK_CHECK_VERSION(3, 0, 0)
9885 /* XXX seems to be needed with gtk+2 */
9886 gtk_notebook_set_tab_hborder(notebook, 0);
9887 gtk_notebook_set_tab_vborder(notebook, 0);
9888 #endif
9889 gtk_notebook_set_scrollable(notebook, TRUE);
9890 gtk_notebook_set_show_border(notebook, FALSE);
9891 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9893 abtn = gtk_button_new();
9894 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9895 gtk_widget_set_size_request(arrow, -1, -1);
9896 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9897 gtk_widget_set_size_request(abtn, -1, 20);
9899 #if GTK_CHECK_VERSION(2, 20, 0)
9900 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9901 #endif
9902 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9904 /* compact tab bar */
9905 tab_bar = gtk_hbox_new(TRUE, 0);
9907 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9908 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9909 gtk_widget_set_size_request(vbox, -1, -1);
9911 g_object_connect(G_OBJECT(notebook),
9912 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9913 (char *)NULL);
9914 g_object_connect(G_OBJECT(notebook),
9915 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9916 NULL, (char *)NULL);
9917 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9918 G_CALLBACK(arrow_cb), NULL);
9920 main_window = create_window();
9921 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9923 /* icons */
9924 for (i = 0; i < LENGTH(icons); i++) {
9925 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9926 pb = gdk_pixbuf_new_from_file(file, NULL);
9927 l = g_list_append(l, pb);
9929 gtk_window_set_default_icon_list(l);
9931 /* clipboard work around */
9932 if (xterm_workaround)
9933 g_signal_connect(
9934 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9935 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9937 gtk_widget_show_all(abtn);
9938 gtk_widget_show_all(main_window);
9939 notebook_tab_set_visibility();
9942 void
9943 set_hook(void **hook, char *name)
9945 if (hook == NULL)
9946 errx(1, "set_hook");
9948 if (*hook == NULL) {
9949 *hook = dlsym(RTLD_NEXT, name);
9950 if (*hook == NULL)
9951 errx(1, "can't hook %s", name);
9955 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9956 gboolean
9957 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9959 g_return_val_if_fail(cookie1, FALSE);
9960 g_return_val_if_fail(cookie2, FALSE);
9962 return (!strcmp (cookie1->name, cookie2->name) &&
9963 !strcmp (cookie1->value, cookie2->value) &&
9964 !strcmp (cookie1->path, cookie2->path) &&
9965 !strcmp (cookie1->domain, cookie2->domain));
9968 void
9969 transfer_cookies(void)
9971 GSList *cf;
9972 SoupCookie *sc, *pc;
9974 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9976 for (;cf; cf = cf->next) {
9977 pc = cf->data;
9978 sc = soup_cookie_copy(pc);
9979 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9982 soup_cookies_free(cf);
9985 void
9986 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9988 GSList *cf;
9989 SoupCookie *ci;
9991 print_cookie("soup_cookie_jar_delete_cookie", c);
9993 if (cookies_enabled == 0)
9994 return;
9996 if (jar == NULL || c == NULL)
9997 return;
9999 /* find and remove from persistent jar */
10000 cf = soup_cookie_jar_all_cookies(p_cookiejar);
10002 for (;cf; cf = cf->next) {
10003 ci = cf->data;
10004 if (soup_cookie_equal(ci, c)) {
10005 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
10006 break;
10010 soup_cookies_free(cf);
10012 /* delete from session jar */
10013 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
10016 void
10017 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
10019 struct domain *d = NULL;
10020 SoupCookie *c;
10021 FILE *r_cookie_f;
10023 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
10024 jar, p_cookiejar, s_cookiejar);
10026 if (cookies_enabled == 0)
10027 return;
10029 /* see if we are up and running */
10030 if (p_cookiejar == NULL) {
10031 _soup_cookie_jar_add_cookie(jar, cookie);
10032 return;
10034 /* disallow p_cookiejar adds, shouldn't happen */
10035 if (jar == p_cookiejar)
10036 return;
10038 /* sanity */
10039 if (jar == NULL || cookie == NULL)
10040 return;
10042 if (enable_cookie_whitelist &&
10043 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
10044 blocked_cookies++;
10045 DNPRINTF(XT_D_COOKIE,
10046 "soup_cookie_jar_add_cookie: reject %s\n",
10047 cookie->domain);
10048 if (save_rejected_cookies) {
10049 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
10050 show_oops(NULL, "can't open reject cookie file");
10051 return;
10053 fseek(r_cookie_f, 0, SEEK_END);
10054 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
10055 cookie->http_only ? "#HttpOnly_" : "",
10056 cookie->domain,
10057 *cookie->domain == '.' ? "TRUE" : "FALSE",
10058 cookie->path,
10059 cookie->secure ? "TRUE" : "FALSE",
10060 cookie->expires ?
10061 (gulong)soup_date_to_time_t(cookie->expires) :
10063 cookie->name,
10064 cookie->value);
10065 fflush(r_cookie_f);
10066 fclose(r_cookie_f);
10068 if (!allow_volatile_cookies)
10069 return;
10072 if (cookie->expires == NULL && session_timeout) {
10073 soup_cookie_set_expires(cookie,
10074 soup_date_new_from_now(session_timeout));
10075 print_cookie("modified add cookie", cookie);
10078 /* see if we are white listed for persistence */
10079 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
10080 /* add to persistent jar */
10081 c = soup_cookie_copy(cookie);
10082 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
10083 _soup_cookie_jar_add_cookie(p_cookiejar, c);
10086 /* add to session jar */
10087 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
10088 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
10091 void
10092 setup_cookies(void)
10094 char file[PATH_MAX];
10096 set_hook((void *)&_soup_cookie_jar_add_cookie,
10097 "soup_cookie_jar_add_cookie");
10098 set_hook((void *)&_soup_cookie_jar_delete_cookie,
10099 "soup_cookie_jar_delete_cookie");
10101 if (cookies_enabled == 0)
10102 return;
10105 * the following code is intricate due to overriding several libsoup
10106 * functions.
10107 * do not alter order of these operations.
10110 /* rejected cookies */
10111 if (save_rejected_cookies)
10112 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
10113 XT_REJECT_FILE);
10115 /* persistent cookies */
10116 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
10117 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
10119 /* session cookies */
10120 s_cookiejar = soup_cookie_jar_new();
10121 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
10122 cookie_policy, (void *)NULL);
10123 transfer_cookies();
10125 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
10128 void
10129 setup_proxy(char *uri)
10131 if (proxy_uri) {
10132 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
10133 soup_uri_free(proxy_uri);
10134 proxy_uri = NULL;
10136 if (http_proxy) {
10137 if (http_proxy != uri) {
10138 g_free(http_proxy);
10139 http_proxy = NULL;
10143 if (uri) {
10144 http_proxy = g_strdup(uri);
10145 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
10146 proxy_uri = soup_uri_new(http_proxy);
10147 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
10148 g_object_set(session, "proxy-uri", proxy_uri,
10149 (char *)NULL);
10154 set_http_proxy(char *proxy)
10156 SoupURI *uri;
10158 if (proxy == NULL)
10159 return (1);
10161 /* see if we need to clear it instead */
10162 if (strlen(proxy) == 0) {
10163 setup_proxy(NULL);
10164 return (0);
10167 uri = soup_uri_new(proxy);
10168 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
10169 return (1);
10171 setup_proxy(proxy);
10173 soup_uri_free(uri);
10175 return (0);
10179 send_cmd_to_socket(char *cmd)
10181 int s, len, rv = 1;
10182 struct sockaddr_un sa;
10184 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10185 warnx("%s: socket", __func__);
10186 return (rv);
10189 sa.sun_family = AF_UNIX;
10190 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10191 work_dir, XT_SOCKET_FILE);
10192 len = SUN_LEN(&sa);
10194 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10195 warnx("%s: connect", __func__);
10196 goto done;
10199 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
10200 warnx("%s: send", __func__);
10201 goto done;
10204 rv = 0;
10205 done:
10206 close(s);
10207 return (rv);
10210 gboolean
10211 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
10213 int s, n;
10214 char str[XT_MAX_URL_LENGTH];
10215 socklen_t t = sizeof(struct sockaddr_un);
10216 struct sockaddr_un sa;
10217 struct passwd *p;
10218 uid_t uid;
10219 gid_t gid;
10220 struct tab *tt;
10221 gint fd = g_io_channel_unix_get_fd(source);
10223 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
10224 warn("accept");
10225 return (FALSE);
10228 if (getpeereid(s, &uid, &gid) == -1) {
10229 warn("getpeereid");
10230 return (FALSE);
10232 if (uid != getuid() || gid != getgid()) {
10233 warnx("unauthorized user");
10234 return (FALSE);
10237 p = getpwuid(uid);
10238 if (p == NULL) {
10239 warnx("not a valid user");
10240 return (FALSE);
10243 n = recv(s, str, sizeof(str), 0);
10244 if (n <= 0)
10245 return (TRUE);
10247 tt = TAILQ_LAST(&tabs, tab_list);
10248 cmd_execute(tt, str);
10249 return (TRUE);
10253 is_running(void)
10255 int s, len, rv = 1;
10256 struct sockaddr_un sa;
10258 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10259 warn("is_running: socket");
10260 return (-1);
10263 sa.sun_family = AF_UNIX;
10264 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10265 work_dir, XT_SOCKET_FILE);
10266 len = SUN_LEN(&sa);
10268 /* connect to see if there is a listener */
10269 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10270 rv = 0; /* not running */
10271 else
10272 rv = 1; /* already running */
10274 close(s);
10276 return (rv);
10280 build_socket(void)
10282 int s, len;
10283 struct sockaddr_un sa;
10285 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10286 warn("build_socket: socket");
10287 return (-1);
10290 sa.sun_family = AF_UNIX;
10291 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10292 work_dir, XT_SOCKET_FILE);
10293 len = SUN_LEN(&sa);
10295 /* connect to see if there is a listener */
10296 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10297 /* no listener so we will */
10298 unlink(sa.sun_path);
10300 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10301 warn("build_socket: bind");
10302 goto done;
10305 if (listen(s, 1) == -1) {
10306 warn("build_socket: listen");
10307 goto done;
10310 return (s);
10313 done:
10314 close(s);
10315 return (-1);
10318 gboolean
10319 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10320 GtkTreeIter *iter, struct tab *t)
10322 gchar *value;
10324 gtk_tree_model_get(model, iter, 0, &value, -1);
10325 load_uri(t, value);
10326 g_free(value);
10328 return (FALSE);
10331 gboolean
10332 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10333 GtkTreeIter *iter, struct tab *t)
10335 gchar *value;
10337 gtk_tree_model_get(model, iter, 0, &value, -1);
10338 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10339 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10340 g_free(value);
10342 return (TRUE);
10345 void
10346 completion_add_uri(const gchar *uri)
10348 GtkTreeIter iter;
10350 /* add uri to list_store */
10351 gtk_list_store_append(completion_model, &iter);
10352 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10355 gboolean
10356 completion_match(GtkEntryCompletion *completion, const gchar *key,
10357 GtkTreeIter *iter, gpointer user_data)
10359 gchar *value;
10360 gboolean match = FALSE;
10362 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10363 -1);
10365 if (value == NULL)
10366 return FALSE;
10368 match = match_uri(value, key);
10370 g_free(value);
10371 return (match);
10374 void
10375 completion_add(struct tab *t)
10377 /* enable completion for tab */
10378 t->completion = gtk_entry_completion_new();
10379 gtk_entry_completion_set_text_column(t->completion, 0);
10380 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10381 gtk_entry_completion_set_model(t->completion,
10382 GTK_TREE_MODEL(completion_model));
10383 gtk_entry_completion_set_match_func(t->completion, completion_match,
10384 NULL, NULL);
10385 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10386 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10387 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10388 G_CALLBACK(completion_select_cb), t);
10389 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10390 G_CALLBACK(completion_hover_cb), t);
10393 void
10394 xxx_dir(char *dir)
10396 struct stat sb;
10398 if (stat(dir, &sb)) {
10399 if (mkdir(dir, S_IRWXU) == -1)
10400 err(1, "mkdir %s", dir);
10401 if (stat(dir, &sb))
10402 err(1, "stat %s", dir);
10404 if (S_ISDIR(sb.st_mode) == 0)
10405 errx(1, "%s not a dir", dir);
10406 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10407 warnx("fixing invalid permissions on %s", dir);
10408 if (chmod(dir, S_IRWXU) == -1)
10409 err(1, "chmod %s", dir);
10413 void
10414 usage(void)
10416 fprintf(stderr,
10417 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10418 exit(0);
10421 GStaticRecMutex my_gdk_mtx = G_STATIC_REC_MUTEX_INIT;
10422 volatile int mtx_depth;
10423 int mtx_complain;
10426 * The linux flash plugin violates the gdk locking mechanism.
10427 * Work around the issue by using a recursive mutex with some match applied
10428 * to see if we hit a buggy condition.
10430 * The following code is painful so just don't read it. It really doesn't
10431 * make much sense but seems to work.
10433 void
10434 mtx_lock(void)
10436 g_static_rec_mutex_lock(&my_gdk_mtx);
10437 mtx_depth++;
10439 if (mtx_depth <= 0) {
10440 /* should not happen */
10441 show_oops(NULL, "negative mutex locking bug, trying to "
10442 "correct");
10443 fprintf(stderr, "negative mutex locking bug, trying to "
10444 "correct\n");
10445 g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10446 g_static_rec_mutex_lock(&my_gdk_mtx);
10447 mtx_depth = 1;
10448 return;
10451 if (mtx_depth != 1) {
10452 /* decrease mutext depth to 1 */
10453 do {
10454 g_static_rec_mutex_unlock(&my_gdk_mtx);
10455 mtx_depth--;
10456 } while (mtx_depth > 1);
10460 void
10461 mtx_unlock(void)
10463 guint x;
10465 /* if mutex depth isn't 1 then something went bad */
10466 if (mtx_depth != 1) {
10467 x = g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10468 if (x != 1) {
10469 /* should not happen */
10470 show_oops(NULL, "mutex unlocking bug, trying to "
10471 "correct");
10472 fprintf(stderr, "mutex unlocking bug, trying to "
10473 "correct\n");
10475 mtx_depth = 0;
10476 if (mtx_complain == 0) {
10477 show_oops(NULL, "buggy mutex implementation detected, "
10478 "work around implemented");
10479 fprintf(stderr, "buggy mutex implementation detected, "
10480 "work around implemented");
10481 mtx_complain = 1;
10483 return;
10486 mtx_depth--;
10487 g_static_rec_mutex_unlock(&my_gdk_mtx);
10491 main(int argc, char *argv[])
10493 struct stat sb;
10494 int c, s, optn = 0, opte = 0, focus = 1;
10495 char conf[PATH_MAX] = { '\0' };
10496 char file[PATH_MAX];
10497 char *env_proxy = NULL;
10498 char *cmd = NULL;
10499 FILE *f = NULL;
10500 struct karg a;
10501 struct sigaction sact;
10502 GIOChannel *channel;
10503 struct rlimit rlp;
10505 start_argv = argv;
10507 /* prepare gtk */
10508 #ifdef USE_THREADS
10509 g_thread_init(NULL);
10510 gdk_threads_set_lock_functions(mtx_lock, mtx_unlock);
10511 gdk_threads_init();
10512 gdk_threads_enter();
10514 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
10515 #endif
10516 gtk_init(&argc, &argv);
10518 gnutls_global_init();
10520 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10522 RB_INIT(&hl);
10523 RB_INIT(&js_wl);
10524 RB_INIT(&pl_wl);
10525 RB_INIT(&downloads);
10527 TAILQ_INIT(&sessions);
10528 TAILQ_INIT(&tabs);
10529 TAILQ_INIT(&mtl);
10530 TAILQ_INIT(&aliases);
10531 TAILQ_INIT(&undos);
10532 TAILQ_INIT(&kbl);
10533 TAILQ_INIT(&spl);
10534 TAILQ_INIT(&chl);
10535 TAILQ_INIT(&shl);
10537 /* fiddle with ulimits */
10538 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10539 warn("getrlimit");
10540 else {
10541 /* just use them all */
10542 rlp.rlim_cur = rlp.rlim_max;
10543 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10544 warn("setrlimit");
10545 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10546 warn("getrlimit");
10547 else if (rlp.rlim_cur <= 256)
10548 startpage_add("%s requires at least 256 file "
10549 "descriptors, currently it has up to %d available",
10550 __progname, rlp.rlim_cur);
10553 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10554 switch (c) {
10555 case 'S':
10556 show_url = 0;
10557 break;
10558 case 'T':
10559 show_tabs = 0;
10560 break;
10561 case 'V':
10562 errx(0 , "Version: %s", version);
10563 break;
10564 case 'f':
10565 strlcpy(conf, optarg, sizeof(conf));
10566 break;
10567 case 's':
10568 strlcpy(named_session, optarg, sizeof(named_session));
10569 break;
10570 case 't':
10571 tabless = 1;
10572 break;
10573 case 'n':
10574 optn = 1;
10575 break;
10576 case 'e':
10577 opte = 1;
10578 break;
10579 default:
10580 usage();
10581 /* NOTREACHED */
10584 argc -= optind;
10585 argv += optind;
10587 init_keybindings();
10589 /* generate session keys for xtp pages */
10590 generate_xtp_session_key(&dl_session_key);
10591 generate_xtp_session_key(&hl_session_key);
10592 generate_xtp_session_key(&cl_session_key);
10593 generate_xtp_session_key(&fl_session_key);
10595 /* signals */
10596 bzero(&sact, sizeof(sact));
10597 sigemptyset(&sact.sa_mask);
10598 sact.sa_handler = sigchild;
10599 sact.sa_flags = SA_NOCLDSTOP;
10600 sigaction(SIGCHLD, &sact, NULL);
10602 /* set download dir */
10603 pwd = getpwuid(getuid());
10604 if (pwd == NULL)
10605 errx(1, "invalid user %d", getuid());
10606 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10608 /* compile buffer command regexes */
10609 buffercmd_init();
10611 /* set default string settings */
10612 home = g_strdup("https://www.cyphertite.com");
10613 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10614 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10615 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10616 cmd_font_name = g_strdup("monospace normal 9");
10617 oops_font_name = g_strdup("monospace normal 9");
10618 statusbar_font_name = g_strdup("monospace normal 9");
10619 tabbar_font_name = g_strdup("monospace normal 9");
10620 statusbar_elems = g_strdup("BP");
10621 encoding = g_strdup("ISO-8859-1");
10623 /* read config file */
10624 if (strlen(conf) == 0)
10625 snprintf(conf, sizeof conf, "%s/.%s",
10626 pwd->pw_dir, XT_CONF_FILE);
10627 config_parse(conf, 0);
10629 /* init fonts */
10630 cmd_font = pango_font_description_from_string(cmd_font_name);
10631 oops_font = pango_font_description_from_string(oops_font_name);
10632 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10633 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10635 /* working directory */
10636 if (strlen(work_dir) == 0)
10637 snprintf(work_dir, sizeof work_dir, "%s/%s",
10638 pwd->pw_dir, XT_DIR);
10639 xxx_dir(work_dir);
10641 /* icon cache dir */
10642 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10643 xxx_dir(cache_dir);
10645 /* certs dir */
10646 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10647 xxx_dir(certs_dir);
10649 /* sessions dir */
10650 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10651 work_dir, XT_SESSIONS_DIR);
10652 xxx_dir(sessions_dir);
10654 /* runtime settings that can override config file */
10655 if (runtime_settings[0] != '\0')
10656 config_parse(runtime_settings, 1);
10658 /* download dir */
10659 if (!strcmp(download_dir, pwd->pw_dir))
10660 strlcat(download_dir, "/downloads", sizeof download_dir);
10661 xxx_dir(download_dir);
10663 /* favorites file */
10664 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10665 if (stat(file, &sb)) {
10666 warnx("favorites file doesn't exist, creating it");
10667 if ((f = fopen(file, "w")) == NULL)
10668 err(1, "favorites");
10669 fclose(f);
10672 /* quickmarks file */
10673 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10674 if (stat(file, &sb)) {
10675 warnx("quickmarks file doesn't exist, creating it");
10676 if ((f = fopen(file, "w")) == NULL)
10677 err(1, "quickmarks");
10678 fclose(f);
10681 /* search history */
10682 if (history_autosave) {
10683 snprintf(search_file, sizeof search_file, "%s/%s",
10684 work_dir, XT_SEARCH_FILE);
10685 if (stat(search_file, &sb)) {
10686 warnx("search history file doesn't exist, creating it");
10687 if ((f = fopen(search_file, "w")) == NULL)
10688 err(1, "search_history");
10689 fclose(f);
10691 history_read(&shl, search_file, &search_history_count);
10694 /* command history */
10695 if (history_autosave) {
10696 snprintf(command_file, sizeof command_file, "%s/%s",
10697 work_dir, XT_COMMAND_FILE);
10698 if (stat(command_file, &sb)) {
10699 warnx("command history file doesn't exist, creating it");
10700 if ((f = fopen(command_file, "w")) == NULL)
10701 err(1, "command_history");
10702 fclose(f);
10704 history_read(&chl, command_file, &cmd_history_count);
10707 /* cookies */
10708 session = webkit_get_default_session();
10709 setup_cookies();
10711 /* certs */
10712 if (ssl_ca_file) {
10713 if (stat(ssl_ca_file, &sb)) {
10714 warnx("no CA file: %s", ssl_ca_file);
10715 g_free(ssl_ca_file);
10716 ssl_ca_file = NULL;
10717 } else
10718 g_object_set(session,
10719 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10720 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10721 (void *)NULL);
10724 /* guess_search regex */
10725 if (url_regex == NULL)
10726 url_regex = g_strdup(XT_URL_REGEX);
10727 if (url_regex)
10728 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10729 startpage_add("invalid url regex %s", url_regex);
10731 /* proxy */
10732 env_proxy = getenv("http_proxy");
10733 if (env_proxy)
10734 setup_proxy(env_proxy);
10735 else
10736 setup_proxy(http_proxy);
10738 if (opte) {
10739 send_cmd_to_socket(argv[0]);
10740 exit(0);
10743 /* set some connection parameters */
10744 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10745 g_object_set(session, "max-conns-per-host", max_host_connections,
10746 (char *)NULL);
10748 /* see if there is already an xxxterm running */
10749 if (single_instance && is_running()) {
10750 optn = 1;
10751 warnx("already running");
10754 if (optn) {
10755 while (argc) {
10756 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10757 send_cmd_to_socket(cmd);
10758 if (cmd)
10759 g_free(cmd);
10761 argc--;
10762 argv++;
10764 exit(0);
10767 /* uri completion */
10768 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10770 /* buffers */
10771 buffers_store = gtk_list_store_new
10772 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10774 qmarks_load();
10776 /* go graphical */
10777 create_canvas();
10778 notebook_tab_set_visibility();
10780 if (save_global_history)
10781 restore_global_history();
10783 /* restore session list */
10784 restore_sessions_list();
10786 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10787 restore_saved_tabs();
10788 else {
10789 a.s = named_session;
10790 a.i = XT_SES_DONOTHING;
10791 open_tabs(NULL, &a);
10794 /* see if we have an exception */
10795 if (!TAILQ_EMPTY(&spl)) {
10796 create_new_tab("about:startpage", NULL, focus, -1);
10797 focus = 0;
10800 while (argc) {
10801 create_new_tab(argv[0], NULL, focus, -1);
10802 focus = 0;
10804 argc--;
10805 argv++;
10808 if (TAILQ_EMPTY(&tabs))
10809 create_new_tab(home, NULL, 1, -1);
10811 if (enable_socket)
10812 if ((s = build_socket()) != -1) {
10813 channel = g_io_channel_unix_new(s);
10814 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10817 gtk_main();
10819 #ifdef USE_THREADS
10820 gdk_threads_leave();
10821 g_static_rec_mutex_unlock_full(&my_gdk_mtx); /* just in case */
10822 #endif
10824 gnutls_global_deinit();
10826 if (url_regex)
10827 regfree(&url_re);
10829 return (0);