init threads a bit earlier and don't enter critical section
[xxxterm.git] / xxxterm.c
blob9022fbb4e0f3687fd961d50b3a39ce8527eb101b
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pthread.h>
35 #include <pwd.h>
36 #include <regex.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
49 #include <libutil.h>
50 #include "freebsd/util.h"
51 #include <sys/tree.h>
52 #else /* OpenBSD */
53 #include <util.h>
54 #include <sys/tree.h>
55 #endif
56 #include <sys/queue.h>
57 #include <sys/resource.h>
58 #include <sys/socket.h>
59 #include <sys/stat.h>
60 #include <sys/time.h>
61 #include <sys/un.h>
63 #include <gtk/gtk.h>
64 #include <gdk/gdkkeysyms.h>
66 #if GTK_CHECK_VERSION(3,0,0)
67 /* we still use GDK_* instead of GDK_KEY_* */
68 #include <gdk/gdkkeysyms-compat.h>
69 #endif
71 #include <webkit/webkit.h>
72 #include <libsoup/soup.h>
73 #include <gnutls/gnutls.h>
74 #include <JavaScriptCore/JavaScript.h>
75 #include <gnutls/x509.h>
77 #include "javascript.h"
80 javascript.h borrowed from vimprobable2 under the following license:
82 Copyright (c) 2009 Leon Winter
83 Copyright (c) 2009 Hannes Schueller
84 Copyright (c) 2009 Matto Fransen
86 Permission is hereby granted, free of charge, to any person obtaining a copy
87 of this software and associated documentation files (the "Software"), to deal
88 in the Software without restriction, including without limitation the rights
89 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 copies of the Software, and to permit persons to whom the Software is
91 furnished to do so, subject to the following conditions:
93 The above copyright notice and this permission notice shall be included in
94 all copies or substantial portions of the Software.
96 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
102 THE SOFTWARE.
105 static char *version = "$xxxterm$";
107 /* hooked functions */
108 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
109 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
110 SoupCookie *);
112 /*#define XT_DEBUG*/
113 #ifdef XT_DEBUG
114 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
115 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
116 #define XT_D_MOVE 0x0001
117 #define XT_D_KEY 0x0002
118 #define XT_D_TAB 0x0004
119 #define XT_D_URL 0x0008
120 #define XT_D_CMD 0x0010
121 #define XT_D_NAV 0x0020
122 #define XT_D_DOWNLOAD 0x0040
123 #define XT_D_CONFIG 0x0080
124 #define XT_D_JS 0x0100
125 #define XT_D_FAVORITE 0x0200
126 #define XT_D_PRINTING 0x0400
127 #define XT_D_COOKIE 0x0800
128 #define XT_D_KEYBINDING 0x1000
129 #define XT_D_CLIP 0x2000
130 #define XT_D_BUFFERCMD 0x4000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
146 | XT_D_BUFFERCMD
148 #else
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
151 #endif
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
159 ~(GDK_BUTTON5_MASK))
161 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
163 char *icons[] = {
164 "xxxtermicon16.png",
165 "xxxtermicon32.png",
166 "xxxtermicon48.png",
167 "xxxtermicon64.png",
168 "xxxtermicon128.png"
171 struct tab {
172 TAILQ_ENTRY(tab) entry;
173 GtkWidget *vbox;
174 GtkWidget *tab_content;
175 struct {
176 GtkWidget *label;
177 GtkWidget *eventbox;
178 GtkWidget *box;
179 GtkWidget *sep;
180 } tab_elems;
181 GtkWidget *label;
182 GtkWidget *spinner;
183 GtkWidget *uri_entry;
184 GtkWidget *search_entry;
185 GtkWidget *toolbar;
186 GtkWidget *browser_win;
187 GtkWidget *statusbar_box;
188 struct {
189 GtkWidget *statusbar;
190 GtkWidget *buffercmd;
191 GtkWidget *zoom;
192 GtkWidget *position;
193 } sbe;
194 GtkWidget *cmd;
195 GtkWidget *buffers;
196 GtkWidget *oops;
197 GtkWidget *backward;
198 GtkWidget *forward;
199 GtkWidget *stop;
200 GtkWidget *gohome;
201 GtkWidget *js_toggle;
202 GtkEntryCompletion *completion;
203 guint tab_id;
204 WebKitWebView *wv;
206 WebKitWebHistoryItem *item;
207 WebKitWebBackForwardList *bfl;
209 /* favicon */
210 WebKitNetworkRequest *icon_request;
211 WebKitDownload *icon_download;
212 gchar *icon_dest_uri;
214 /* adjustments for browser */
215 GtkScrollbar *sb_h;
216 GtkScrollbar *sb_v;
217 GtkAdjustment *adjust_h;
218 GtkAdjustment *adjust_v;
220 /* flags */
221 int focus_wv;
222 int ctrl_click;
223 gchar *status;
224 int xtp_meaning; /* identifies dls/favorites */
225 gchar *tmp_uri;
226 int popup; /* 1 if cmd_entry has popup visible */
228 /* hints */
229 int hints_on;
230 int hint_mode;
231 #define XT_HINT_NONE (0)
232 #define XT_HINT_NUMERICAL (1)
233 #define XT_HINT_ALPHANUM (2)
234 char hint_buf[128];
235 char hint_num[128];
237 /* custom stylesheet */
238 int styled;
239 char *stylesheet;
241 /* search */
242 char *search_text;
243 int search_forward;
244 guint search_id;
246 /* settings */
247 WebKitWebSettings *settings;
248 gchar *user_agent;
250 /* marks */
251 double mark[XT_NOMARKS];
253 TAILQ_HEAD(tab_list, tab);
255 struct history {
256 RB_ENTRY(history) entry;
257 const gchar *uri;
258 const gchar *title;
260 RB_HEAD(history_list, history);
262 struct download {
263 RB_ENTRY(download) entry;
264 int id;
265 WebKitDownload *download;
266 struct tab *tab;
268 RB_HEAD(download_list, download);
270 struct domain {
271 RB_ENTRY(domain) entry;
272 gchar *d;
273 int handy; /* app use */
275 RB_HEAD(domain_list, domain);
277 struct undo {
278 TAILQ_ENTRY(undo) entry;
279 gchar *uri;
280 GList *history;
281 int back; /* Keeps track of how many back
282 * history items there are. */
284 TAILQ_HEAD(undo_tailq, undo);
286 struct sp {
287 char *line;
288 TAILQ_ENTRY(sp) entry;
290 TAILQ_HEAD(sp_list, sp);
292 struct command_entry {
293 char *line;
294 TAILQ_ENTRY(command_entry) entry;
296 TAILQ_HEAD(command_list, command_entry);
298 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
299 int next_download_id = 1;
301 struct karg {
302 int i;
303 char *s;
304 int precount;
307 /* defines */
308 #define XT_NAME ("XXXTerm")
309 #define XT_DIR (".xxxterm")
310 #define XT_CACHE_DIR ("cache")
311 #define XT_CERT_DIR ("certs/")
312 #define XT_SESSIONS_DIR ("sessions/")
313 #define XT_CONF_FILE ("xxxterm.conf")
314 #define XT_FAVS_FILE ("favorites")
315 #define XT_QMARKS_FILE ("quickmarks")
316 #define XT_SAVED_TABS_FILE ("main_session")
317 #define XT_RESTART_TABS_FILE ("restart_tabs")
318 #define XT_SOCKET_FILE ("socket")
319 #define XT_HISTORY_FILE ("history")
320 #define XT_REJECT_FILE ("rejected.txt")
321 #define XT_COOKIE_FILE ("cookies.txt")
322 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
323 #define XT_SEARCH_FILE ("search_history")
324 #define XT_COMMAND_FILE ("command_history")
325 #define XT_CB_HANDLED (TRUE)
326 #define XT_CB_PASSTHROUGH (FALSE)
327 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
328 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
329 #define XT_DLMAN_REFRESH "10"
330 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
331 "td{overflow: hidden;" \
332 " padding: 2px 2px 2px 2px;" \
333 " border: 1px solid black;" \
334 " vertical-align:top;" \
335 " word-wrap: break-word}\n" \
336 "tr:hover{background: #ffff99}\n" \
337 "th{background-color: #cccccc;" \
338 " border: 1px solid black}\n" \
339 "table{width: 100%%;" \
340 " border: 1px black solid;" \
341 " border-collapse:collapse}\n" \
342 ".progress-outer{" \
343 "border: 1px solid black;" \
344 " height: 8px;" \
345 " width: 90%%}\n" \
346 ".progress-inner{float: left;" \
347 " height: 8px;" \
348 " background: green}\n" \
349 ".dlstatus{font-size: small;" \
350 " text-align: center}\n" \
351 "</style>\n"
352 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
353 #define XT_MAX_UNDO_CLOSE_TAB (32)
354 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
355 #define XT_PRINT_EXTRA_MARGIN 10
356 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
357 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
359 /* colors */
360 #define XT_COLOR_RED "#cc0000"
361 #define XT_COLOR_YELLOW "#ffff66"
362 #define XT_COLOR_BLUE "lightblue"
363 #define XT_COLOR_GREEN "#99ff66"
364 #define XT_COLOR_WHITE "white"
365 #define XT_COLOR_BLACK "black"
367 #define XT_COLOR_CT_BACKGROUND "#000000"
368 #define XT_COLOR_CT_INACTIVE "#dddddd"
369 #define XT_COLOR_CT_ACTIVE "#bbbb00"
370 #define XT_COLOR_CT_SEPARATOR "#555555"
372 #define XT_COLOR_SB_SEPARATOR "#555555"
374 #define XT_PROTO_DELIM "://"
377 * xxxterm "protocol" (xtp)
378 * We use this for managing stuff like downloads and favorites. They
379 * make magical HTML pages in memory which have xxxt:// links in order
380 * to communicate with xxxterm's internals. These links take the format:
381 * xxxt://class/session_key/action/arg
383 * Don't begin xtp class/actions as 0. atoi returns that on error.
385 * Typically we have not put addition of items in this framework, as
386 * adding items is either done via an ex-command or via a keybinding instead.
389 #define XT_XTP_STR "xxxt://"
391 /* XTP classes (xxxt://<class>) */
392 #define XT_XTP_INVALID 0 /* invalid */
393 #define XT_XTP_DL 1 /* downloads */
394 #define XT_XTP_HL 2 /* history */
395 #define XT_XTP_CL 3 /* cookies */
396 #define XT_XTP_FL 4 /* favorites */
398 /* XTP download actions */
399 #define XT_XTP_DL_LIST 1
400 #define XT_XTP_DL_CANCEL 2
401 #define XT_XTP_DL_REMOVE 3
403 /* XTP history actions */
404 #define XT_XTP_HL_LIST 1
405 #define XT_XTP_HL_REMOVE 2
407 /* XTP cookie actions */
408 #define XT_XTP_CL_LIST 1
409 #define XT_XTP_CL_REMOVE 2
411 /* XTP cookie actions */
412 #define XT_XTP_FL_LIST 1
413 #define XT_XTP_FL_REMOVE 2
415 /* actions */
416 #define XT_MOVE_INVALID (0)
417 #define XT_MOVE_DOWN (1)
418 #define XT_MOVE_UP (2)
419 #define XT_MOVE_BOTTOM (3)
420 #define XT_MOVE_TOP (4)
421 #define XT_MOVE_PAGEDOWN (5)
422 #define XT_MOVE_PAGEUP (6)
423 #define XT_MOVE_HALFDOWN (7)
424 #define XT_MOVE_HALFUP (8)
425 #define XT_MOVE_LEFT (9)
426 #define XT_MOVE_FARLEFT (10)
427 #define XT_MOVE_RIGHT (11)
428 #define XT_MOVE_FARRIGHT (12)
429 #define XT_MOVE_PERCENT (13)
431 #define XT_QMARK_SET (0)
432 #define XT_QMARK_OPEN (1)
433 #define XT_QMARK_TAB (2)
435 #define XT_MARK_SET (0)
436 #define XT_MARK_GOTO (1)
438 #define XT_TAB_LAST (-4)
439 #define XT_TAB_FIRST (-3)
440 #define XT_TAB_PREV (-2)
441 #define XT_TAB_NEXT (-1)
442 #define XT_TAB_INVALID (0)
443 #define XT_TAB_NEW (1)
444 #define XT_TAB_DELETE (2)
445 #define XT_TAB_DELQUIT (3)
446 #define XT_TAB_OPEN (4)
447 #define XT_TAB_UNDO_CLOSE (5)
448 #define XT_TAB_SHOW (6)
449 #define XT_TAB_HIDE (7)
450 #define XT_TAB_NEXTSTYLE (8)
452 #define XT_NAV_INVALID (0)
453 #define XT_NAV_BACK (1)
454 #define XT_NAV_FORWARD (2)
455 #define XT_NAV_RELOAD (3)
457 #define XT_FOCUS_INVALID (0)
458 #define XT_FOCUS_URI (1)
459 #define XT_FOCUS_SEARCH (2)
461 #define XT_SEARCH_INVALID (0)
462 #define XT_SEARCH_NEXT (1)
463 #define XT_SEARCH_PREV (2)
465 #define XT_PASTE_CURRENT_TAB (0)
466 #define XT_PASTE_NEW_TAB (1)
468 #define XT_ZOOM_IN (-1)
469 #define XT_ZOOM_OUT (-2)
470 #define XT_ZOOM_NORMAL (100)
472 #define XT_URL_SHOW (1)
473 #define XT_URL_HIDE (2)
475 #define XT_WL_TOGGLE (1<<0)
476 #define XT_WL_ENABLE (1<<1)
477 #define XT_WL_DISABLE (1<<2)
478 #define XT_WL_FQDN (1<<3) /* default */
479 #define XT_WL_TOPLEVEL (1<<4)
480 #define XT_WL_PERSISTENT (1<<5)
481 #define XT_WL_SESSION (1<<6)
482 #define XT_WL_RELOAD (1<<7)
484 #define XT_SHOW (1<<7)
485 #define XT_DELETE (1<<8)
486 #define XT_SAVE (1<<9)
487 #define XT_OPEN (1<<10)
489 #define XT_CMD_OPEN (0)
490 #define XT_CMD_OPEN_CURRENT (1)
491 #define XT_CMD_TABNEW (2)
492 #define XT_CMD_TABNEW_CURRENT (3)
494 #define XT_STATUS_NOTHING (0)
495 #define XT_STATUS_LINK (1)
496 #define XT_STATUS_URI (2)
497 #define XT_STATUS_LOADING (3)
499 #define XT_SES_DONOTHING (0)
500 #define XT_SES_CLOSETABS (1)
502 #define XT_BM_NORMAL (0)
503 #define XT_BM_WHITELIST (1)
504 #define XT_BM_KIOSK (2)
506 #define XT_PREFIX (1<<0)
507 #define XT_USERARG (1<<1)
508 #define XT_URLARG (1<<2)
509 #define XT_INTARG (1<<3)
511 #define XT_TABS_NORMAL 0
512 #define XT_TABS_COMPACT 1
514 #define XT_BUFCMD_SZ (8)
516 /* mime types */
517 struct mime_type {
518 char *mt_type;
519 char *mt_action;
520 int mt_default;
521 int mt_download;
522 TAILQ_ENTRY(mime_type) entry;
524 TAILQ_HEAD(mime_type_list, mime_type);
526 /* uri aliases */
527 struct alias {
528 char *a_name;
529 char *a_uri;
530 TAILQ_ENTRY(alias) entry;
532 TAILQ_HEAD(alias_list, alias);
534 /* settings that require restart */
535 int tabless = 0; /* allow only 1 tab */
536 int enable_socket = 0;
537 int single_instance = 0; /* only allow one xxxterm to run */
538 int fancy_bar = 1; /* fancy toolbar */
539 int browser_mode = XT_BM_NORMAL;
540 int enable_localstorage = 0;
541 char *statusbar_elems = NULL;
543 /* runtime settings */
544 int show_tabs = 1; /* show tabs on notebook */
545 int tab_style = XT_TABS_NORMAL; /* tab bar style */
546 int show_url = 1; /* show url toolbar on notebook */
547 int show_statusbar = 0; /* vimperator style status bar */
548 int ctrl_click_focus = 0; /* ctrl click gets focus */
549 int cookies_enabled = 1; /* enable cookies */
550 int read_only_cookies = 0; /* enable to not write cookies */
551 int enable_scripts = 1;
552 int enable_plugins = 0;
553 gfloat default_zoom_level = 1.0;
554 char default_script[PATH_MAX];
555 int window_height = 768;
556 int window_width = 1024;
557 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
558 int refresh_interval = 10; /* download refresh interval */
559 int enable_cookie_whitelist = 0;
560 int enable_js_whitelist = 0;
561 int session_timeout = 3600; /* cookie session timeout */
562 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
563 char *ssl_ca_file = NULL;
564 char *resource_dir = NULL;
565 gboolean ssl_strict_certs = FALSE;
566 int append_next = 1; /* append tab after current tab */
567 char *home = NULL;
568 char *search_string = NULL;
569 char *http_proxy = NULL;
570 char download_dir[PATH_MAX];
571 char runtime_settings[PATH_MAX]; /* override of settings */
572 int allow_volatile_cookies = 0;
573 int save_global_history = 0; /* save global history to disk */
574 char *user_agent = NULL;
575 int save_rejected_cookies = 0;
576 int session_autosave = 0;
577 int guess_search = 0;
578 int dns_prefetch = FALSE;
579 gint max_connections = 25;
580 gint max_host_connections = 5;
581 gint enable_spell_checking = 0;
582 char *spell_check_languages = NULL;
583 int xterm_workaround = 0;
584 char *url_regex = NULL;
585 int history_autosave = 0;
586 char search_file[PATH_MAX];
587 char command_file[PATH_MAX];
589 char *cmd_font_name = NULL;
590 char *oops_font_name = NULL;
591 char *statusbar_font_name = NULL;
592 char *tabbar_font_name = NULL;
593 PangoFontDescription *cmd_font;
594 PangoFontDescription *oops_font;
595 PangoFontDescription *statusbar_font;
596 PangoFontDescription *tabbar_font;
597 char *qmarks[XT_NOMARKS];
599 int btn_down; /* M1 down in any wv */
600 regex_t url_re; /* guess_search regex */
602 struct settings;
603 struct key_binding;
604 int set_browser_mode(struct settings *, char *);
605 int set_cookie_policy(struct settings *, char *);
606 int set_download_dir(struct settings *, char *);
607 int set_default_script(struct settings *, char *);
608 int set_runtime_dir(struct settings *, char *);
609 int set_tab_style(struct settings *, char *);
610 int set_work_dir(struct settings *, char *);
611 int add_alias(struct settings *, char *);
612 int add_mime_type(struct settings *, char *);
613 int add_cookie_wl(struct settings *, char *);
614 int add_js_wl(struct settings *, char *);
615 int add_kb(struct settings *, char *);
616 void button_set_stockid(GtkWidget *, char *);
617 GtkWidget * create_button(char *, char *, int);
619 char *get_browser_mode(struct settings *);
620 char *get_cookie_policy(struct settings *);
621 char *get_download_dir(struct settings *);
622 char *get_default_script(struct settings *);
623 char *get_runtime_dir(struct settings *);
624 char *get_tab_style(struct settings *);
625 char *get_work_dir(struct settings *);
626 void startpage_add(const char *, ...);
628 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
629 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
630 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
631 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
632 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
634 void recalc_tabs(void);
635 void recolor_compact_tabs(void);
636 void set_current_tab(int page_num);
637 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
638 void marks_clear(struct tab *t);
640 int set_http_proxy(char *);
642 struct special {
643 int (*set)(struct settings *, char *);
644 char *(*get)(struct settings *);
645 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
648 struct special s_browser_mode = {
649 set_browser_mode,
650 get_browser_mode,
651 NULL
654 struct special s_cookie = {
655 set_cookie_policy,
656 get_cookie_policy,
657 NULL
660 struct special s_alias = {
661 add_alias,
662 NULL,
663 walk_alias
666 struct special s_mime = {
667 add_mime_type,
668 NULL,
669 walk_mime_type
672 struct special s_js = {
673 add_js_wl,
674 NULL,
675 walk_js_wl
678 struct special s_kb = {
679 add_kb,
680 NULL,
681 walk_kb
684 struct special s_cookie_wl = {
685 add_cookie_wl,
686 NULL,
687 walk_cookie_wl
690 struct special s_default_script = {
691 set_default_script,
692 get_default_script,
693 NULL
696 struct special s_download_dir = {
697 set_download_dir,
698 get_download_dir,
699 NULL
702 struct special s_work_dir = {
703 set_work_dir,
704 get_work_dir,
705 NULL
708 struct special s_tab_style = {
709 set_tab_style,
710 get_tab_style,
711 NULL
714 struct settings {
715 char *name;
716 int type;
717 #define XT_S_INVALID (0)
718 #define XT_S_INT (1)
719 #define XT_S_STR (2)
720 #define XT_S_FLOAT (3)
721 uint32_t flags;
722 #define XT_SF_RESTART (1<<0)
723 #define XT_SF_RUNTIME (1<<1)
724 int *ival;
725 char **sval;
726 struct special *s;
727 gfloat *fval;
728 int (*activate)(char *);
729 } rs[] = {
730 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
731 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
732 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
733 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
734 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
735 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
736 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
737 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
738 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
739 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
740 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
741 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
742 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
743 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
744 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
745 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
746 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
747 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
748 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
749 { "home", XT_S_STR, 0, NULL, &home, NULL },
750 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
751 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
752 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
753 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
754 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
755 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
756 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
757 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
758 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
759 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
760 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
761 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
762 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
763 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
764 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
765 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
766 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
767 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
768 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
769 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
770 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
771 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
772 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
773 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
774 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
775 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
776 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
778 /* font settings */
779 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
780 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
781 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
782 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
784 /* runtime settings */
785 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
786 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
787 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
788 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
789 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
792 int about(struct tab *, struct karg *);
793 int blank(struct tab *, struct karg *);
794 int ca_cmd(struct tab *, struct karg *);
795 int cookie_show_wl(struct tab *, struct karg *);
796 int js_show_wl(struct tab *, struct karg *);
797 int help(struct tab *, struct karg *);
798 int set(struct tab *, struct karg *);
799 int stats(struct tab *, struct karg *);
800 int marco(struct tab *, struct karg *);
801 int startpage(struct tab *, struct karg *);
802 const char * marco_message(int *);
803 int xtp_page_cl(struct tab *, struct karg *);
804 int xtp_page_dl(struct tab *, struct karg *);
805 int xtp_page_fl(struct tab *, struct karg *);
806 int xtp_page_hl(struct tab *, struct karg *);
807 void xt_icon_from_file(struct tab *, char *);
808 const gchar *get_uri(struct tab *);
809 const gchar *get_title(struct tab *, bool);
811 #define XT_URI_ABOUT ("about:")
812 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
813 #define XT_URI_ABOUT_ABOUT ("about")
814 #define XT_URI_ABOUT_BLANK ("blank")
815 #define XT_URI_ABOUT_CERTS ("certs")
816 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
817 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
818 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
819 #define XT_URI_ABOUT_FAVORITES ("favorites")
820 #define XT_URI_ABOUT_HELP ("help")
821 #define XT_URI_ABOUT_HISTORY ("history")
822 #define XT_URI_ABOUT_JSWL ("jswl")
823 #define XT_URI_ABOUT_SET ("set")
824 #define XT_URI_ABOUT_STATS ("stats")
825 #define XT_URI_ABOUT_MARCO ("marco")
826 #define XT_URI_ABOUT_STARTPAGE ("startpage")
828 struct about_type {
829 char *name;
830 int (*func)(struct tab *, struct karg *);
831 } about_list[] = {
832 { XT_URI_ABOUT_ABOUT, about },
833 { XT_URI_ABOUT_BLANK, blank },
834 { XT_URI_ABOUT_CERTS, ca_cmd },
835 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
836 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
837 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
838 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
839 { XT_URI_ABOUT_HELP, help },
840 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
841 { XT_URI_ABOUT_JSWL, js_show_wl },
842 { XT_URI_ABOUT_SET, set },
843 { XT_URI_ABOUT_STATS, stats },
844 { XT_URI_ABOUT_MARCO, marco },
845 { XT_URI_ABOUT_STARTPAGE, startpage },
848 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
849 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
850 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
851 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
852 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
853 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
854 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
856 /* globals */
857 extern char *__progname;
858 char **start_argv;
859 struct passwd *pwd;
860 GtkWidget *main_window;
861 GtkNotebook *notebook;
862 GtkWidget *tab_bar;
863 GtkWidget *arrow, *abtn;
864 struct tab_list tabs;
865 struct history_list hl;
866 struct download_list downloads;
867 struct domain_list c_wl;
868 struct domain_list js_wl;
869 struct undo_tailq undos;
870 struct keybinding_list kbl;
871 struct sp_list spl;
872 struct command_list chl;
873 struct command_list shl;
874 struct command_entry *history_at;
875 struct command_entry *search_at;
876 int undo_count;
877 int updating_dl_tabs = 0;
878 int updating_hl_tabs = 0;
879 int updating_cl_tabs = 0;
880 int updating_fl_tabs = 0;
881 int cmd_history_count = 0;
882 int search_history_count = 0;
883 char *global_search;
884 uint64_t blocked_cookies = 0;
885 char named_session[PATH_MAX];
886 GtkListStore *completion_model;
887 GtkListStore *buffers_store;
889 void xxx_dir(char *);
890 int icon_size_map(int);
891 void completion_add(struct tab *);
892 void completion_add_uri(const gchar *);
893 void show_oops(struct tab *, const char *, ...);
895 void
896 history_delete(struct command_list *l, int *counter)
898 struct command_entry *c;
900 if (l == NULL || counter == NULL)
901 return;
903 c = TAILQ_LAST(l, command_list);
904 if (c == NULL)
905 return;
907 TAILQ_REMOVE(l, c, entry);
908 *counter -= 1;
909 g_free(c->line);
910 g_free(c);
913 void
914 history_add(struct command_list *list, char *file, char *l, int *counter)
916 struct command_entry *c;
917 FILE *f;
919 if (list == NULL || l == NULL || counter == NULL)
920 return;
922 /* don't add the same line */
923 c = TAILQ_FIRST(list);
924 if (c)
925 if (!strcmp(c->line + 1 /* skip space */, l))
926 return;
928 c = g_malloc0(sizeof *c);
929 c->line = g_strdup_printf(" %s", l);
931 *counter += 1;
932 TAILQ_INSERT_HEAD(list, c, entry);
934 if (*counter > 1000)
935 history_delete(list, counter);
937 if (history_autosave && file) {
938 f = fopen(file, "w");
939 if (f == NULL) {
940 show_oops(NULL, "couldn't write history %s", file);
941 return;
944 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
945 c->line[0] = ' ';
946 fprintf(f, "%s\n", c->line);
949 fclose(f);
954 history_read(struct command_list *list, char *file, int *counter)
956 FILE *f;
957 char *s, line[65536];
959 if (list == NULL || file == NULL)
960 return (1);
962 f = fopen(file, "r");
963 if (f == NULL) {
964 startpage_add("couldn't open history file %s", file);
965 return (1);
968 for (;;) {
969 s = fgets(line, sizeof line, f);
970 if (s == NULL || feof(f) || ferror(f))
971 break;
972 if ((s = strchr(line, '\n')) == NULL) {
973 startpage_add("invalid history file %s", file);
974 fclose(f);
975 return (1);
977 *s = '\0';
979 history_add(list, NULL, line + 1, counter);
982 fclose(f);
984 return (0);
987 /* marks and quickmarks array storage.
988 * first a-z, then A-Z, then 0-9 */
989 char
990 indextomark(int i)
992 if (i < 0)
993 return 0;
995 if (i >= 0 && i <= 'z' - 'a')
996 return 'a' + i;
998 i -= 'z' - 'a' + 1;
999 if (i >= 0 && i <= 'Z' - 'A')
1000 return 'A' + i;
1002 i -= 'Z' - 'A' + 1;
1003 if (i >= 10)
1004 return 0;
1006 return i + '0';
1010 marktoindex(char m)
1012 int ret = 0;
1014 if (m >= 'a' && m <= 'z')
1015 return ret + m - 'a';
1017 ret += 'z' - 'a' + 1;
1018 if (m >= 'A' && m <= 'Z')
1019 return ret + m - 'A';
1021 ret += 'Z' - 'A' + 1;
1022 if (m >= '0' && m <= '9')
1023 return ret + m - '0';
1025 return -1;
1029 void
1030 sigchild(int sig)
1032 int saved_errno, status;
1033 pid_t pid;
1035 saved_errno = errno;
1037 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1038 if (pid == -1) {
1039 if (errno == EINTR)
1040 continue;
1041 if (errno != ECHILD) {
1043 clog_warn("sigchild: waitpid:");
1046 break;
1049 if (WIFEXITED(status)) {
1050 if (WEXITSTATUS(status) != 0) {
1052 clog_warnx("sigchild: child exit status: %d",
1053 WEXITSTATUS(status));
1056 } else {
1058 clog_warnx("sigchild: child is terminated abnormally");
1063 errno = saved_errno;
1067 is_g_object_setting(GObject *o, char *str)
1069 guint n_props = 0, i;
1070 GParamSpec **proplist;
1072 if (! G_IS_OBJECT(o))
1073 return (0);
1075 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1076 &n_props);
1078 for (i=0; i < n_props; i++) {
1079 if (! strcmp(proplist[i]->name, str))
1080 return (1);
1082 return (0);
1085 gchar *
1086 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1088 gchar *r;
1090 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1091 "<head>\n"
1092 "<title>%s</title>\n"
1093 "%s"
1094 "%s"
1095 "</head>\n"
1096 "<body>\n"
1097 "<h1>%s</h1>\n"
1098 "%s\n</body>\n"
1099 "</html>",
1100 title,
1101 addstyles ? XT_PAGE_STYLE : "",
1102 head,
1103 title,
1104 body);
1106 return r;
1110 * Display a web page from a HTML string in memory, rather than from a URL
1112 void
1113 load_webkit_string(struct tab *t, const char *str, gchar *title)
1115 char file[PATH_MAX];
1116 int i;
1118 /* we set this to indicate we want to manually do navaction */
1119 if (t->bfl)
1120 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1122 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1123 if (title) {
1124 /* set t->xtp_meaning */
1125 for (i = 0; i < LENGTH(about_list); i++)
1126 if (!strcmp(title, about_list[i].name)) {
1127 t->xtp_meaning = i;
1128 break;
1131 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1132 #if GTK_CHECK_VERSION(2, 20, 0)
1133 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1134 gtk_widget_hide(t->spinner);
1135 #endif
1136 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1137 xt_icon_from_file(t, file);
1141 struct tab *
1142 get_current_tab(void)
1144 struct tab *t;
1146 TAILQ_FOREACH(t, &tabs, entry) {
1147 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1148 return (t);
1151 warnx("%s: no current tab", __func__);
1153 return (NULL);
1156 void
1157 set_status(struct tab *t, gchar *s, int status)
1159 gchar *type = NULL;
1161 if (s == NULL)
1162 return;
1164 switch (status) {
1165 case XT_STATUS_LOADING:
1166 type = g_strdup_printf("Loading: %s", s);
1167 s = type;
1168 break;
1169 case XT_STATUS_LINK:
1170 type = g_strdup_printf("Link: %s", s);
1171 if (!t->status)
1172 t->status = g_strdup(gtk_entry_get_text(
1173 GTK_ENTRY(t->sbe.statusbar)));
1174 s = type;
1175 break;
1176 case XT_STATUS_URI:
1177 type = g_strdup_printf("%s", s);
1178 if (!t->status) {
1179 t->status = g_strdup(type);
1181 s = type;
1182 if (!t->status)
1183 t->status = g_strdup(s);
1184 break;
1185 case XT_STATUS_NOTHING:
1186 /* FALL THROUGH */
1187 default:
1188 break;
1190 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1191 if (type)
1192 g_free(type);
1195 void
1196 hide_cmd(struct tab *t)
1198 history_at = NULL; /* just in case */
1199 search_at = NULL; /* just in case */
1200 gtk_widget_hide(t->cmd);
1203 void
1204 show_cmd(struct tab *t)
1206 history_at = NULL;
1207 search_at = NULL;
1208 gtk_widget_hide(t->oops);
1209 gtk_widget_show(t->cmd);
1212 void
1213 hide_buffers(struct tab *t)
1215 gtk_widget_hide(t->buffers);
1216 gtk_list_store_clear(buffers_store);
1219 enum {
1220 COL_ID = 0,
1221 COL_TITLE,
1222 NUM_COLS
1226 sort_tabs_by_page_num(struct tab ***stabs)
1228 int num_tabs = 0;
1229 struct tab *t;
1231 num_tabs = gtk_notebook_get_n_pages(notebook);
1233 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1235 TAILQ_FOREACH(t, &tabs, entry)
1236 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1238 return (num_tabs);
1241 void
1242 buffers_make_list(void)
1244 int i, num_tabs;
1245 const gchar *title = NULL;
1246 GtkTreeIter iter;
1247 struct tab **stabs = NULL;
1249 num_tabs = sort_tabs_by_page_num(&stabs);
1251 for (i = 0; i < num_tabs; i++)
1252 if (stabs[i]) {
1253 gtk_list_store_append(buffers_store, &iter);
1254 title = get_title(stabs[i], FALSE);
1255 gtk_list_store_set(buffers_store, &iter,
1256 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1257 * rather than 0. */
1258 COL_TITLE, title,
1259 -1);
1262 g_free(stabs);
1265 void
1266 show_buffers(struct tab *t)
1268 buffers_make_list();
1269 gtk_widget_show(t->buffers);
1270 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1273 void
1274 toggle_buffers(struct tab *t)
1276 if (gtk_widget_get_visible(t->buffers))
1277 hide_buffers(t);
1278 else
1279 show_buffers(t);
1283 buffers(struct tab *t, struct karg *args)
1285 show_buffers(t);
1287 return (0);
1290 void
1291 hide_oops(struct tab *t)
1293 gtk_widget_hide(t->oops);
1296 void
1297 show_oops(struct tab *at, const char *fmt, ...)
1299 va_list ap;
1300 char *msg = NULL;
1301 struct tab *t = NULL;
1303 if (fmt == NULL)
1304 return;
1306 if (at == NULL) {
1307 if ((t = get_current_tab()) == NULL)
1308 return;
1309 } else
1310 t = at;
1312 va_start(ap, fmt);
1313 if (vasprintf(&msg, fmt, ap) == -1)
1314 errx(1, "show_oops failed");
1315 va_end(ap);
1317 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1318 gtk_widget_hide(t->cmd);
1319 gtk_widget_show(t->oops);
1321 if (msg)
1322 free(msg);
1325 char *
1326 get_as_string(struct settings *s)
1328 char *r = NULL;
1330 if (s == NULL)
1331 return (NULL);
1333 if (s->s) {
1334 if (s->s->get)
1335 r = s->s->get(s);
1336 else
1337 warnx("get_as_string skip %s\n", s->name);
1338 } else if (s->type == XT_S_INT)
1339 r = g_strdup_printf("%d", *s->ival);
1340 else if (s->type == XT_S_STR)
1341 r = g_strdup(*s->sval);
1342 else if (s->type == XT_S_FLOAT)
1343 r = g_strdup_printf("%f", *s->fval);
1344 else
1345 r = g_strdup_printf("INVALID TYPE");
1347 return (r);
1350 void
1351 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1353 int i;
1354 char *s;
1356 for (i = 0; i < LENGTH(rs); i++) {
1357 if (rs[i].s && rs[i].s->walk)
1358 rs[i].s->walk(&rs[i], cb, cb_args);
1359 else {
1360 s = get_as_string(&rs[i]);
1361 cb(&rs[i], s, cb_args);
1362 g_free(s);
1368 set_browser_mode(struct settings *s, char *val)
1370 if (!strcmp(val, "whitelist")) {
1371 browser_mode = XT_BM_WHITELIST;
1372 allow_volatile_cookies = 0;
1373 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1374 cookies_enabled = 1;
1375 enable_cookie_whitelist = 1;
1376 read_only_cookies = 0;
1377 save_rejected_cookies = 0;
1378 session_timeout = 3600;
1379 enable_scripts = 0;
1380 enable_js_whitelist = 1;
1381 enable_localstorage = 0;
1382 } else if (!strcmp(val, "normal")) {
1383 browser_mode = XT_BM_NORMAL;
1384 allow_volatile_cookies = 0;
1385 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1386 cookies_enabled = 1;
1387 enable_cookie_whitelist = 0;
1388 read_only_cookies = 0;
1389 save_rejected_cookies = 0;
1390 session_timeout = 3600;
1391 enable_scripts = 1;
1392 enable_js_whitelist = 0;
1393 enable_localstorage = 1;
1394 } else if (!strcmp(val, "kiosk")) {
1395 browser_mode = XT_BM_KIOSK;
1396 allow_volatile_cookies = 0;
1397 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1398 cookies_enabled = 1;
1399 enable_cookie_whitelist = 0;
1400 read_only_cookies = 0;
1401 save_rejected_cookies = 0;
1402 session_timeout = 3600;
1403 enable_scripts = 1;
1404 enable_js_whitelist = 0;
1405 enable_localstorage = 1;
1406 show_tabs = 0;
1407 tabless = 1;
1408 } else
1409 return (1);
1411 return (0);
1414 char *
1415 get_browser_mode(struct settings *s)
1417 char *r = NULL;
1419 if (browser_mode == XT_BM_WHITELIST)
1420 r = g_strdup("whitelist");
1421 else if (browser_mode == XT_BM_NORMAL)
1422 r = g_strdup("normal");
1423 else if (browser_mode == XT_BM_KIOSK)
1424 r = g_strdup("kiosk");
1425 else
1426 return (NULL);
1428 return (r);
1432 set_cookie_policy(struct settings *s, char *val)
1434 if (!strcmp(val, "no3rdparty"))
1435 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1436 else if (!strcmp(val, "accept"))
1437 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1438 else if (!strcmp(val, "reject"))
1439 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1440 else
1441 return (1);
1443 return (0);
1446 char *
1447 get_cookie_policy(struct settings *s)
1449 char *r = NULL;
1451 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1452 r = g_strdup("no3rdparty");
1453 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1454 r = g_strdup("accept");
1455 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1456 r = g_strdup("reject");
1457 else
1458 return (NULL);
1460 return (r);
1463 char *
1464 get_default_script(struct settings *s)
1466 if (default_script[0] == '\0')
1467 return (0);
1468 return (g_strdup(default_script));
1472 set_default_script(struct settings *s, char *val)
1474 if (val[0] == '~')
1475 snprintf(default_script, sizeof default_script, "%s/%s",
1476 pwd->pw_dir, &val[1]);
1477 else
1478 strlcpy(default_script, val, sizeof default_script);
1480 return (0);
1483 char *
1484 get_download_dir(struct settings *s)
1486 if (download_dir[0] == '\0')
1487 return (0);
1488 return (g_strdup(download_dir));
1492 set_download_dir(struct settings *s, char *val)
1494 if (val[0] == '~')
1495 snprintf(download_dir, sizeof download_dir, "%s/%s",
1496 pwd->pw_dir, &val[1]);
1497 else
1498 strlcpy(download_dir, val, sizeof download_dir);
1500 return (0);
1504 * Session IDs.
1505 * We use these to prevent people putting xxxt:// URLs on
1506 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1508 #define XT_XTP_SES_KEY_SZ 8
1509 #define XT_XTP_SES_KEY_HEX_FMT \
1510 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1511 char *dl_session_key; /* downloads */
1512 char *hl_session_key; /* history list */
1513 char *cl_session_key; /* cookie list */
1514 char *fl_session_key; /* favorites list */
1516 char work_dir[PATH_MAX];
1517 char certs_dir[PATH_MAX];
1518 char cache_dir[PATH_MAX];
1519 char sessions_dir[PATH_MAX];
1520 char cookie_file[PATH_MAX];
1521 SoupURI *proxy_uri = NULL;
1522 SoupSession *session;
1523 SoupCookieJar *s_cookiejar;
1524 SoupCookieJar *p_cookiejar;
1525 char rc_fname[PATH_MAX];
1527 struct mime_type_list mtl;
1528 struct alias_list aliases;
1530 /* protos */
1531 struct tab *create_new_tab(char *, struct undo *, int, int);
1532 void delete_tab(struct tab *);
1533 void setzoom_webkit(struct tab *, int);
1534 int run_script(struct tab *, char *);
1535 int download_rb_cmp(struct download *, struct download *);
1536 gboolean cmd_execute(struct tab *t, char *str);
1539 history_rb_cmp(struct history *h1, struct history *h2)
1541 return (strcmp(h1->uri, h2->uri));
1543 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1546 domain_rb_cmp(struct domain *d1, struct domain *d2)
1548 return (strcmp(d1->d, d2->d));
1550 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1552 char *
1553 get_work_dir(struct settings *s)
1555 if (work_dir[0] == '\0')
1556 return (0);
1557 return (g_strdup(work_dir));
1561 set_work_dir(struct settings *s, char *val)
1563 if (val[0] == '~')
1564 snprintf(work_dir, sizeof work_dir, "%s/%s",
1565 pwd->pw_dir, &val[1]);
1566 else
1567 strlcpy(work_dir, val, sizeof work_dir);
1569 return (0);
1572 char *
1573 get_tab_style(struct settings *s)
1575 if (tab_style == XT_TABS_NORMAL)
1576 return (g_strdup("normal"));
1577 else
1578 return (g_strdup("compact"));
1582 set_tab_style(struct settings *s, char *val)
1584 if (!strcmp(val, "normal"))
1585 tab_style = XT_TABS_NORMAL;
1586 else if (!strcmp(val, "compact"))
1587 tab_style = XT_TABS_COMPACT;
1588 else
1589 return (1);
1591 return (0);
1595 * generate a session key to secure xtp commands.
1596 * pass in a ptr to the key in question and it will
1597 * be modified in place.
1599 void
1600 generate_xtp_session_key(char **key)
1602 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1604 /* free old key */
1605 if (*key)
1606 g_free(*key);
1608 /* make a new one */
1609 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1610 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1611 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1612 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1614 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1618 * validate a xtp session key.
1619 * return 1 if OK
1622 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1624 if (strcmp(trusted, untrusted) != 0) {
1625 show_oops(t, "%s: xtp session key mismatch possible spoof",
1626 __func__);
1627 return (0);
1630 return (1);
1634 download_rb_cmp(struct download *e1, struct download *e2)
1636 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1638 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1640 struct valid_url_types {
1641 char *type;
1642 } vut[] = {
1643 { "http://" },
1644 { "https://" },
1645 { "ftp://" },
1646 { "file://" },
1647 { XT_XTP_STR },
1651 valid_url_type(char *url)
1653 int i;
1655 for (i = 0; i < LENGTH(vut); i++)
1656 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1657 return (0);
1659 return (1);
1662 void
1663 print_cookie(char *msg, SoupCookie *c)
1665 if (c == NULL)
1666 return;
1668 if (msg)
1669 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1670 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1671 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1672 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1673 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1674 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1675 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1676 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1677 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1678 DNPRINTF(XT_D_COOKIE, "====================================\n");
1681 void
1682 walk_alias(struct settings *s,
1683 void (*cb)(struct settings *, char *, void *), void *cb_args)
1685 struct alias *a;
1686 char *str;
1688 if (s == NULL || cb == NULL) {
1689 show_oops(NULL, "walk_alias invalid parameters");
1690 return;
1693 TAILQ_FOREACH(a, &aliases, entry) {
1694 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1695 cb(s, str, cb_args);
1696 g_free(str);
1700 char *
1701 match_alias(char *url_in)
1703 struct alias *a;
1704 char *arg;
1705 char *url_out = NULL, *search, *enc_arg;
1707 search = g_strdup(url_in);
1708 arg = search;
1709 if (strsep(&arg, " \t") == NULL) {
1710 show_oops(NULL, "match_alias: NULL URL");
1711 goto done;
1714 TAILQ_FOREACH(a, &aliases, entry) {
1715 if (!strcmp(search, a->a_name))
1716 break;
1719 if (a != NULL) {
1720 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1721 a->a_name);
1722 if (arg != NULL) {
1723 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1724 url_out = g_strdup_printf(a->a_uri, enc_arg);
1725 g_free(enc_arg);
1726 } else
1727 url_out = g_strdup_printf(a->a_uri, "");
1729 done:
1730 g_free(search);
1731 return (url_out);
1734 char *
1735 guess_url_type(char *url_in)
1737 struct stat sb;
1738 char *url_out = NULL, *enc_search = NULL;
1739 int i;
1741 /* substitute aliases */
1742 url_out = match_alias(url_in);
1743 if (url_out != NULL)
1744 return (url_out);
1746 /* see if we are an about page */
1747 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1748 for (i = 0; i < LENGTH(about_list); i++)
1749 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1750 about_list[i].name)) {
1751 url_out = g_strdup(url_in);
1752 goto done;
1755 if (guess_search && url_regex &&
1756 !(g_str_has_prefix(url_in, "http://") ||
1757 g_str_has_prefix(url_in, "https://"))) {
1758 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1759 /* invalid URI so search instead */
1760 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1761 url_out = g_strdup_printf(search_string, enc_search);
1762 g_free(enc_search);
1763 goto done;
1767 /* XXX not sure about this heuristic */
1768 if (stat(url_in, &sb) == 0)
1769 url_out = g_strdup_printf("file://%s", url_in);
1770 else
1771 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1772 done:
1773 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1775 return (url_out);
1778 void
1779 load_uri(struct tab *t, gchar *uri)
1781 struct karg args;
1782 gchar *newuri = NULL;
1783 int i;
1785 if (uri == NULL)
1786 return;
1788 /* Strip leading spaces. */
1789 while (*uri && isspace(*uri))
1790 uri++;
1792 if (strlen(uri) == 0) {
1793 blank(t, NULL);
1794 return;
1797 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1799 if (valid_url_type(uri)) {
1800 newuri = guess_url_type(uri);
1801 uri = newuri;
1804 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1805 for (i = 0; i < LENGTH(about_list); i++)
1806 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1807 bzero(&args, sizeof args);
1808 about_list[i].func(t, &args);
1809 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1810 FALSE);
1811 goto done;
1813 show_oops(t, "invalid about page");
1814 goto done;
1817 set_status(t, (char *)uri, XT_STATUS_LOADING);
1818 marks_clear(t);
1819 webkit_web_view_load_uri(t->wv, uri);
1820 done:
1821 if (newuri)
1822 g_free(newuri);
1825 const gchar *
1826 get_uri(struct tab *t)
1828 const gchar *uri = NULL;
1830 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1831 return t->tmp_uri;
1832 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1833 uri = webkit_web_view_get_uri(t->wv);
1834 } else {
1835 /* use tmp_uri to make sure it is g_freed */
1836 if (t->tmp_uri)
1837 g_free(t->tmp_uri);
1838 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1839 about_list[t->xtp_meaning].name);
1840 uri = t->tmp_uri;
1842 return uri;
1845 const gchar *
1846 get_title(struct tab *t, bool window)
1848 const gchar *set = NULL, *title = NULL;
1849 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1851 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1852 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1853 goto notitle;
1855 title = webkit_web_view_get_title(t->wv);
1856 if ((set = title ? title : get_uri(t)))
1857 return set;
1859 notitle:
1860 set = window ? XT_NAME : "(untitled)";
1862 return set;
1866 add_alias(struct settings *s, char *line)
1868 char *l, *alias;
1869 struct alias *a = NULL;
1871 if (s == NULL || line == NULL) {
1872 show_oops(NULL, "add_alias invalid parameters");
1873 return (1);
1876 l = line;
1877 a = g_malloc(sizeof(*a));
1879 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1880 show_oops(NULL, "add_alias: incomplete alias definition");
1881 goto bad;
1883 if (strlen(alias) == 0 || strlen(l) == 0) {
1884 show_oops(NULL, "add_alias: invalid alias definition");
1885 goto bad;
1888 a->a_name = g_strdup(alias);
1889 a->a_uri = g_strdup(l);
1891 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1893 TAILQ_INSERT_TAIL(&aliases, a, entry);
1895 return (0);
1896 bad:
1897 if (a)
1898 g_free(a);
1899 return (1);
1903 add_mime_type(struct settings *s, char *line)
1905 char *mime_type;
1906 char *l;
1907 struct mime_type *m = NULL;
1908 int downloadfirst = 0;
1910 /* XXX this could be smarter */
1912 if (line == NULL || strlen(line) == 0) {
1913 show_oops(NULL, "add_mime_type invalid parameters");
1914 return (1);
1917 l = line;
1918 if (*l == '@') {
1919 downloadfirst = 1;
1920 l++;
1922 m = g_malloc(sizeof(*m));
1924 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1925 show_oops(NULL, "add_mime_type: invalid mime_type");
1926 goto bad;
1928 if (mime_type[strlen(mime_type) - 1] == '*') {
1929 mime_type[strlen(mime_type) - 1] = '\0';
1930 m->mt_default = 1;
1931 } else
1932 m->mt_default = 0;
1934 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1935 show_oops(NULL, "add_mime_type: invalid mime_type");
1936 goto bad;
1939 m->mt_type = g_strdup(mime_type);
1940 m->mt_action = g_strdup(l);
1941 m->mt_download = downloadfirst;
1943 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1944 m->mt_type, m->mt_action, m->mt_default);
1946 TAILQ_INSERT_TAIL(&mtl, m, entry);
1948 return (0);
1949 bad:
1950 if (m)
1951 g_free(m);
1952 return (1);
1955 struct mime_type *
1956 find_mime_type(char *mime_type)
1958 struct mime_type *m, *def = NULL, *rv = NULL;
1960 TAILQ_FOREACH(m, &mtl, entry) {
1961 if (m->mt_default &&
1962 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1963 def = m;
1965 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1966 rv = m;
1967 break;
1971 if (rv == NULL)
1972 rv = def;
1974 return (rv);
1977 void
1978 walk_mime_type(struct settings *s,
1979 void (*cb)(struct settings *, char *, void *), void *cb_args)
1981 struct mime_type *m;
1982 char *str;
1984 if (s == NULL || cb == NULL) {
1985 show_oops(NULL, "walk_mime_type invalid parameters");
1986 return;
1989 TAILQ_FOREACH(m, &mtl, entry) {
1990 str = g_strdup_printf("%s%s --> %s",
1991 m->mt_type,
1992 m->mt_default ? "*" : "",
1993 m->mt_action);
1994 cb(s, str, cb_args);
1995 g_free(str);
1999 void
2000 wl_add(char *str, struct domain_list *wl, int handy)
2002 struct domain *d;
2003 int add_dot = 0;
2004 char *p;
2006 if (str == NULL || wl == NULL || strlen(str) < 2)
2007 return;
2009 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2011 /* treat *.moo.com the same as .moo.com */
2012 if (str[0] == '*' && str[1] == '.')
2013 str = &str[1];
2014 else if (str[0] == '.')
2015 str = &str[0];
2016 else
2017 add_dot = 1;
2019 /* slice off port number */
2020 p = g_strrstr(str, ":");
2021 if (p)
2022 *p = '\0';
2024 d = g_malloc(sizeof *d);
2025 if (add_dot)
2026 d->d = g_strdup_printf(".%s", str);
2027 else
2028 d->d = g_strdup(str);
2029 d->handy = handy;
2031 if (RB_INSERT(domain_list, wl, d))
2032 goto unwind;
2034 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2035 return;
2036 unwind:
2037 if (d) {
2038 if (d->d)
2039 g_free(d->d);
2040 g_free(d);
2045 add_cookie_wl(struct settings *s, char *entry)
2047 wl_add(entry, &c_wl, 1);
2048 return (0);
2051 void
2052 walk_cookie_wl(struct settings *s,
2053 void (*cb)(struct settings *, char *, void *), void *cb_args)
2055 struct domain *d;
2057 if (s == NULL || cb == NULL) {
2058 show_oops(NULL, "walk_cookie_wl invalid parameters");
2059 return;
2062 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2063 cb(s, d->d, cb_args);
2066 void
2067 walk_js_wl(struct settings *s,
2068 void (*cb)(struct settings *, char *, void *), void *cb_args)
2070 struct domain *d;
2072 if (s == NULL || cb == NULL) {
2073 show_oops(NULL, "walk_js_wl invalid parameters");
2074 return;
2077 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2078 cb(s, d->d, cb_args);
2082 add_js_wl(struct settings *s, char *entry)
2084 wl_add(entry, &js_wl, 1 /* persistent */);
2085 return (0);
2088 struct domain *
2089 wl_find(const gchar *search, struct domain_list *wl)
2091 int i;
2092 struct domain *d = NULL, dfind;
2093 gchar *s = NULL;
2095 if (search == NULL || wl == NULL)
2096 return (NULL);
2097 if (strlen(search) < 2)
2098 return (NULL);
2100 if (search[0] != '.')
2101 s = g_strdup_printf(".%s", search);
2102 else
2103 s = g_strdup(search);
2105 for (i = strlen(s) - 1; i >= 0; i--) {
2106 if (s[i] == '.') {
2107 dfind.d = &s[i];
2108 d = RB_FIND(domain_list, wl, &dfind);
2109 if (d)
2110 goto done;
2114 done:
2115 if (s)
2116 g_free(s);
2118 return (d);
2121 struct domain *
2122 wl_find_uri(const gchar *s, struct domain_list *wl)
2124 int i;
2125 char *ss;
2126 struct domain *r;
2128 if (s == NULL || wl == NULL)
2129 return (NULL);
2131 if (!strncmp(s, "http://", strlen("http://")))
2132 s = &s[strlen("http://")];
2133 else if (!strncmp(s, "https://", strlen("https://")))
2134 s = &s[strlen("https://")];
2136 if (strlen(s) < 2)
2137 return (NULL);
2139 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2140 /* chop string at first slash */
2141 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2142 ss = g_strdup(s);
2143 ss[i] = '\0';
2144 r = wl_find(ss, wl);
2145 g_free(ss);
2146 return (r);
2149 return (NULL);
2153 settings_add(char *var, char *val)
2155 int i, rv, *p;
2156 gfloat *f;
2157 char **s;
2159 /* get settings */
2160 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2161 if (strcmp(var, rs[i].name))
2162 continue;
2164 if (rs[i].s) {
2165 if (rs[i].s->set(&rs[i], val))
2166 errx(1, "invalid value for %s: %s", var, val);
2167 rv = 1;
2168 break;
2169 } else
2170 switch (rs[i].type) {
2171 case XT_S_INT:
2172 p = rs[i].ival;
2173 *p = atoi(val);
2174 rv = 1;
2175 break;
2176 case XT_S_STR:
2177 s = rs[i].sval;
2178 if (s == NULL)
2179 errx(1, "invalid sval for %s",
2180 rs[i].name);
2181 if (*s)
2182 g_free(*s);
2183 *s = g_strdup(val);
2184 rv = 1;
2185 break;
2186 case XT_S_FLOAT:
2187 f = rs[i].fval;
2188 *f = atof(val);
2189 rv = 1;
2190 break;
2191 case XT_S_INVALID:
2192 default:
2193 errx(1, "invalid type for %s", var);
2195 break;
2197 return (rv);
2200 #define WS "\n= \t"
2201 void
2202 config_parse(char *filename, int runtime)
2204 FILE *config, *f;
2205 char *line, *cp, *var, *val;
2206 size_t len, lineno = 0;
2207 int handled;
2208 char file[PATH_MAX];
2209 struct stat sb;
2211 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2213 if (filename == NULL)
2214 return;
2216 if (runtime && runtime_settings[0] != '\0') {
2217 snprintf(file, sizeof file, "%s/%s",
2218 work_dir, runtime_settings);
2219 if (stat(file, &sb)) {
2220 warnx("runtime file doesn't exist, creating it");
2221 if ((f = fopen(file, "w")) == NULL)
2222 err(1, "runtime");
2223 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2224 fclose(f);
2226 } else
2227 strlcpy(file, filename, sizeof file);
2229 if ((config = fopen(file, "r")) == NULL) {
2230 warn("config_parse: cannot open %s", filename);
2231 return;
2234 for (;;) {
2235 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2236 if (feof(config) || ferror(config))
2237 break;
2239 cp = line;
2240 cp += (long)strspn(cp, WS);
2241 if (cp[0] == '\0') {
2242 /* empty line */
2243 free(line);
2244 continue;
2247 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2248 startpage_add("invalid configuration file entry: %s",
2249 line);
2251 cp += (long)strspn(cp, WS);
2253 if ((val = strsep(&cp, "\0")) == NULL)
2254 break;
2256 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2257 handled = settings_add(var, val);
2258 if (handled == 0)
2259 startpage_add("invalid configuration file entry: %s=%s",
2260 var, val);
2262 free(line);
2265 fclose(config);
2268 char *
2269 js_ref_to_string(JSContextRef context, JSValueRef ref)
2271 char *s = NULL;
2272 size_t l;
2273 JSStringRef jsref;
2275 jsref = JSValueToStringCopy(context, ref, NULL);
2276 if (jsref == NULL)
2277 return (NULL);
2279 l = JSStringGetMaximumUTF8CStringSize(jsref);
2280 s = g_malloc(l);
2281 if (s)
2282 JSStringGetUTF8CString(jsref, s, l);
2283 JSStringRelease(jsref);
2285 return (s);
2288 void
2289 disable_hints(struct tab *t)
2291 bzero(t->hint_buf, sizeof t->hint_buf);
2292 bzero(t->hint_num, sizeof t->hint_num);
2293 run_script(t, "vimprobable_clear()");
2294 t->hints_on = 0;
2295 t->hint_mode = XT_HINT_NONE;
2298 void
2299 enable_hints(struct tab *t)
2301 bzero(t->hint_buf, sizeof t->hint_buf);
2302 run_script(t, "vimprobable_show_hints()");
2303 t->hints_on = 1;
2304 t->hint_mode = XT_HINT_NONE;
2307 #define XT_JS_OPEN ("open;")
2308 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2309 #define XT_JS_FIRE ("fire;")
2310 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2311 #define XT_JS_FOUND ("found;")
2312 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2315 run_script(struct tab *t, char *s)
2317 JSGlobalContextRef ctx;
2318 WebKitWebFrame *frame;
2319 JSStringRef str;
2320 JSValueRef val, exception;
2321 char *es, buf[128];
2323 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2324 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2326 frame = webkit_web_view_get_main_frame(t->wv);
2327 ctx = webkit_web_frame_get_global_context(frame);
2329 str = JSStringCreateWithUTF8CString(s);
2330 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2331 NULL, 0, &exception);
2332 JSStringRelease(str);
2334 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2335 if (val == NULL) {
2336 es = js_ref_to_string(ctx, exception);
2337 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2338 g_free(es);
2339 return (1);
2340 } else {
2341 es = js_ref_to_string(ctx, val);
2342 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2344 /* handle return value right here */
2345 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2346 disable_hints(t);
2347 marks_clear(t);
2348 load_uri(t, &es[XT_JS_OPEN_LEN]);
2351 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2352 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2353 &es[XT_JS_FIRE_LEN]);
2354 run_script(t, buf);
2355 disable_hints(t);
2358 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2359 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2360 disable_hints(t);
2363 g_free(es);
2366 return (0);
2370 hint(struct tab *t, struct karg *args)
2373 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2375 if (t->hints_on == 0)
2376 enable_hints(t);
2377 else
2378 disable_hints(t);
2380 return (0);
2383 void
2384 apply_style(struct tab *t)
2386 g_object_set(G_OBJECT(t->settings),
2387 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2391 userstyle(struct tab *t, struct karg *args)
2393 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2395 if (t->styled) {
2396 t->styled = 0;
2397 g_object_set(G_OBJECT(t->settings),
2398 "user-stylesheet-uri", NULL, (char *)NULL);
2399 } else {
2400 t->styled = 1;
2401 apply_style(t);
2403 return (0);
2407 * Doesn't work fully, due to the following bug:
2408 * https://bugs.webkit.org/show_bug.cgi?id=51747
2411 restore_global_history(void)
2413 char file[PATH_MAX];
2414 FILE *f;
2415 struct history *h;
2416 gchar *uri;
2417 gchar *title;
2418 const char delim[3] = {'\\', '\\', '\0'};
2420 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2422 if ((f = fopen(file, "r")) == NULL) {
2423 warnx("%s: fopen", __func__);
2424 return (1);
2427 for (;;) {
2428 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2429 if (feof(f) || ferror(f))
2430 break;
2432 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2433 if (feof(f) || ferror(f)) {
2434 free(uri);
2435 warnx("%s: broken history file\n", __func__);
2436 return (1);
2439 if (uri && strlen(uri) && title && strlen(title)) {
2440 webkit_web_history_item_new_with_data(uri, title);
2441 h = g_malloc(sizeof(struct history));
2442 h->uri = g_strdup(uri);
2443 h->title = g_strdup(title);
2444 RB_INSERT(history_list, &hl, h);
2445 completion_add_uri(h->uri);
2446 } else {
2447 warnx("%s: failed to restore history\n", __func__);
2448 free(uri);
2449 free(title);
2450 return (1);
2453 free(uri);
2454 free(title);
2455 uri = NULL;
2456 title = NULL;
2459 return (0);
2463 save_global_history_to_disk(struct tab *t)
2465 char file[PATH_MAX];
2466 FILE *f;
2467 struct history *h;
2469 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2471 if ((f = fopen(file, "w")) == NULL) {
2472 show_oops(t, "%s: global history file: %s",
2473 __func__, strerror(errno));
2474 return (1);
2477 RB_FOREACH_REVERSE(h, history_list, &hl) {
2478 if (h->uri && h->title)
2479 fprintf(f, "%s\n%s\n", h->uri, h->title);
2482 fclose(f);
2484 return (0);
2488 quit(struct tab *t, struct karg *args)
2490 if (save_global_history)
2491 save_global_history_to_disk(t);
2493 gtk_main_quit();
2495 return (1);
2499 open_tabs(struct tab *t, struct karg *a)
2501 char file[PATH_MAX];
2502 FILE *f = NULL;
2503 char *uri = NULL;
2504 int rv = 1;
2505 struct tab *ti, *tt;
2507 if (a == NULL)
2508 goto done;
2510 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2511 if ((f = fopen(file, "r")) == NULL)
2512 goto done;
2514 ti = TAILQ_LAST(&tabs, tab_list);
2516 for (;;) {
2517 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2518 if (feof(f) || ferror(f))
2519 break;
2521 /* retrieve session name */
2522 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2523 strlcpy(named_session,
2524 &uri[strlen(XT_SAVE_SESSION_ID)],
2525 sizeof named_session);
2526 continue;
2529 if (uri && strlen(uri))
2530 create_new_tab(uri, NULL, 1, -1);
2532 free(uri);
2533 uri = NULL;
2536 /* close open tabs */
2537 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2538 for (;;) {
2539 tt = TAILQ_FIRST(&tabs);
2540 if (tt != ti) {
2541 delete_tab(tt);
2542 continue;
2544 delete_tab(tt);
2545 break;
2547 recalc_tabs();
2550 rv = 0;
2551 done:
2552 if (f)
2553 fclose(f);
2555 return (rv);
2559 restore_saved_tabs(void)
2561 char file[PATH_MAX];
2562 int unlink_file = 0;
2563 struct stat sb;
2564 struct karg a;
2565 int rv = 0;
2567 snprintf(file, sizeof file, "%s/%s",
2568 sessions_dir, XT_RESTART_TABS_FILE);
2569 if (stat(file, &sb) == -1)
2570 a.s = XT_SAVED_TABS_FILE;
2571 else {
2572 unlink_file = 1;
2573 a.s = XT_RESTART_TABS_FILE;
2576 a.i = XT_SES_DONOTHING;
2577 rv = open_tabs(NULL, &a);
2579 if (unlink_file)
2580 unlink(file);
2582 return (rv);
2586 save_tabs(struct tab *t, struct karg *a)
2588 char file[PATH_MAX];
2589 FILE *f;
2590 int num_tabs = 0, i;
2591 struct tab **stabs = NULL;
2593 if (a == NULL)
2594 return (1);
2595 if (a->s == NULL)
2596 snprintf(file, sizeof file, "%s/%s",
2597 sessions_dir, named_session);
2598 else
2599 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2601 if ((f = fopen(file, "w")) == NULL) {
2602 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2603 return (1);
2606 /* save session name */
2607 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2609 /* Save tabs, in the order they are arranged in the notebook. */
2610 num_tabs = sort_tabs_by_page_num(&stabs);
2612 for (i = 0; i < num_tabs; i++)
2613 if (stabs[i]) {
2614 if (get_uri(stabs[i]) != NULL)
2615 fprintf(f, "%s\n", get_uri(stabs[i]));
2616 else if (gtk_entry_get_text(GTK_ENTRY(
2617 stabs[i]->uri_entry)))
2618 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2619 stabs[i]->uri_entry)));
2622 g_free(stabs);
2624 /* try and make sure this gets to disk NOW. XXX Backup first? */
2625 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2626 show_oops(t, "May not have managed to save session: %s",
2627 strerror(errno));
2630 fclose(f);
2632 return (0);
2636 save_tabs_and_quit(struct tab *t, struct karg *args)
2638 struct karg a;
2640 a.s = NULL;
2641 save_tabs(t, &a);
2642 quit(t, NULL);
2644 return (1);
2648 run_page_script(struct tab *t, struct karg *args)
2650 const gchar *uri;
2651 char *tmp, script[PATH_MAX];
2653 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2654 if (tmp[0] == '\0') {
2655 show_oops(t, "no script specified");
2656 return (1);
2659 if ((uri = get_uri(t)) == NULL) {
2660 show_oops(t, "tab is empty, not running script");
2661 return (1);
2664 if (tmp[0] == '~')
2665 snprintf(script, sizeof script, "%s/%s",
2666 pwd->pw_dir, &tmp[1]);
2667 else
2668 strlcpy(script, tmp, sizeof script);
2670 switch (fork()) {
2671 case -1:
2672 show_oops(t, "can't fork to run script");
2673 return (1);
2674 /* NOTREACHED */
2675 case 0:
2676 break;
2677 default:
2678 return (0);
2681 /* child */
2682 execlp(script, script, uri, (void *)NULL);
2684 _exit(0);
2686 /* NOTREACHED */
2688 return (0);
2692 yank_uri(struct tab *t, struct karg *args)
2694 const gchar *uri;
2695 GtkClipboard *clipboard;
2697 if ((uri = get_uri(t)) == NULL)
2698 return (1);
2700 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2701 gtk_clipboard_set_text(clipboard, uri, -1);
2703 return (0);
2707 paste_uri(struct tab *t, struct karg *args)
2709 GtkClipboard *clipboard;
2710 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2711 gint len;
2712 gchar *p = NULL, *uri;
2714 /* try primary clipboard first */
2715 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2716 p = gtk_clipboard_wait_for_text(clipboard);
2718 /* if it failed get whatever text is in cut_buffer0 */
2719 if (p == NULL && xterm_workaround)
2720 if (gdk_property_get(gdk_get_default_root_window(),
2721 atom,
2722 gdk_atom_intern("STRING", FALSE),
2724 1024 * 1024 /* picked out of my butt */,
2725 FALSE,
2726 NULL,
2727 NULL,
2728 &len,
2729 (guchar **)&p)) {
2730 /* yes sir, we need to NUL the string */
2731 p[len] = '\0';
2734 if (p) {
2735 uri = p;
2736 while (*uri && isspace(*uri))
2737 uri++;
2738 if (strlen(uri) == 0) {
2739 show_oops(t, "empty paste buffer");
2740 goto done;
2742 if (guess_search == 0 && valid_url_type(uri)) {
2743 /* we can be clever and paste this in search box */
2744 show_oops(t, "not a valid URL");
2745 goto done;
2748 if (args->i == XT_PASTE_CURRENT_TAB)
2749 load_uri(t, uri);
2750 else if (args->i == XT_PASTE_NEW_TAB)
2751 create_new_tab(uri, NULL, 1, -1);
2754 done:
2755 if (p)
2756 g_free(p);
2758 return (0);
2761 gchar *
2762 find_domain(const gchar *s, int toplevel)
2764 SoupURI *uri;
2765 gchar *ret, *p;
2767 if (s == NULL)
2768 return (NULL);
2770 uri = soup_uri_new(s);
2772 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2773 return (NULL);
2776 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2777 if ((p = strrchr(uri->host, '.')) != NULL) {
2778 while(--p >= uri->host && *p != '.');
2779 p++;
2780 } else
2781 p = uri->host;
2782 } else
2783 p = uri->host;
2785 ret = g_strdup_printf(".%s", p);
2787 soup_uri_free(uri);
2789 return ret;
2793 toggle_cwl(struct tab *t, struct karg *args)
2795 struct domain *d;
2796 const gchar *uri;
2797 char *dom = NULL;
2798 int es;
2800 if (args == NULL)
2801 return (1);
2803 uri = get_uri(t);
2804 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2806 if (uri == NULL || dom == NULL ||
2807 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2808 show_oops(t, "Can't toggle domain in cookie white list");
2809 goto done;
2811 d = wl_find(dom, &c_wl);
2813 if (d == NULL)
2814 es = 0;
2815 else
2816 es = 1;
2818 if (args->i & XT_WL_TOGGLE)
2819 es = !es;
2820 else if ((args->i & XT_WL_ENABLE) && es != 1)
2821 es = 1;
2822 else if ((args->i & XT_WL_DISABLE) && es != 0)
2823 es = 0;
2825 if (es)
2826 /* enable cookies for domain */
2827 wl_add(dom, &c_wl, 0);
2828 else
2829 /* disable cookies for domain */
2830 RB_REMOVE(domain_list, &c_wl, d);
2832 if (args->i & XT_WL_RELOAD)
2833 webkit_web_view_reload(t->wv);
2835 done:
2836 g_free(dom);
2837 return (0);
2841 toggle_js(struct tab *t, struct karg *args)
2843 int es;
2844 const gchar *uri;
2845 struct domain *d;
2846 char *dom = NULL;
2848 if (args == NULL)
2849 return (1);
2851 g_object_get(G_OBJECT(t->settings),
2852 "enable-scripts", &es, (char *)NULL);
2853 if (args->i & XT_WL_TOGGLE)
2854 es = !es;
2855 else if ((args->i & XT_WL_ENABLE) && es != 1)
2856 es = 1;
2857 else if ((args->i & XT_WL_DISABLE) && es != 0)
2858 es = 0;
2859 else
2860 return (1);
2862 uri = get_uri(t);
2863 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2865 if (uri == NULL || dom == NULL ||
2866 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2867 show_oops(t, "Can't toggle domain in JavaScript white list");
2868 goto done;
2871 if (es) {
2872 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2873 wl_add(dom, &js_wl, 0 /* session */);
2874 } else {
2875 d = wl_find(dom, &js_wl);
2876 if (d)
2877 RB_REMOVE(domain_list, &js_wl, d);
2878 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2880 g_object_set(G_OBJECT(t->settings),
2881 "enable-scripts", es, (char *)NULL);
2882 g_object_set(G_OBJECT(t->settings),
2883 "javascript-can-open-windows-automatically", es, (char *)NULL);
2884 webkit_web_view_set_settings(t->wv, t->settings);
2886 if (args->i & XT_WL_RELOAD)
2887 webkit_web_view_reload(t->wv);
2888 done:
2889 if (dom)
2890 g_free(dom);
2891 return (0);
2894 void
2895 js_toggle_cb(GtkWidget *w, struct tab *t)
2897 struct karg a;
2899 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2900 toggle_cwl(t, &a);
2902 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2903 toggle_js(t, &a);
2907 toggle_src(struct tab *t, struct karg *args)
2909 gboolean mode;
2911 if (t == NULL)
2912 return (0);
2914 mode = webkit_web_view_get_view_source_mode(t->wv);
2915 webkit_web_view_set_view_source_mode(t->wv, !mode);
2916 webkit_web_view_reload(t->wv);
2918 return (0);
2921 void
2922 focus_webview(struct tab *t)
2924 if (t == NULL)
2925 return;
2927 /* only grab focus if we are visible */
2928 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2929 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2933 focus(struct tab *t, struct karg *args)
2935 if (t == NULL || args == NULL)
2936 return (1);
2938 if (show_url == 0)
2939 return (0);
2941 if (args->i == XT_FOCUS_URI)
2942 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2943 else if (args->i == XT_FOCUS_SEARCH)
2944 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2946 return (0);
2950 stats(struct tab *t, struct karg *args)
2952 char *page, *body, *s, line[64 * 1024];
2953 uint64_t line_count = 0;
2954 FILE *r_cookie_f;
2956 if (t == NULL)
2957 show_oops(NULL, "stats invalid parameters");
2959 line[0] = '\0';
2960 if (save_rejected_cookies) {
2961 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2962 for (;;) {
2963 s = fgets(line, sizeof line, r_cookie_f);
2964 if (s == NULL || feof(r_cookie_f) ||
2965 ferror(r_cookie_f))
2966 break;
2967 line_count++;
2969 fclose(r_cookie_f);
2970 snprintf(line, sizeof line,
2971 "<br/>Cookies blocked(*) total: %llu", line_count);
2972 } else
2973 show_oops(t, "Can't open blocked cookies file: %s",
2974 strerror(errno));
2977 body = g_strdup_printf(
2978 "Cookies blocked(*) this session: %llu"
2979 "%s"
2980 "<p><small><b>*</b> results vary based on settings</small></p>",
2981 blocked_cookies,
2982 line);
2984 page = get_html_page("Statistics", body, "", 0);
2985 g_free(body);
2987 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2988 g_free(page);
2990 return (0);
2994 marco(struct tab *t, struct karg *args)
2996 char *page, line[64 * 1024];
2997 int len;
2999 if (t == NULL)
3000 show_oops(NULL, "marco invalid parameters");
3002 line[0] = '\0';
3003 snprintf(line, sizeof line, "%s", marco_message(&len));
3005 page = get_html_page("Marco Sez...", line, "", 0);
3007 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3008 g_free(page);
3010 return (0);
3014 blank(struct tab *t, struct karg *args)
3016 if (t == NULL)
3017 show_oops(NULL, "blank invalid parameters");
3019 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3021 return (0);
3025 about(struct tab *t, struct karg *args)
3027 char *page, *body;
3029 if (t == NULL)
3030 show_oops(NULL, "about invalid parameters");
3032 body = g_strdup_printf("<b>Version: %s</b><p>"
3033 "Authors:"
3034 "<ul>"
3035 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3036 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3037 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3038 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3039 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3040 "</ul>"
3041 "Copyrights and licenses can be found on the XXXTerm "
3042 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
3043 version
3046 page = get_html_page("About", body, "", 0);
3047 g_free(body);
3049 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3050 g_free(page);
3052 return (0);
3056 help(struct tab *t, struct karg *args)
3058 char *page, *head, *body;
3060 if (t == NULL)
3061 show_oops(NULL, "help invalid parameters");
3063 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3064 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3065 "</head>\n";
3066 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3067 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3068 "cgi-bin/man-cgi?xxxterm</a>";
3070 page = get_html_page(XT_NAME, body, head, FALSE);
3072 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3073 g_free(page);
3075 return (0);
3079 startpage(struct tab *t, struct karg *args)
3081 char *page, *body, *b;
3082 struct sp *s;
3084 if (t == NULL)
3085 show_oops(NULL, "startpage invalid parameters");
3087 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3089 TAILQ_FOREACH(s, &spl, entry) {
3090 b = body;
3091 body = g_strdup_printf("%s%s<br>", body, s->line);
3092 g_free(b);
3095 page = get_html_page("Startup Exception", body, "", 0);
3096 g_free(body);
3098 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3099 g_free(page);
3101 return (0);
3104 void
3105 startpage_add(const char *fmt, ...)
3107 va_list ap;
3108 char *msg;
3109 struct sp *s;
3111 if (fmt == NULL)
3112 return;
3114 va_start(ap, fmt);
3115 if (vasprintf(&msg, fmt, ap) == -1)
3116 errx(1, "startpage_add failed");
3117 va_end(ap);
3119 s = g_malloc0(sizeof *s);
3120 s->line = msg;
3122 TAILQ_INSERT_TAIL(&spl, s, entry);
3126 * update all favorite tabs apart from one. Pass NULL if
3127 * you want to update all.
3129 void
3130 update_favorite_tabs(struct tab *apart_from)
3132 struct tab *t;
3133 if (!updating_fl_tabs) {
3134 updating_fl_tabs = 1; /* stop infinite recursion */
3135 TAILQ_FOREACH(t, &tabs, entry)
3136 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3137 && (t != apart_from))
3138 xtp_page_fl(t, NULL);
3139 updating_fl_tabs = 0;
3143 /* show a list of favorites (bookmarks) */
3145 xtp_page_fl(struct tab *t, struct karg *args)
3147 char file[PATH_MAX];
3148 FILE *f;
3149 char *uri = NULL, *title = NULL;
3150 size_t len, lineno = 0;
3151 int i, failed = 0;
3152 char *body, *tmp, *page = NULL;
3153 const char delim[3] = {'\\', '\\', '\0'};
3155 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3157 if (t == NULL)
3158 warn("%s: bad param", __func__);
3160 /* new session key */
3161 if (!updating_fl_tabs)
3162 generate_xtp_session_key(&fl_session_key);
3164 /* open favorites */
3165 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3166 if ((f = fopen(file, "r")) == NULL) {
3167 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3168 return (1);
3171 /* body */
3172 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3173 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3174 "<th style='width: 40px'>Rm</th></tr>\n");
3176 for (i = 1;;) {
3177 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3178 if (feof(f) || ferror(f))
3179 break;
3180 if (strlen(title) == 0 || title[0] == '#') {
3181 free(title);
3182 title = NULL;
3183 continue;
3186 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3187 if (feof(f) || ferror(f)) {
3188 show_oops(t, "favorites file corrupt");
3189 failed = 1;
3190 break;
3193 tmp = body;
3194 body = g_strdup_printf("%s<tr>"
3195 "<td>%d</td>"
3196 "<td><a href='%s'>%s</a></td>"
3197 "<td style='text-align: center'>"
3198 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3199 "</tr>\n",
3200 body, i, uri, title,
3201 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3203 g_free(tmp);
3205 free(uri);
3206 uri = NULL;
3207 free(title);
3208 title = NULL;
3209 i++;
3211 fclose(f);
3213 /* if none, say so */
3214 if (i == 1) {
3215 tmp = body;
3216 body = g_strdup_printf("%s<tr>"
3217 "<td colspan='3' style='text-align: center'>"
3218 "No favorites - To add one use the 'favadd' command."
3219 "</td></tr>", body);
3220 g_free(tmp);
3223 tmp = body;
3224 body = g_strdup_printf("%s</table>", body);
3225 g_free(tmp);
3227 if (uri)
3228 free(uri);
3229 if (title)
3230 free(title);
3232 /* render */
3233 if (!failed) {
3234 page = get_html_page("Favorites", body, "", 1);
3235 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3236 g_free(page);
3239 update_favorite_tabs(t);
3241 if (body)
3242 g_free(body);
3244 return (failed);
3247 void
3248 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3249 size_t cert_count, char *title)
3251 gnutls_datum_t cinfo;
3252 char *tmp, *body;
3253 int i;
3255 body = g_strdup("");
3257 for (i = 0; i < cert_count; i++) {
3258 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3259 &cinfo))
3260 return;
3262 tmp = body;
3263 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3264 body, i, cinfo.data);
3265 gnutls_free(cinfo.data);
3266 g_free(tmp);
3269 tmp = get_html_page(title, body, "", 0);
3270 g_free(body);
3272 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3273 g_free(tmp);
3277 ca_cmd(struct tab *t, struct karg *args)
3279 FILE *f = NULL;
3280 int rv = 1, certs = 0, certs_read;
3281 struct stat sb;
3282 gnutls_datum_t dt;
3283 gnutls_x509_crt_t *c = NULL;
3284 char *certs_buf = NULL, *s;
3286 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3287 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3288 return (1);
3291 if (fstat(fileno(f), &sb) == -1) {
3292 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3293 goto done;
3296 certs_buf = g_malloc(sb.st_size + 1);
3297 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3298 show_oops(t, "Can't read CA file: %s", strerror(errno));
3299 goto done;
3301 certs_buf[sb.st_size] = '\0';
3303 s = certs_buf;
3304 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3305 certs++;
3306 s += strlen("BEGIN CERTIFICATE");
3309 bzero(&dt, sizeof dt);
3310 dt.data = (unsigned char *)certs_buf;
3311 dt.size = sb.st_size;
3312 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3313 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3314 GNUTLS_X509_FMT_PEM, 0);
3315 if (certs_read <= 0) {
3316 show_oops(t, "No cert(s) available");
3317 goto done;
3319 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3320 done:
3321 if (c)
3322 g_free(c);
3323 if (certs_buf)
3324 g_free(certs_buf);
3325 if (f)
3326 fclose(f);
3328 return (rv);
3332 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3333 size_t domain_sz)
3335 SoupURI *su = NULL;
3336 struct addrinfo hints, *res = NULL, *ai;
3337 int rv = -1, s = -1, on, error;
3338 char port[8];
3340 if (uri && !g_str_has_prefix(uri, "https://")) {
3341 show_oops(t, "invalid URI");
3342 goto done;
3345 su = soup_uri_new(uri);
3346 if (su == NULL) {
3347 show_oops(t, "invalid soup URI");
3348 goto done;
3350 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3351 show_oops(t, "invalid HTTPS URI");
3352 goto done;
3355 snprintf(port, sizeof port, "%d", su->port);
3356 bzero(&hints, sizeof(struct addrinfo));
3357 hints.ai_flags = AI_CANONNAME;
3358 hints.ai_family = AF_UNSPEC;
3359 hints.ai_socktype = SOCK_STREAM;
3361 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3362 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3363 goto done;
3366 for (ai = res; ai; ai = ai->ai_next) {
3367 if (s != -1) {
3368 close(s);
3369 s = -1;
3372 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3373 continue;
3374 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3375 if (s == -1)
3376 continue;
3377 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3378 sizeof(on)) == -1)
3379 continue;
3380 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3381 break;
3383 if (s == -1) {
3384 show_oops(t, "could not obtain certificates from: %s",
3385 su->host);
3386 goto done;
3389 if (domain)
3390 strlcpy(domain, su->host, domain_sz);
3391 rv = s;
3392 done:
3393 if (su)
3394 soup_uri_free(su);
3395 if (res)
3396 freeaddrinfo(res);
3397 if (rv == -1 && s != -1)
3398 close(s);
3400 return (rv);
3404 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3406 if (gsession)
3407 gnutls_deinit(gsession);
3408 if (xcred)
3409 gnutls_certificate_free_credentials(xcred);
3411 return (0);
3415 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3416 gnutls_certificate_credentials_t *xc)
3418 gnutls_certificate_credentials_t xcred;
3419 gnutls_session_t gsession;
3420 int rv = 1;
3422 if (gs == NULL || xc == NULL)
3423 goto done;
3425 *gs = NULL;
3426 *xc = NULL;
3428 gnutls_certificate_allocate_credentials(&xcred);
3429 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3430 GNUTLS_X509_FMT_PEM);
3432 gnutls_init(&gsession, GNUTLS_CLIENT);
3433 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3434 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3435 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3436 if ((rv = gnutls_handshake(gsession)) < 0) {
3437 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3439 gnutls_error_is_fatal(rv),
3440 gnutls_strerror_name(rv));
3441 stop_tls(gsession, xcred);
3442 goto done;
3445 gnutls_credentials_type_t cred;
3446 cred = gnutls_auth_get_type(gsession);
3447 if (cred != GNUTLS_CRD_CERTIFICATE) {
3448 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3449 stop_tls(gsession, xcred);
3450 goto done;
3453 *gs = gsession;
3454 *xc = xcred;
3455 rv = 0;
3456 done:
3457 return (rv);
3461 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3462 size_t *cert_count)
3464 unsigned int len;
3465 const gnutls_datum_t *cl;
3466 gnutls_x509_crt_t *all_certs;
3467 int i, rv = 1;
3469 if (certs == NULL || cert_count == NULL)
3470 goto done;
3471 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3472 goto done;
3473 cl = gnutls_certificate_get_peers(gsession, &len);
3474 if (len == 0)
3475 goto done;
3477 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3478 for (i = 0; i < len; i++) {
3479 gnutls_x509_crt_init(&all_certs[i]);
3480 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3481 GNUTLS_X509_FMT_PEM < 0)) {
3482 g_free(all_certs);
3483 goto done;
3487 *certs = all_certs;
3488 *cert_count = len;
3489 rv = 0;
3490 done:
3491 return (rv);
3494 void
3495 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3497 int i;
3499 for (i = 0; i < cert_count; i++)
3500 gnutls_x509_crt_deinit(certs[i]);
3501 g_free(certs);
3504 void
3505 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3507 GdkColor c_text, c_base;
3509 gdk_color_parse(text, &c_text);
3510 gdk_color_parse(base, &c_base);
3512 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3513 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3514 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3515 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3517 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3518 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3519 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3520 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3523 void
3524 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3525 size_t cert_count, char *domain)
3527 size_t cert_buf_sz;
3528 char cert_buf[64 * 1024], file[PATH_MAX];
3529 int i;
3530 FILE *f;
3531 GdkColor color;
3533 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3534 return;
3536 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3537 if ((f = fopen(file, "w")) == NULL) {
3538 show_oops(t, "Can't create cert file %s %s",
3539 file, strerror(errno));
3540 return;
3543 for (i = 0; i < cert_count; i++) {
3544 cert_buf_sz = sizeof cert_buf;
3545 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3546 cert_buf, &cert_buf_sz)) {
3547 show_oops(t, "gnutls_x509_crt_export failed");
3548 goto done;
3550 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3551 show_oops(t, "Can't write certs: %s", strerror(errno));
3552 goto done;
3556 /* not the best spot but oh well */
3557 gdk_color_parse("lightblue", &color);
3558 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3559 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3560 done:
3561 fclose(f);
3564 enum cert_trust {
3565 CERT_LOCAL,
3566 CERT_TRUSTED,
3567 CERT_UNTRUSTED,
3568 CERT_BAD
3571 enum cert_trust
3572 load_compare_cert(struct tab *t, struct karg *args)
3574 const gchar *uri;
3575 char domain[8182], file[PATH_MAX];
3576 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3577 int s = -1, i, error;
3578 FILE *f = NULL;
3579 size_t cert_buf_sz, cert_count;
3580 enum cert_trust rv = CERT_UNTRUSTED;
3581 char serr[80];
3582 gnutls_session_t gsession;
3583 gnutls_x509_crt_t *certs;
3584 gnutls_certificate_credentials_t xcred;
3586 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3588 if (t == NULL)
3589 return (rv);
3591 if ((uri = get_uri(t)) == NULL)
3592 return (rv);
3593 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3595 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3596 return (rv);
3597 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3599 /* go ssl/tls */
3600 if (start_tls(t, s, &gsession, &xcred))
3601 goto done;
3602 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3604 /* verify certs in case cert file doesn't exist */
3605 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3606 GNUTLS_E_SUCCESS) {
3607 show_oops(t, "Invalid certificates");
3608 goto done;
3611 /* get certs */
3612 if (get_connection_certs(gsession, &certs, &cert_count)) {
3613 show_oops(t, "Can't get connection certificates");
3614 goto done;
3617 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3618 if ((f = fopen(file, "r")) == NULL) {
3619 if (!error)
3620 rv = CERT_TRUSTED;
3621 goto freeit;
3624 for (i = 0; i < cert_count; i++) {
3625 cert_buf_sz = sizeof cert_buf;
3626 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3627 cert_buf, &cert_buf_sz)) {
3628 goto freeit;
3630 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3631 rv = CERT_BAD; /* critical */
3632 goto freeit;
3634 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3635 rv = CERT_BAD; /* critical */
3636 goto freeit;
3638 rv = CERT_LOCAL;
3641 freeit:
3642 if (f)
3643 fclose(f);
3644 free_connection_certs(certs, cert_count);
3645 done:
3646 /* we close the socket first for speed */
3647 if (s != -1)
3648 close(s);
3650 /* only complain if we didn't save it locally */
3651 if (error && rv != CERT_LOCAL) {
3652 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3653 if (error & GNUTLS_CERT_INVALID)
3654 strlcat(serr, "invalid, ", sizeof serr);
3655 if (error & GNUTLS_CERT_REVOKED)
3656 strlcat(serr, "revoked, ", sizeof serr);
3657 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3658 strlcat(serr, "signer not found, ", sizeof serr);
3659 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3660 strlcat(serr, "not signed by CA, ", sizeof serr);
3661 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3662 strlcat(serr, "insecure algorithm, ", sizeof serr);
3663 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3664 strlcat(serr, "not activated, ", sizeof serr);
3665 if (error & GNUTLS_CERT_EXPIRED)
3666 strlcat(serr, "expired, ", sizeof serr);
3667 for (i = strlen(serr) - 1; i > 0; i--)
3668 if (serr[i] == ',') {
3669 serr[i] = '\0';
3670 break;
3672 show_oops(t, serr);
3675 stop_tls(gsession, xcred);
3677 return (rv);
3681 cert_cmd(struct tab *t, struct karg *args)
3683 const gchar *uri;
3684 char domain[8182];
3685 int s = -1;
3686 size_t cert_count;
3687 gnutls_session_t gsession;
3688 gnutls_x509_crt_t *certs;
3689 gnutls_certificate_credentials_t xcred;
3691 if (t == NULL)
3692 return (1);
3694 if (ssl_ca_file == NULL) {
3695 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3696 return (1);
3699 if ((uri = get_uri(t)) == NULL) {
3700 show_oops(t, "Invalid URI");
3701 return (1);
3704 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3705 show_oops(t, "Invalid certificate URI: %s", uri);
3706 return (1);
3709 /* go ssl/tls */
3710 if (start_tls(t, s, &gsession, &xcred))
3711 goto done;
3713 /* get certs */
3714 if (get_connection_certs(gsession, &certs, &cert_count)) {
3715 show_oops(t, "get_connection_certs failed");
3716 goto done;
3719 if (args->i & XT_SHOW)
3720 show_certs(t, certs, cert_count, "Certificate Chain");
3721 else if (args->i & XT_SAVE)
3722 save_certs(t, certs, cert_count, domain);
3724 free_connection_certs(certs, cert_count);
3725 done:
3726 /* we close the socket first for speed */
3727 if (s != -1)
3728 close(s);
3729 stop_tls(gsession, xcred);
3731 return (0);
3735 remove_cookie(int index)
3737 int i, rv = 1;
3738 GSList *cf;
3739 SoupCookie *c;
3741 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3743 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3745 for (i = 1; cf; cf = cf->next, i++) {
3746 if (i != index)
3747 continue;
3748 c = cf->data;
3749 print_cookie("remove cookie", c);
3750 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3751 rv = 0;
3752 break;
3755 soup_cookies_free(cf);
3757 return (rv);
3761 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3763 struct domain *d;
3764 char *tmp, *body;
3766 body = g_strdup("");
3768 /* p list */
3769 if (args->i & XT_WL_PERSISTENT) {
3770 tmp = body;
3771 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3772 g_free(tmp);
3773 RB_FOREACH(d, domain_list, wl) {
3774 if (d->handy == 0)
3775 continue;
3776 tmp = body;
3777 body = g_strdup_printf("%s%s<br/>", body, d->d);
3778 g_free(tmp);
3782 /* s list */
3783 if (args->i & XT_WL_SESSION) {
3784 tmp = body;
3785 body = g_strdup_printf("%s<h2>Session</h2>", body);
3786 g_free(tmp);
3787 RB_FOREACH(d, domain_list, wl) {
3788 if (d->handy == 1)
3789 continue;
3790 tmp = body;
3791 body = g_strdup_printf("%s%s<br/>", body, d->d);
3792 g_free(tmp);
3796 tmp = get_html_page(title, body, "", 0);
3797 g_free(body);
3798 if (wl == &js_wl)
3799 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3800 else
3801 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3802 g_free(tmp);
3803 return (0);
3807 wl_save(struct tab *t, struct karg *args, int js)
3809 char file[PATH_MAX];
3810 FILE *f;
3811 char *line = NULL, *lt = NULL, *dom = NULL;
3812 size_t linelen;
3813 const gchar *uri;
3814 struct karg a;
3815 struct domain *d;
3816 GSList *cf;
3817 SoupCookie *ci, *c;
3819 if (t == NULL || args == NULL)
3820 return (1);
3822 if (runtime_settings[0] == '\0')
3823 return (1);
3825 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3826 if ((f = fopen(file, "r+")) == NULL)
3827 return (1);
3829 uri = get_uri(t);
3830 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3831 if (uri == NULL || dom == NULL ||
3832 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3833 show_oops(t, "Can't add domain to %s white list",
3834 js ? "JavaScript" : "cookie");
3835 goto done;
3838 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3840 while (!feof(f)) {
3841 line = fparseln(f, &linelen, NULL, NULL, 0);
3842 if (line == NULL)
3843 continue;
3844 if (!strcmp(line, lt))
3845 goto done;
3846 free(line);
3847 line = NULL;
3850 fprintf(f, "%s\n", lt);
3852 a.i = XT_WL_ENABLE;
3853 a.i |= args->i;
3854 if (js) {
3855 d = wl_find(dom, &js_wl);
3856 if (!d) {
3857 settings_add("js_wl", dom);
3858 d = wl_find(dom, &js_wl);
3860 toggle_js(t, &a);
3861 } else {
3862 d = wl_find(dom, &c_wl);
3863 if (!d) {
3864 settings_add("cookie_wl", dom);
3865 d = wl_find(dom, &c_wl);
3867 toggle_cwl(t, &a);
3869 /* find and add to persistent jar */
3870 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3871 for (;cf; cf = cf->next) {
3872 ci = cf->data;
3873 if (!strcmp(dom, ci->domain) ||
3874 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3875 c = soup_cookie_copy(ci);
3876 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3879 soup_cookies_free(cf);
3881 if (d)
3882 d->handy = 1;
3884 done:
3885 if (line)
3886 free(line);
3887 if (dom)
3888 g_free(dom);
3889 if (lt)
3890 g_free(lt);
3891 fclose(f);
3893 return (0);
3897 js_show_wl(struct tab *t, struct karg *args)
3899 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3900 wl_show(t, args, "JavaScript White List", &js_wl);
3902 return (0);
3906 cookie_show_wl(struct tab *t, struct karg *args)
3908 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3909 wl_show(t, args, "Cookie White List", &c_wl);
3911 return (0);
3915 cookie_cmd(struct tab *t, struct karg *args)
3917 if (args->i & XT_SHOW)
3918 wl_show(t, args, "Cookie White List", &c_wl);
3919 else if (args->i & XT_WL_TOGGLE) {
3920 args->i |= XT_WL_RELOAD;
3921 toggle_cwl(t, args);
3922 } else if (args->i & XT_SAVE) {
3923 args->i |= XT_WL_RELOAD;
3924 wl_save(t, args, 0);
3925 } else if (args->i & XT_DELETE)
3926 show_oops(t, "'cookie delete' currently unimplemented");
3928 return (0);
3932 js_cmd(struct tab *t, struct karg *args)
3934 if (args->i & XT_SHOW)
3935 wl_show(t, args, "JavaScript White List", &js_wl);
3936 else if (args->i & XT_SAVE) {
3937 args->i |= XT_WL_RELOAD;
3938 wl_save(t, args, 1);
3939 } else if (args->i & XT_WL_TOGGLE) {
3940 args->i |= XT_WL_RELOAD;
3941 toggle_js(t, args);
3942 } else if (args->i & XT_DELETE)
3943 show_oops(t, "'js delete' currently unimplemented");
3945 return (0);
3949 toplevel_cmd(struct tab *t, struct karg *args)
3951 js_toggle_cb(t->js_toggle, t);
3953 return (0);
3957 add_favorite(struct tab *t, struct karg *args)
3959 char file[PATH_MAX];
3960 FILE *f;
3961 char *line = NULL;
3962 size_t urilen, linelen;
3963 const gchar *uri, *title;
3965 if (t == NULL)
3966 return (1);
3968 /* don't allow adding of xtp pages to favorites */
3969 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3970 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3971 return (1);
3974 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3975 if ((f = fopen(file, "r+")) == NULL) {
3976 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3977 return (1);
3980 title = get_title(t, FALSE);
3981 uri = get_uri(t);
3983 if (title == NULL || uri == NULL) {
3984 show_oops(t, "can't add page to favorites");
3985 goto done;
3988 urilen = strlen(uri);
3990 for (;;) {
3991 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3992 if (feof(f) || ferror(f))
3993 break;
3995 if (linelen == urilen && !strcmp(line, uri))
3996 goto done;
3998 free(line);
3999 line = NULL;
4002 fprintf(f, "\n%s\n%s", title, uri);
4003 done:
4004 if (line)
4005 free(line);
4006 fclose(f);
4008 update_favorite_tabs(NULL);
4010 return (0);
4014 can_go_back_for_real(struct tab *t)
4016 int i;
4017 WebKitWebHistoryItem *item;
4019 /* rely on webkit to make sure we can go backward when on an about page */
4020 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4021 return (webkit_web_view_can_go_forward(t->wv));
4024 /* the back/forwars list is stupid so help determine if we can go back */
4025 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4026 item != NULL;
4027 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4028 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4029 return (TRUE);
4032 return (FALSE);
4036 can_go_forward_for_real(struct tab *t)
4038 int i;
4039 WebKitWebHistoryItem *item;
4041 /* rely on webkit to make sure we can go forward when on an about page */
4042 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4043 return (webkit_web_view_can_go_forward(t->wv));
4045 /* the back/forwars list is stupid so help selecting a different item */
4046 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4047 item != NULL;
4048 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4049 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4050 return (TRUE);
4053 return (FALSE);
4056 void
4057 go_back_for_real(struct tab *t)
4059 int i;
4060 WebKitWebHistoryItem *item;
4062 /* the back/forwars list is stupid so help selecting a different item */
4063 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4064 item != NULL;
4065 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4066 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4067 webkit_web_view_go_to_back_forward_item(t->wv, item);
4068 break;
4073 void
4074 go_forward_for_real(struct tab *t)
4076 int i;
4077 WebKitWebHistoryItem *item;
4079 /* the back/forwars list is stupid so help selecting a different item */
4080 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4081 item != NULL;
4082 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4083 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4084 webkit_web_view_go_to_back_forward_item(t->wv, item);
4085 break;
4091 navaction(struct tab *t, struct karg *args)
4093 WebKitWebHistoryItem *item;
4094 WebKitWebFrame *frame;
4096 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4097 t->tab_id, args->i);
4099 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4100 if (t->item) {
4101 if (args->i == XT_NAV_BACK)
4102 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4103 else
4104 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4105 if (item == NULL)
4106 return (XT_CB_PASSTHROUGH);
4107 webkit_web_view_go_to_back_forward_item(t->wv, item);
4108 t->item = NULL;
4109 return (XT_CB_PASSTHROUGH);
4112 switch (args->i) {
4113 case XT_NAV_BACK:
4114 marks_clear(t);
4115 go_back_for_real(t);
4116 break;
4117 case XT_NAV_FORWARD:
4118 marks_clear(t);
4119 go_forward_for_real(t);
4120 break;
4121 case XT_NAV_RELOAD:
4122 frame = webkit_web_view_get_main_frame(t->wv);
4123 webkit_web_frame_reload(frame);
4124 break;
4126 return (XT_CB_PASSTHROUGH);
4130 move(struct tab *t, struct karg *args)
4132 GtkAdjustment *adjust;
4133 double pi, si, pos, ps, upper, lower, max;
4134 double percent;
4136 switch (args->i) {
4137 case XT_MOVE_DOWN:
4138 case XT_MOVE_UP:
4139 case XT_MOVE_BOTTOM:
4140 case XT_MOVE_TOP:
4141 case XT_MOVE_PAGEDOWN:
4142 case XT_MOVE_PAGEUP:
4143 case XT_MOVE_HALFDOWN:
4144 case XT_MOVE_HALFUP:
4145 case XT_MOVE_PERCENT:
4146 adjust = t->adjust_v;
4147 break;
4148 default:
4149 adjust = t->adjust_h;
4150 break;
4153 pos = gtk_adjustment_get_value(adjust);
4154 ps = gtk_adjustment_get_page_size(adjust);
4155 upper = gtk_adjustment_get_upper(adjust);
4156 lower = gtk_adjustment_get_lower(adjust);
4157 si = gtk_adjustment_get_step_increment(adjust);
4158 pi = gtk_adjustment_get_page_increment(adjust);
4159 max = upper - ps;
4161 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4162 "max %f si %f pi %f\n",
4163 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4164 pos, ps, upper, lower, max, si, pi);
4166 switch (args->i) {
4167 case XT_MOVE_DOWN:
4168 case XT_MOVE_RIGHT:
4169 pos += si;
4170 gtk_adjustment_set_value(adjust, MIN(pos, max));
4171 break;
4172 case XT_MOVE_UP:
4173 case XT_MOVE_LEFT:
4174 pos -= si;
4175 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4176 break;
4177 case XT_MOVE_BOTTOM:
4178 case XT_MOVE_FARRIGHT:
4179 gtk_adjustment_set_value(adjust, max);
4180 break;
4181 case XT_MOVE_TOP:
4182 case XT_MOVE_FARLEFT:
4183 gtk_adjustment_set_value(adjust, lower);
4184 break;
4185 case XT_MOVE_PAGEDOWN:
4186 pos += pi;
4187 gtk_adjustment_set_value(adjust, MIN(pos, max));
4188 break;
4189 case XT_MOVE_PAGEUP:
4190 pos -= pi;
4191 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4192 break;
4193 case XT_MOVE_HALFDOWN:
4194 pos += pi / 2;
4195 gtk_adjustment_set_value(adjust, MIN(pos, max));
4196 break;
4197 case XT_MOVE_HALFUP:
4198 pos -= pi / 2;
4199 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4200 break;
4201 case XT_MOVE_PERCENT:
4202 percent = atoi(args->s) / 100.0;
4203 pos = max * percent;
4204 if (pos < 0.0 || pos > max)
4205 break;
4206 gtk_adjustment_set_value(adjust, pos);
4207 break;
4208 default:
4209 return (XT_CB_PASSTHROUGH);
4212 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4214 return (XT_CB_HANDLED);
4217 void
4218 url_set_visibility(void)
4220 struct tab *t;
4222 TAILQ_FOREACH(t, &tabs, entry)
4223 if (show_url == 0) {
4224 gtk_widget_hide(t->toolbar);
4225 focus_webview(t);
4226 } else
4227 gtk_widget_show(t->toolbar);
4230 void
4231 notebook_tab_set_visibility(void)
4233 if (show_tabs == 0) {
4234 gtk_widget_hide(tab_bar);
4235 gtk_notebook_set_show_tabs(notebook, FALSE);
4236 } else {
4237 if (tab_style == XT_TABS_NORMAL) {
4238 gtk_widget_hide(tab_bar);
4239 gtk_notebook_set_show_tabs(notebook, TRUE);
4240 } else if (tab_style == XT_TABS_COMPACT) {
4241 gtk_widget_show(tab_bar);
4242 gtk_notebook_set_show_tabs(notebook, FALSE);
4247 void
4248 statusbar_set_visibility(void)
4250 struct tab *t;
4252 TAILQ_FOREACH(t, &tabs, entry)
4253 if (show_statusbar == 0) {
4254 gtk_widget_hide(t->statusbar_box);
4255 focus_webview(t);
4256 } else
4257 gtk_widget_show(t->statusbar_box);
4260 void
4261 url_set(struct tab *t, int enable_url_entry)
4263 GdkPixbuf *pixbuf;
4264 int progress;
4266 show_url = enable_url_entry;
4268 if (enable_url_entry) {
4269 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4270 GTK_ENTRY_ICON_PRIMARY, NULL);
4271 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4272 } else {
4273 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4274 GTK_ENTRY_ICON_PRIMARY);
4275 progress =
4276 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4277 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4278 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4279 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4280 progress);
4285 fullscreen(struct tab *t, struct karg *args)
4287 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4289 if (t == NULL)
4290 return (XT_CB_PASSTHROUGH);
4292 if (show_url == 0) {
4293 url_set(t, 1);
4294 show_tabs = 1;
4295 } else {
4296 url_set(t, 0);
4297 show_tabs = 0;
4300 url_set_visibility();
4301 notebook_tab_set_visibility();
4303 return (XT_CB_HANDLED);
4307 statustoggle(struct tab *t, struct karg *args)
4309 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4311 if (show_statusbar == 1) {
4312 show_statusbar = 0;
4313 statusbar_set_visibility();
4314 } else if (show_statusbar == 0) {
4315 show_statusbar = 1;
4316 statusbar_set_visibility();
4318 return (XT_CB_HANDLED);
4322 urlaction(struct tab *t, struct karg *args)
4324 int rv = XT_CB_HANDLED;
4326 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4328 if (t == NULL)
4329 return (XT_CB_PASSTHROUGH);
4331 switch (args->i) {
4332 case XT_URL_SHOW:
4333 if (show_url == 0) {
4334 url_set(t, 1);
4335 url_set_visibility();
4337 break;
4338 case XT_URL_HIDE:
4339 if (show_url == 1) {
4340 url_set(t, 0);
4341 url_set_visibility();
4343 break;
4345 return (rv);
4349 tabaction(struct tab *t, struct karg *args)
4351 int rv = XT_CB_HANDLED;
4352 char *url = args->s;
4353 struct undo *u;
4354 struct tab *tt;
4356 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4358 if (t == NULL)
4359 return (XT_CB_PASSTHROUGH);
4361 switch (args->i) {
4362 case XT_TAB_NEW:
4363 if (strlen(url) > 0)
4364 create_new_tab(url, NULL, 1, args->precount);
4365 else
4366 create_new_tab(NULL, NULL, 1, args->precount);
4367 break;
4368 case XT_TAB_DELETE:
4369 if (args->precount < 0)
4370 delete_tab(t);
4371 else
4372 TAILQ_FOREACH(tt, &tabs, entry)
4373 if (tt->tab_id == args->precount - 1) {
4374 delete_tab(tt);
4375 break;
4377 break;
4378 case XT_TAB_DELQUIT:
4379 if (gtk_notebook_get_n_pages(notebook) > 1)
4380 delete_tab(t);
4381 else
4382 quit(t, args);
4383 break;
4384 case XT_TAB_OPEN:
4385 if (strlen(url) > 0)
4387 else {
4388 rv = XT_CB_PASSTHROUGH;
4389 goto done;
4391 load_uri(t, url);
4392 break;
4393 case XT_TAB_SHOW:
4394 if (show_tabs == 0) {
4395 show_tabs = 1;
4396 notebook_tab_set_visibility();
4398 break;
4399 case XT_TAB_HIDE:
4400 if (show_tabs == 1) {
4401 show_tabs = 0;
4402 notebook_tab_set_visibility();
4404 break;
4405 case XT_TAB_NEXTSTYLE:
4406 if (tab_style == XT_TABS_NORMAL) {
4407 tab_style = XT_TABS_COMPACT;
4408 recolor_compact_tabs();
4410 else
4411 tab_style = XT_TABS_NORMAL;
4412 notebook_tab_set_visibility();
4413 break;
4414 case XT_TAB_UNDO_CLOSE:
4415 if (undo_count == 0) {
4416 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4417 __func__);
4418 goto done;
4419 } else {
4420 undo_count--;
4421 u = TAILQ_FIRST(&undos);
4422 create_new_tab(u->uri, u, 1, -1);
4424 TAILQ_REMOVE(&undos, u, entry);
4425 g_free(u->uri);
4426 /* u->history is freed in create_new_tab() */
4427 g_free(u);
4429 break;
4430 default:
4431 rv = XT_CB_PASSTHROUGH;
4432 goto done;
4435 done:
4436 if (args->s) {
4437 g_free(args->s);
4438 args->s = NULL;
4441 return (rv);
4445 resizetab(struct tab *t, struct karg *args)
4447 if (t == NULL || args == NULL) {
4448 show_oops(NULL, "resizetab invalid parameters");
4449 return (XT_CB_PASSTHROUGH);
4452 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4453 t->tab_id, args->i);
4455 setzoom_webkit(t, args->i);
4457 return (XT_CB_HANDLED);
4461 movetab(struct tab *t, struct karg *args)
4463 int n, dest;
4465 if (t == NULL || args == NULL) {
4466 show_oops(NULL, "movetab invalid parameters");
4467 return (XT_CB_PASSTHROUGH);
4470 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4471 t->tab_id, args->i);
4473 if (args->i >= XT_TAB_INVALID)
4474 return (XT_CB_PASSTHROUGH);
4476 if (TAILQ_EMPTY(&tabs))
4477 return (XT_CB_PASSTHROUGH);
4479 n = gtk_notebook_get_n_pages(notebook);
4480 dest = gtk_notebook_get_current_page(notebook);
4482 switch (args->i) {
4483 case XT_TAB_NEXT:
4484 if (args->precount < 0)
4485 dest = dest == n - 1 ? 0 : dest + 1;
4486 else
4487 dest = args->precount - 1;
4489 break;
4490 case XT_TAB_PREV:
4491 if (args->precount < 0)
4492 dest -= 1;
4493 else
4494 dest -= args->precount % n;
4496 if (dest < 0)
4497 dest += n;
4499 break;
4500 case XT_TAB_FIRST:
4501 dest = 0;
4502 break;
4503 case XT_TAB_LAST:
4504 dest = n - 1;
4505 break;
4506 default:
4507 return (XT_CB_PASSTHROUGH);
4510 if (dest < 0 || dest >= n)
4511 return (XT_CB_PASSTHROUGH);
4512 if (t->tab_id == dest) {
4513 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4514 return (XT_CB_HANDLED);
4517 set_current_tab(dest);
4519 return (XT_CB_HANDLED);
4522 int cmd_prefix = 0;
4525 command(struct tab *t, struct karg *args)
4527 char *s = NULL, *ss = NULL;
4528 GdkColor color;
4529 const gchar *uri;
4531 if (t == NULL || args == NULL) {
4532 show_oops(NULL, "command invalid parameters");
4533 return (XT_CB_PASSTHROUGH);
4536 switch (args->i) {
4537 case '/':
4538 s = "/";
4539 break;
4540 case '?':
4541 s = "?";
4542 break;
4543 case ':':
4544 if (cmd_prefix == 0)
4545 s = ":";
4546 else {
4547 ss = g_strdup_printf(":%d", cmd_prefix);
4548 s = ss;
4549 cmd_prefix = 0;
4551 break;
4552 case XT_CMD_OPEN:
4553 s = ":open ";
4554 break;
4555 case XT_CMD_TABNEW:
4556 s = ":tabnew ";
4557 break;
4558 case XT_CMD_OPEN_CURRENT:
4559 s = ":open ";
4560 /* FALL THROUGH */
4561 case XT_CMD_TABNEW_CURRENT:
4562 if (!s) /* FALL THROUGH? */
4563 s = ":tabnew ";
4564 if ((uri = get_uri(t)) != NULL) {
4565 ss = g_strdup_printf("%s%s", s, uri);
4566 s = ss;
4568 break;
4569 default:
4570 show_oops(t, "command: invalid opcode %d", args->i);
4571 return (XT_CB_PASSTHROUGH);
4574 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4576 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4577 gdk_color_parse(XT_COLOR_WHITE, &color);
4578 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4579 show_cmd(t);
4580 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4581 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4583 if (ss)
4584 g_free(ss);
4586 return (XT_CB_HANDLED);
4590 * Return a new string with a download row (in html)
4591 * appended. Old string is freed.
4593 char *
4594 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4597 WebKitDownloadStatus stat;
4598 char *status_html = NULL, *cmd_html = NULL, *new_html;
4599 gdouble progress;
4600 char cur_sz[FMT_SCALED_STRSIZE];
4601 char tot_sz[FMT_SCALED_STRSIZE];
4602 char *xtp_prefix;
4604 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4606 /* All actions wil take this form:
4607 * xxxt://class/seskey
4609 xtp_prefix = g_strdup_printf("%s%d/%s/",
4610 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4612 stat = webkit_download_get_status(dl->download);
4614 switch (stat) {
4615 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4616 status_html = g_strdup_printf("Finished");
4617 cmd_html = g_strdup_printf(
4618 "<a href='%s%d/%d'>Remove</a>",
4619 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4620 break;
4621 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4622 /* gather size info */
4623 progress = 100 * webkit_download_get_progress(dl->download);
4625 fmt_scaled(
4626 webkit_download_get_current_size(dl->download), cur_sz);
4627 fmt_scaled(
4628 webkit_download_get_total_size(dl->download), tot_sz);
4630 status_html = g_strdup_printf(
4631 "<div style='width: 100%%' align='center'>"
4632 "<div class='progress-outer'>"
4633 "<div class='progress-inner' style='width: %.2f%%'>"
4634 "</div></div></div>"
4635 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4636 progress, cur_sz, tot_sz, progress);
4638 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4639 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4641 break;
4642 /* LLL */
4643 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4644 status_html = g_strdup_printf("Cancelled");
4645 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4646 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4647 break;
4648 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4649 status_html = g_strdup_printf("Error!");
4650 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4651 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4652 break;
4653 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4654 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4655 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4656 status_html = g_strdup_printf("Starting");
4657 break;
4658 default:
4659 show_oops(t, "%s: unknown download status", __func__);
4662 new_html = g_strdup_printf(
4663 "%s\n<tr><td>%s</td><td>%s</td>"
4664 "<td style='text-align:center'>%s</td></tr>\n",
4665 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4666 status_html, cmd_html);
4667 g_free(html);
4669 if (status_html)
4670 g_free(status_html);
4672 if (cmd_html)
4673 g_free(cmd_html);
4675 g_free(xtp_prefix);
4677 return new_html;
4681 * update all download tabs apart from one. Pass NULL if
4682 * you want to update all.
4684 void
4685 update_download_tabs(struct tab *apart_from)
4687 struct tab *t;
4688 if (!updating_dl_tabs) {
4689 updating_dl_tabs = 1; /* stop infinite recursion */
4690 TAILQ_FOREACH(t, &tabs, entry)
4691 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4692 && (t != apart_from))
4693 xtp_page_dl(t, NULL);
4694 updating_dl_tabs = 0;
4699 * update all cookie tabs apart from one. Pass NULL if
4700 * you want to update all.
4702 void
4703 update_cookie_tabs(struct tab *apart_from)
4705 struct tab *t;
4706 if (!updating_cl_tabs) {
4707 updating_cl_tabs = 1; /* stop infinite recursion */
4708 TAILQ_FOREACH(t, &tabs, entry)
4709 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4710 && (t != apart_from))
4711 xtp_page_cl(t, NULL);
4712 updating_cl_tabs = 0;
4717 * update all history tabs apart from one. Pass NULL if
4718 * you want to update all.
4720 void
4721 update_history_tabs(struct tab *apart_from)
4723 struct tab *t;
4725 if (!updating_hl_tabs) {
4726 updating_hl_tabs = 1; /* stop infinite recursion */
4727 TAILQ_FOREACH(t, &tabs, entry)
4728 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4729 && (t != apart_from))
4730 xtp_page_hl(t, NULL);
4731 updating_hl_tabs = 0;
4735 /* cookie management XTP page */
4737 xtp_page_cl(struct tab *t, struct karg *args)
4739 char *body, *page, *tmp;
4740 int i = 1; /* all ids start 1 */
4741 GSList *sc, *pc, *pc_start;
4742 SoupCookie *c;
4743 char *type, *table_headers, *last_domain;
4745 DNPRINTF(XT_D_CMD, "%s", __func__);
4747 if (t == NULL) {
4748 show_oops(NULL, "%s invalid parameters", __func__);
4749 return (1);
4752 /* Generate a new session key */
4753 if (!updating_cl_tabs)
4754 generate_xtp_session_key(&cl_session_key);
4756 /* table headers */
4757 table_headers = g_strdup_printf("<table><tr>"
4758 "<th>Type</th>"
4759 "<th>Name</th>"
4760 "<th style='width:200px'>Value</th>"
4761 "<th>Path</th>"
4762 "<th>Expires</th>"
4763 "<th>Secure</th>"
4764 "<th>HTTP<br />only</th>"
4765 "<th style='width:40px'>Rm</th></tr>\n");
4767 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4768 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4769 pc_start = pc;
4771 body = NULL;
4772 last_domain = strdup("");
4773 for (; sc; sc = sc->next) {
4774 c = sc->data;
4776 if (strcmp(last_domain, c->domain) != 0) {
4777 /* new domain */
4778 free(last_domain);
4779 last_domain = strdup(c->domain);
4781 if (body != NULL) {
4782 tmp = body;
4783 body = g_strdup_printf("%s</table>"
4784 "<h2>%s</h2>%s\n",
4785 body, c->domain, table_headers);
4786 g_free(tmp);
4787 } else {
4788 /* first domain */
4789 body = g_strdup_printf("<h2>%s</h2>%s\n",
4790 c->domain, table_headers);
4794 type = "Session";
4795 for (pc = pc_start; pc; pc = pc->next)
4796 if (soup_cookie_equal(pc->data, c)) {
4797 type = "Session + Persistent";
4798 break;
4801 tmp = body;
4802 body = g_strdup_printf(
4803 "%s\n<tr>"
4804 "<td>%s</td>"
4805 "<td style='word-wrap:normal'>%s</td>"
4806 "<td>"
4807 " <textarea rows='4'>%s</textarea>"
4808 "</td>"
4809 "<td>%s</td>"
4810 "<td>%s</td>"
4811 "<td>%d</td>"
4812 "<td>%d</td>"
4813 "<td style='text-align:center'>"
4814 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4815 body,
4816 type,
4817 c->name,
4818 c->value,
4819 c->path,
4820 c->expires ?
4821 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4822 c->secure,
4823 c->http_only,
4825 XT_XTP_STR,
4826 XT_XTP_CL,
4827 cl_session_key,
4828 XT_XTP_CL_REMOVE,
4832 g_free(tmp);
4833 i++;
4836 soup_cookies_free(sc);
4837 soup_cookies_free(pc);
4839 /* small message if there are none */
4840 if (i == 1) {
4841 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4842 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4844 tmp = body;
4845 body = g_strdup_printf("%s</table>", body);
4846 g_free(tmp);
4848 page = get_html_page("Cookie Jar", body, "", TRUE);
4849 g_free(body);
4850 g_free(table_headers);
4851 g_free(last_domain);
4853 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4854 update_cookie_tabs(t);
4856 g_free(page);
4858 return (0);
4862 xtp_page_hl(struct tab *t, struct karg *args)
4864 char *body, *page, *tmp;
4865 struct history *h;
4866 int i = 1; /* all ids start 1 */
4868 DNPRINTF(XT_D_CMD, "%s", __func__);
4870 if (t == NULL) {
4871 show_oops(NULL, "%s invalid parameters", __func__);
4872 return (1);
4875 /* Generate a new session key */
4876 if (!updating_hl_tabs)
4877 generate_xtp_session_key(&hl_session_key);
4879 /* body */
4880 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4881 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4883 RB_FOREACH_REVERSE(h, history_list, &hl) {
4884 tmp = body;
4885 body = g_strdup_printf(
4886 "%s\n<tr>"
4887 "<td><a href='%s'>%s</a></td>"
4888 "<td>%s</td>"
4889 "<td style='text-align: center'>"
4890 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4891 body, h->uri, h->uri, h->title,
4892 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4893 XT_XTP_HL_REMOVE, i);
4895 g_free(tmp);
4896 i++;
4899 /* small message if there are none */
4900 if (i == 1) {
4901 tmp = body;
4902 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4903 "colspan='3'>No History</td></tr>\n", body);
4904 g_free(tmp);
4907 tmp = body;
4908 body = g_strdup_printf("%s</table>", body);
4909 g_free(tmp);
4911 page = get_html_page("History", body, "", TRUE);
4912 g_free(body);
4915 * update all history manager tabs as the xtp session
4916 * key has now changed. No need to update the current tab.
4917 * Already did that above.
4919 update_history_tabs(t);
4921 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4922 g_free(page);
4924 return (0);
4928 * Generate a web page detailing the status of any downloads
4931 xtp_page_dl(struct tab *t, struct karg *args)
4933 struct download *dl;
4934 char *body, *page, *tmp;
4935 char *ref;
4936 int n_dl = 1;
4938 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4940 if (t == NULL) {
4941 show_oops(NULL, "%s invalid parameters", __func__);
4942 return (1);
4946 * Generate a new session key for next page instance.
4947 * This only happens for the top level call to xtp_page_dl()
4948 * in which case updating_dl_tabs is 0.
4950 if (!updating_dl_tabs)
4951 generate_xtp_session_key(&dl_session_key);
4953 /* header - with refresh so as to update */
4954 if (refresh_interval >= 1)
4955 ref = g_strdup_printf(
4956 "<meta http-equiv='refresh' content='%u"
4957 ";url=%s%d/%s/%d' />\n",
4958 refresh_interval,
4959 XT_XTP_STR,
4960 XT_XTP_DL,
4961 dl_session_key,
4962 XT_XTP_DL_LIST);
4963 else
4964 ref = g_strdup("");
4966 body = g_strdup_printf("<div align='center'>"
4967 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4968 "</p><table><tr><th style='width: 60%%'>"
4969 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4970 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4972 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4973 body = xtp_page_dl_row(t, body, dl);
4974 n_dl++;
4977 /* message if no downloads in list */
4978 if (n_dl == 1) {
4979 tmp = body;
4980 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4981 " style='text-align: center'>"
4982 "No downloads</td></tr>\n", body);
4983 g_free(tmp);
4986 tmp = body;
4987 body = g_strdup_printf("%s</table></div>", body);
4988 g_free(tmp);
4990 page = get_html_page("Downloads", body, ref, 1);
4991 g_free(ref);
4992 g_free(body);
4995 * update all download manager tabs as the xtp session
4996 * key has now changed. No need to update the current tab.
4997 * Already did that above.
4999 update_download_tabs(t);
5001 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5002 g_free(page);
5004 return (0);
5008 search(struct tab *t, struct karg *args)
5010 gboolean d;
5012 if (t == NULL || args == NULL) {
5013 show_oops(NULL, "search invalid parameters");
5014 return (1);
5016 if (t->search_text == NULL) {
5017 if (global_search == NULL)
5018 return (XT_CB_PASSTHROUGH);
5019 else {
5020 t->search_text = g_strdup(global_search);
5021 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5022 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5026 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5027 t->tab_id, args->i, t->search_forward, t->search_text);
5029 switch (args->i) {
5030 case XT_SEARCH_NEXT:
5031 d = t->search_forward;
5032 break;
5033 case XT_SEARCH_PREV:
5034 d = !t->search_forward;
5035 break;
5036 default:
5037 return (XT_CB_PASSTHROUGH);
5040 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5042 return (XT_CB_HANDLED);
5045 struct settings_args {
5046 char **body;
5047 int i;
5050 void
5051 print_setting(struct settings *s, char *val, void *cb_args)
5053 char *tmp, *color;
5054 struct settings_args *sa = cb_args;
5056 if (sa == NULL)
5057 return;
5059 if (s->flags & XT_SF_RUNTIME)
5060 color = "#22cc22";
5061 else
5062 color = "#cccccc";
5064 tmp = *sa->body;
5065 *sa->body = g_strdup_printf(
5066 "%s\n<tr>"
5067 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5068 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5069 *sa->body,
5070 color,
5071 s->name,
5072 color,
5075 g_free(tmp);
5076 sa->i++;
5080 set_show(struct tab *t, struct karg *args)
5082 char *body, *page, *tmp;
5083 int i = 1;
5084 struct settings_args sa;
5086 bzero(&sa, sizeof sa);
5087 sa.body = &body;
5089 /* body */
5090 body = g_strdup_printf("<div align='center'><table><tr>"
5091 "<th align='left'>Setting</th>"
5092 "<th align='left'>Value</th></tr>\n");
5094 settings_walk(print_setting, &sa);
5095 i = sa.i;
5097 /* small message if there are none */
5098 if (i == 1) {
5099 tmp = body;
5100 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5101 "colspan='2'>No settings</td></tr>\n", body);
5102 g_free(tmp);
5105 tmp = body;
5106 body = g_strdup_printf("%s</table></div>", body);
5107 g_free(tmp);
5109 page = get_html_page("Settings", body, "", 0);
5111 g_free(body);
5113 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5115 g_free(page);
5117 return (XT_CB_PASSTHROUGH);
5121 set(struct tab *t, struct karg *args)
5123 char *p, *val;
5124 int i;
5126 if (args == NULL || args->s == NULL)
5127 return (set_show(t, args));
5129 /* strip spaces */
5130 p = g_strstrip(args->s);
5132 if (strlen(p) == 0)
5133 return (set_show(t, args));
5135 /* we got some sort of string */
5136 val = g_strrstr(p, "=");
5137 if (val) {
5138 *val++ = '\0';
5139 val = g_strchomp(val);
5140 p = g_strchomp(p);
5142 for (i = 0; i < LENGTH(rs); i++) {
5143 if (strcmp(rs[i].name, p))
5144 continue;
5146 if (rs[i].activate) {
5147 if (rs[i].activate(val))
5148 show_oops(t, "%s invalid value %s",
5149 p, val);
5150 else
5151 show_oops(t, ":set %s = %s", p, val);
5152 goto done;
5153 } else {
5154 show_oops(t, "not a runtime option: %s", p);
5155 goto done;
5158 show_oops(t, "unknown option: %s", p);
5159 } else {
5160 p = g_strchomp(p);
5162 for (i = 0; i < LENGTH(rs); i++) {
5163 if (strcmp(rs[i].name, p))
5164 continue;
5166 /* XXX this could use some cleanup */
5167 switch (rs[i].type) {
5168 case XT_S_INT:
5169 if (rs[i].ival)
5170 show_oops(t, "%s = %d",
5171 rs[i].name, *rs[i].ival);
5172 else if (rs[i].s && rs[i].s->get)
5173 show_oops(t, "%s = %s",
5174 rs[i].name,
5175 rs[i].s->get(&rs[i]));
5176 else if (rs[i].s && rs[i].s->get == NULL)
5177 show_oops(t, "%s = ...", rs[i].name);
5178 else
5179 show_oops(t, "%s = ", rs[i].name);
5180 break;
5181 case XT_S_FLOAT:
5182 if (rs[i].fval)
5183 show_oops(t, "%s = %f",
5184 rs[i].name, *rs[i].fval);
5185 else if (rs[i].s && rs[i].s->get)
5186 show_oops(t, "%s = %s",
5187 rs[i].name,
5188 rs[i].s->get(&rs[i]));
5189 else if (rs[i].s && rs[i].s->get == NULL)
5190 show_oops(t, "%s = ...", rs[i].name);
5191 else
5192 show_oops(t, "%s = ", rs[i].name);
5193 break;
5194 case XT_S_STR:
5195 if (rs[i].sval && *rs[i].sval)
5196 show_oops(t, "%s = %s",
5197 rs[i].name, *rs[i].sval);
5198 else if (rs[i].s && rs[i].s->get)
5199 show_oops(t, "%s = %s",
5200 rs[i].name,
5201 rs[i].s->get(&rs[i]));
5202 else if (rs[i].s && rs[i].s->get == NULL)
5203 show_oops(t, "%s = ...", rs[i].name);
5204 else
5205 show_oops(t, "%s = ", rs[i].name);
5206 break;
5207 default:
5208 show_oops(t, "unknown type for %s", rs[i].name);
5209 goto done;
5212 goto done;
5214 show_oops(t, "unknown option: %s", p);
5216 done:
5217 return (XT_CB_PASSTHROUGH);
5221 session_save(struct tab *t, char *filename)
5223 struct karg a;
5224 int rv = 1;
5226 if (strlen(filename) == 0)
5227 goto done;
5229 if (filename[0] == '.' || filename[0] == '/')
5230 goto done;
5232 a.s = filename;
5233 if (save_tabs(t, &a))
5234 goto done;
5235 strlcpy(named_session, filename, sizeof named_session);
5237 rv = 0;
5238 done:
5239 return (rv);
5243 session_open(struct tab *t, char *filename)
5245 struct karg a;
5246 int rv = 1;
5248 if (strlen(filename) == 0)
5249 goto done;
5251 if (filename[0] == '.' || filename[0] == '/')
5252 goto done;
5254 a.s = filename;
5255 a.i = XT_SES_CLOSETABS;
5256 if (open_tabs(t, &a))
5257 goto done;
5259 strlcpy(named_session, filename, sizeof named_session);
5261 rv = 0;
5262 done:
5263 return (rv);
5267 session_delete(struct tab *t, char *filename)
5269 char file[PATH_MAX];
5270 int rv = 1;
5272 if (strlen(filename) == 0)
5273 goto done;
5275 if (filename[0] == '.' || filename[0] == '/')
5276 goto done;
5278 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5279 if (unlink(file))
5280 goto done;
5282 if (!strcmp(filename, named_session))
5283 strlcpy(named_session, XT_SAVED_TABS_FILE,
5284 sizeof named_session);
5286 rv = 0;
5287 done:
5288 return (rv);
5292 session_cmd(struct tab *t, struct karg *args)
5294 char *filename = args->s;
5296 if (t == NULL)
5297 return (1);
5299 if (args->i & XT_SHOW)
5300 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5301 XT_SAVED_TABS_FILE : named_session);
5302 else if (args->i & XT_SAVE) {
5303 if (session_save(t, filename)) {
5304 show_oops(t, "Can't save session: %s",
5305 filename ? filename : "INVALID");
5306 goto done;
5308 } else if (args->i & XT_OPEN) {
5309 if (session_open(t, filename)) {
5310 show_oops(t, "Can't open session: %s",
5311 filename ? filename : "INVALID");
5312 goto done;
5314 } else if (args->i & XT_DELETE) {
5315 if (session_delete(t, filename)) {
5316 show_oops(t, "Can't delete session: %s",
5317 filename ? filename : "INVALID");
5318 goto done;
5321 done:
5322 return (XT_CB_PASSTHROUGH);
5326 * Make a hardcopy of the page
5329 print_page(struct tab *t, struct karg *args)
5331 WebKitWebFrame *frame;
5332 GtkPageSetup *ps;
5333 GtkPrintOperation *op;
5334 GtkPrintOperationAction action;
5335 GtkPrintOperationResult print_res;
5336 GError *g_err = NULL;
5337 int marg_l, marg_r, marg_t, marg_b;
5339 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5341 ps = gtk_page_setup_new();
5342 op = gtk_print_operation_new();
5343 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5344 frame = webkit_web_view_get_main_frame(t->wv);
5346 /* the default margins are too small, so we will bump them */
5347 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5348 XT_PRINT_EXTRA_MARGIN;
5349 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5350 XT_PRINT_EXTRA_MARGIN;
5351 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5352 XT_PRINT_EXTRA_MARGIN;
5353 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5354 XT_PRINT_EXTRA_MARGIN;
5356 /* set margins */
5357 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5358 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5359 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5360 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5362 gtk_print_operation_set_default_page_setup(op, ps);
5364 /* this appears to free 'op' and 'ps' */
5365 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5367 /* check it worked */
5368 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5369 show_oops(NULL, "can't print: %s", g_err->message);
5370 g_error_free (g_err);
5371 return (1);
5374 return (0);
5378 go_home(struct tab *t, struct karg *args)
5380 load_uri(t, home);
5381 return (0);
5385 restart(struct tab *t, struct karg *args)
5387 struct karg a;
5389 a.s = XT_RESTART_TABS_FILE;
5390 save_tabs(t, &a);
5391 execvp(start_argv[0], start_argv);
5392 /* NOTREACHED */
5394 return (0);
5397 #define CTRL GDK_CONTROL_MASK
5398 #define MOD1 GDK_MOD1_MASK
5399 #define SHFT GDK_SHIFT_MASK
5401 /* inherent to GTK not all keys will be caught at all times */
5402 /* XXX sort key bindings */
5403 struct key_binding {
5404 char *cmd;
5405 guint mask;
5406 guint use_in_entry;
5407 guint key;
5408 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5409 } keys[] = {
5410 { "cookiejar", MOD1, 0, GDK_j },
5411 { "downloadmgr", MOD1, 0, GDK_d },
5412 { "history", MOD1, 0, GDK_h },
5413 { "print", CTRL, 0, GDK_p },
5414 { "search", 0, 0, GDK_slash },
5415 { "searchb", 0, 0, GDK_question },
5416 { "statustoggle", CTRL, 0, GDK_n },
5417 { "command", 0, 0, GDK_colon },
5418 { "qa", CTRL, 0, GDK_q },
5419 { "restart", MOD1, 0, GDK_q },
5420 { "js toggle", CTRL, 0, GDK_j },
5421 { "cookie toggle", MOD1, 0, GDK_c },
5422 { "togglesrc", CTRL, 0, GDK_s },
5423 { "yankuri", 0, 0, GDK_y },
5424 { "pasteuricur", 0, 0, GDK_p },
5425 { "pasteurinew", 0, 0, GDK_P },
5426 { "toplevel toggle", 0, 0, GDK_F4 },
5427 { "help", 0, 0, GDK_F1 },
5428 { "run_script", MOD1, 0, GDK_r },
5430 /* search */
5431 { "searchnext", 0, 0, GDK_n },
5432 { "searchprevious", 0, 0, GDK_N },
5434 /* focus */
5435 { "focusaddress", 0, 0, GDK_F6 },
5436 { "focussearch", 0, 0, GDK_F7 },
5438 /* hinting */
5439 { "hinting", 0, 0, GDK_f },
5441 /* custom stylesheet */
5442 { "userstyle", 0, 0, GDK_i },
5444 /* navigation */
5445 { "goback", 0, 0, GDK_BackSpace },
5446 { "goback", MOD1, 0, GDK_Left },
5447 { "goforward", SHFT, 0, GDK_BackSpace },
5448 { "goforward", MOD1, 0, GDK_Right },
5449 { "reload", 0, 0, GDK_F5 },
5450 { "reload", CTRL, 0, GDK_r },
5451 { "reload", CTRL, 0, GDK_l },
5452 { "favorites", MOD1, 1, GDK_f },
5454 /* vertical movement */
5455 { "scrolldown", 0, 0, GDK_j },
5456 { "scrolldown", 0, 0, GDK_Down },
5457 { "scrollup", 0, 0, GDK_Up },
5458 { "scrollup", 0, 0, GDK_k },
5459 { "scrollbottom", 0, 0, GDK_G },
5460 { "scrollbottom", 0, 0, GDK_End },
5461 { "scrolltop", 0, 0, GDK_Home },
5462 { "scrollpagedown", 0, 0, GDK_space },
5463 { "scrollpagedown", CTRL, 0, GDK_f },
5464 { "scrollhalfdown", CTRL, 0, GDK_d },
5465 { "scrollpagedown", 0, 0, GDK_Page_Down },
5466 { "scrollpageup", 0, 0, GDK_Page_Up },
5467 { "scrollpageup", CTRL, 0, GDK_b },
5468 { "scrollhalfup", CTRL, 0, GDK_u },
5469 /* horizontal movement */
5470 { "scrollright", 0, 0, GDK_l },
5471 { "scrollright", 0, 0, GDK_Right },
5472 { "scrollleft", 0, 0, GDK_Left },
5473 { "scrollleft", 0, 0, GDK_h },
5474 { "scrollfarright", 0, 0, GDK_dollar },
5475 { "scrollfarleft", 0, 0, GDK_0 },
5477 /* tabs */
5478 { "tabnew", CTRL, 0, GDK_t },
5479 { "999tabnew", CTRL, 0, GDK_T },
5480 { "tabclose", CTRL, 1, GDK_w },
5481 { "tabundoclose", 0, 0, GDK_U },
5482 { "tabnext 1", CTRL, 0, GDK_1 },
5483 { "tabnext 2", CTRL, 0, GDK_2 },
5484 { "tabnext 3", CTRL, 0, GDK_3 },
5485 { "tabnext 4", CTRL, 0, GDK_4 },
5486 { "tabnext 5", CTRL, 0, GDK_5 },
5487 { "tabnext 6", CTRL, 0, GDK_6 },
5488 { "tabnext 7", CTRL, 0, GDK_7 },
5489 { "tabnext 8", CTRL, 0, GDK_8 },
5490 { "tabnext 9", CTRL, 0, GDK_9 },
5491 { "tabfirst", CTRL, 0, GDK_less },
5492 { "tablast", CTRL, 0, GDK_greater },
5493 { "tabprevious", CTRL, 0, GDK_Left },
5494 { "tabnext", CTRL, 0, GDK_Right },
5495 { "focusout", CTRL, 0, GDK_minus },
5496 { "focusin", CTRL, 0, GDK_plus },
5497 { "focusin", CTRL, 0, GDK_equal },
5498 { "focusreset", CTRL, 0, GDK_0 },
5500 /* command aliases (handy when -S flag is used) */
5501 { "promptopen", 0, 0, GDK_F9 },
5502 { "promptopencurrent", 0, 0, GDK_F10 },
5503 { "prompttabnew", 0, 0, GDK_F11 },
5504 { "prompttabnewcurrent",0, 0, GDK_F12 },
5506 TAILQ_HEAD(keybinding_list, key_binding);
5508 void
5509 walk_kb(struct settings *s,
5510 void (*cb)(struct settings *, char *, void *), void *cb_args)
5512 struct key_binding *k;
5513 char str[1024];
5515 if (s == NULL || cb == NULL) {
5516 show_oops(NULL, "walk_kb invalid parameters");
5517 return;
5520 TAILQ_FOREACH(k, &kbl, entry) {
5521 if (k->cmd == NULL)
5522 continue;
5523 str[0] = '\0';
5525 /* sanity */
5526 if (gdk_keyval_name(k->key) == NULL)
5527 continue;
5529 strlcat(str, k->cmd, sizeof str);
5530 strlcat(str, ",", sizeof str);
5532 if (k->mask & GDK_SHIFT_MASK)
5533 strlcat(str, "S-", sizeof str);
5534 if (k->mask & GDK_CONTROL_MASK)
5535 strlcat(str, "C-", sizeof str);
5536 if (k->mask & GDK_MOD1_MASK)
5537 strlcat(str, "M1-", sizeof str);
5538 if (k->mask & GDK_MOD2_MASK)
5539 strlcat(str, "M2-", sizeof str);
5540 if (k->mask & GDK_MOD3_MASK)
5541 strlcat(str, "M3-", sizeof str);
5542 if (k->mask & GDK_MOD4_MASK)
5543 strlcat(str, "M4-", sizeof str);
5544 if (k->mask & GDK_MOD5_MASK)
5545 strlcat(str, "M5-", sizeof str);
5547 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5548 cb(s, str, cb_args);
5552 void
5553 init_keybindings(void)
5555 int i;
5556 struct key_binding *k;
5558 for (i = 0; i < LENGTH(keys); i++) {
5559 k = g_malloc0(sizeof *k);
5560 k->cmd = keys[i].cmd;
5561 k->mask = keys[i].mask;
5562 k->use_in_entry = keys[i].use_in_entry;
5563 k->key = keys[i].key;
5564 TAILQ_INSERT_HEAD(&kbl, k, entry);
5566 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5567 k->cmd ? k->cmd : "unnamed key");
5571 void
5572 keybinding_clearall(void)
5574 struct key_binding *k, *next;
5576 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5577 next = TAILQ_NEXT(k, entry);
5578 if (k->cmd == NULL)
5579 continue;
5581 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5582 k->cmd ? k->cmd : "unnamed key");
5583 TAILQ_REMOVE(&kbl, k, entry);
5584 g_free(k);
5589 keybinding_add(char *cmd, char *key, int use_in_entry)
5591 struct key_binding *k;
5592 guint keyval, mask = 0;
5593 int i;
5595 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5597 /* Keys which are to be used in entry have been prefixed with an
5598 * exclamation mark. */
5599 if (use_in_entry)
5600 key++;
5602 /* find modifier keys */
5603 if (strstr(key, "S-"))
5604 mask |= GDK_SHIFT_MASK;
5605 if (strstr(key, "C-"))
5606 mask |= GDK_CONTROL_MASK;
5607 if (strstr(key, "M1-"))
5608 mask |= GDK_MOD1_MASK;
5609 if (strstr(key, "M2-"))
5610 mask |= GDK_MOD2_MASK;
5611 if (strstr(key, "M3-"))
5612 mask |= GDK_MOD3_MASK;
5613 if (strstr(key, "M4-"))
5614 mask |= GDK_MOD4_MASK;
5615 if (strstr(key, "M5-"))
5616 mask |= GDK_MOD5_MASK;
5618 /* find keyname */
5619 for (i = strlen(key) - 1; i > 0; i--)
5620 if (key[i] == '-')
5621 key = &key[i + 1];
5623 /* validate keyname */
5624 keyval = gdk_keyval_from_name(key);
5625 if (keyval == GDK_VoidSymbol) {
5626 warnx("invalid keybinding name %s", key);
5627 return (1);
5629 /* must run this test too, gtk+ doesn't handle 10 for example */
5630 if (gdk_keyval_name(keyval) == NULL) {
5631 warnx("invalid keybinding name %s", key);
5632 return (1);
5635 /* Remove eventual dupes. */
5636 TAILQ_FOREACH(k, &kbl, entry)
5637 if (k->key == keyval && k->mask == mask) {
5638 TAILQ_REMOVE(&kbl, k, entry);
5639 g_free(k);
5640 break;
5643 /* add keyname */
5644 k = g_malloc0(sizeof *k);
5645 k->cmd = g_strdup(cmd);
5646 k->mask = mask;
5647 k->use_in_entry = use_in_entry;
5648 k->key = keyval;
5650 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5651 k->cmd,
5652 k->mask,
5653 k->use_in_entry,
5654 k->key);
5655 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5656 k->cmd, gdk_keyval_name(keyval));
5658 TAILQ_INSERT_HEAD(&kbl, k, entry);
5660 return (0);
5664 add_kb(struct settings *s, char *entry)
5666 char *kb, *key;
5668 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5670 /* clearall is special */
5671 if (!strcmp(entry, "clearall")) {
5672 keybinding_clearall();
5673 return (0);
5676 kb = strstr(entry, ",");
5677 if (kb == NULL)
5678 return (1);
5679 *kb = '\0';
5680 key = kb + 1;
5682 return (keybinding_add(entry, key, key[0] == '!'));
5685 struct cmd {
5686 char *cmd;
5687 int level;
5688 int (*func)(struct tab *, struct karg *);
5689 int arg;
5690 int type;
5691 } cmds[] = {
5692 { "command", 0, command, ':', 0 },
5693 { "search", 0, command, '/', 0 },
5694 { "searchb", 0, command, '?', 0 },
5695 { "togglesrc", 0, toggle_src, 0, 0 },
5697 /* yanking and pasting */
5698 { "yankuri", 0, yank_uri, 0, 0 },
5699 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5700 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5701 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5703 /* search */
5704 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5705 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5707 /* focus */
5708 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5709 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5711 /* hinting */
5712 { "hinting", 0, hint, 0, 0 },
5714 /* custom stylesheet */
5715 { "userstyle", 0, userstyle, 0, 0 },
5717 /* navigation */
5718 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5719 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5720 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5722 /* vertical movement */
5723 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5724 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5725 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5726 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5727 { "1", 0, move, XT_MOVE_TOP, 0 },
5728 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5729 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5730 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5731 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5732 /* horizontal movement */
5733 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5734 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5735 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5736 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5738 { "favorites", 0, xtp_page_fl, 0, 0 },
5739 { "fav", 0, xtp_page_fl, 0, 0 },
5740 { "favadd", 0, add_favorite, 0, 0 },
5742 { "qall", 0, quit, 0, 0 },
5743 { "quitall", 0, quit, 0, 0 },
5744 { "w", 0, save_tabs, 0, 0 },
5745 { "wq", 0, save_tabs_and_quit, 0, 0 },
5746 { "help", 0, help, 0, 0 },
5747 { "about", 0, about, 0, 0 },
5748 { "stats", 0, stats, 0, 0 },
5749 { "version", 0, about, 0, 0 },
5751 /* js command */
5752 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5753 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5754 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5755 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5756 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5757 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5758 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5759 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5760 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5761 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5762 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5764 /* cookie command */
5765 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5766 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5767 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5768 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5769 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5770 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5771 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5772 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5773 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5774 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5775 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5777 /* toplevel (domain) command */
5778 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5779 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5781 /* cookie jar */
5782 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5784 /* cert command */
5785 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5786 { "save", 1, cert_cmd, XT_SAVE, 0 },
5787 { "show", 1, cert_cmd, XT_SHOW, 0 },
5789 { "ca", 0, ca_cmd, 0, 0 },
5790 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5791 { "dl", 0, xtp_page_dl, 0, 0 },
5792 { "h", 0, xtp_page_hl, 0, 0 },
5793 { "history", 0, xtp_page_hl, 0, 0 },
5794 { "home", 0, go_home, 0, 0 },
5795 { "restart", 0, restart, 0, 0 },
5796 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5797 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5798 { "statustoggle", 0, statustoggle, 0, 0 },
5799 { "run_script", 0, run_page_script, 0, XT_USERARG },
5801 { "print", 0, print_page, 0, 0 },
5803 /* tabs */
5804 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5805 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5806 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5807 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5808 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5809 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5810 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5811 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5812 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5813 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5814 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5815 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5816 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5817 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5818 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5819 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5820 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5821 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5822 { "buffers", 0, buffers, 0, 0 },
5823 { "ls", 0, buffers, 0, 0 },
5824 { "tabs", 0, buffers, 0, 0 },
5826 /* command aliases (handy when -S flag is used) */
5827 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5828 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5829 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5830 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5832 /* settings */
5833 { "set", 0, set, 0, XT_USERARG },
5835 { "fullscreen", 0, fullscreen, 0, 0 },
5836 { "f", 0, fullscreen, 0, 0 },
5838 /* sessions */
5839 { "session", 0, session_cmd, XT_SHOW, 0 },
5840 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5841 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5842 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5843 { "show", 1, session_cmd, XT_SHOW, 0 },
5846 struct {
5847 int index;
5848 int len;
5849 gchar *list[256];
5850 } cmd_status = {-1, 0};
5852 gboolean
5853 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5856 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5857 btn_down = 0;
5859 return (FALSE);
5862 gboolean
5863 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5865 struct karg a;
5867 hide_oops(t);
5868 hide_buffers(t);
5870 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5871 btn_down = 1;
5872 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5873 /* go backward */
5874 a.i = XT_NAV_BACK;
5875 navaction(t, &a);
5877 return (TRUE);
5878 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5879 /* go forward */
5880 a.i = XT_NAV_FORWARD;
5881 navaction(t, &a);
5883 return (TRUE);
5886 return (FALSE);
5889 gboolean
5890 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5892 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5894 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5895 delete_tab(t);
5897 return (FALSE);
5901 * cancel, remove, etc. downloads
5903 void
5904 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5906 struct download find, *d = NULL;
5908 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5910 /* some commands require a valid download id */
5911 if (cmd != XT_XTP_DL_LIST) {
5912 /* lookup download in question */
5913 find.id = id;
5914 d = RB_FIND(download_list, &downloads, &find);
5916 if (d == NULL) {
5917 show_oops(t, "%s: no such download", __func__);
5918 return;
5922 /* decide what to do */
5923 switch (cmd) {
5924 case XT_XTP_DL_CANCEL:
5925 webkit_download_cancel(d->download);
5926 break;
5927 case XT_XTP_DL_REMOVE:
5928 webkit_download_cancel(d->download); /* just incase */
5929 g_object_unref(d->download);
5930 RB_REMOVE(download_list, &downloads, d);
5931 break;
5932 case XT_XTP_DL_LIST:
5933 /* Nothing */
5934 break;
5935 default:
5936 show_oops(t, "%s: unknown command", __func__);
5937 break;
5939 xtp_page_dl(t, NULL);
5943 * Actions on history, only does one thing for now, but
5944 * we provide the function for future actions
5946 void
5947 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5949 struct history *h, *next;
5950 int i = 1;
5952 switch (cmd) {
5953 case XT_XTP_HL_REMOVE:
5954 /* walk backwards, as listed in reverse */
5955 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5956 next = RB_PREV(history_list, &hl, h);
5957 if (id == i) {
5958 RB_REMOVE(history_list, &hl, h);
5959 g_free((gpointer) h->title);
5960 g_free((gpointer) h->uri);
5961 g_free(h);
5962 break;
5964 i++;
5966 break;
5967 case XT_XTP_HL_LIST:
5968 /* Nothing - just xtp_page_hl() below */
5969 break;
5970 default:
5971 show_oops(t, "%s: unknown command", __func__);
5972 break;
5975 xtp_page_hl(t, NULL);
5978 /* remove a favorite */
5979 void
5980 remove_favorite(struct tab *t, int index)
5982 char file[PATH_MAX], *title, *uri = NULL;
5983 char *new_favs, *tmp;
5984 FILE *f;
5985 int i;
5986 size_t len, lineno;
5988 /* open favorites */
5989 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5991 if ((f = fopen(file, "r")) == NULL) {
5992 show_oops(t, "%s: can't open favorites: %s",
5993 __func__, strerror(errno));
5994 return;
5997 /* build a string which will become the new favroites file */
5998 new_favs = g_strdup("");
6000 for (i = 1;;) {
6001 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6002 if (feof(f) || ferror(f))
6003 break;
6004 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6005 if (len == 0) {
6006 free(title);
6007 title = NULL;
6008 continue;
6011 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6012 if (feof(f) || ferror(f)) {
6013 show_oops(t, "%s: can't parse favorites %s",
6014 __func__, strerror(errno));
6015 goto clean;
6019 /* as long as this isn't the one we are deleting add to file */
6020 if (i != index) {
6021 tmp = new_favs;
6022 new_favs = g_strdup_printf("%s%s\n%s\n",
6023 new_favs, title, uri);
6024 g_free(tmp);
6027 free(uri);
6028 uri = NULL;
6029 free(title);
6030 title = NULL;
6031 i++;
6033 fclose(f);
6035 /* write back new favorites file */
6036 if ((f = fopen(file, "w")) == NULL) {
6037 show_oops(t, "%s: can't open favorites: %s",
6038 __func__, strerror(errno));
6039 goto clean;
6042 fwrite(new_favs, strlen(new_favs), 1, f);
6043 fclose(f);
6045 clean:
6046 if (uri)
6047 free(uri);
6048 if (title)
6049 free(title);
6051 g_free(new_favs);
6054 void
6055 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6057 switch (cmd) {
6058 case XT_XTP_FL_LIST:
6059 /* nothing, just the below call to xtp_page_fl() */
6060 break;
6061 case XT_XTP_FL_REMOVE:
6062 remove_favorite(t, arg);
6063 break;
6064 default:
6065 show_oops(t, "%s: invalid favorites command", __func__);
6066 break;
6069 xtp_page_fl(t, NULL);
6072 void
6073 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6075 switch (cmd) {
6076 case XT_XTP_CL_LIST:
6077 /* nothing, just xtp_page_cl() */
6078 break;
6079 case XT_XTP_CL_REMOVE:
6080 remove_cookie(arg);
6081 break;
6082 default:
6083 show_oops(t, "%s: unknown cookie xtp command", __func__);
6084 break;
6087 xtp_page_cl(t, NULL);
6090 /* link an XTP class to it's session key and handler function */
6091 struct xtp_despatch {
6092 uint8_t xtp_class;
6093 char **session_key;
6094 void (*handle_func)(struct tab *, uint8_t, int);
6097 struct xtp_despatch xtp_despatches[] = {
6098 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6099 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6100 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6101 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6102 { XT_XTP_INVALID, NULL, NULL }
6106 * is the url xtp protocol? (xxxt://)
6107 * if so, parse and despatch correct bahvior
6110 parse_xtp_url(struct tab *t, const char *url)
6112 char *dup = NULL, *p, *last;
6113 uint8_t n_tokens = 0;
6114 char *tokens[4] = {NULL, NULL, NULL, ""};
6115 struct xtp_despatch *dsp, *dsp_match = NULL;
6116 uint8_t req_class;
6117 int ret = FALSE;
6120 * tokens array meaning:
6121 * tokens[0] = class
6122 * tokens[1] = session key
6123 * tokens[2] = action
6124 * tokens[3] = optional argument
6127 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6129 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6130 goto clean;
6132 dup = g_strdup(url + strlen(XT_XTP_STR));
6134 /* split out the url */
6135 for ((p = strtok_r(dup, "/", &last)); p;
6136 (p = strtok_r(NULL, "/", &last))) {
6137 if (n_tokens < 4)
6138 tokens[n_tokens++] = p;
6141 /* should be atleast three fields 'class/seskey/command/arg' */
6142 if (n_tokens < 3)
6143 goto clean;
6145 dsp = xtp_despatches;
6146 req_class = atoi(tokens[0]);
6147 while (dsp->xtp_class) {
6148 if (dsp->xtp_class == req_class) {
6149 dsp_match = dsp;
6150 break;
6152 dsp++;
6155 /* did we find one atall? */
6156 if (dsp_match == NULL) {
6157 show_oops(t, "%s: no matching xtp despatch found", __func__);
6158 goto clean;
6161 /* check session key and call despatch function */
6162 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6163 ret = TRUE; /* all is well, this was a valid xtp request */
6164 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6167 clean:
6168 if (dup)
6169 g_free(dup);
6171 return (ret);
6176 void
6177 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6179 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6181 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6183 if (t == NULL) {
6184 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6185 return;
6188 if (uri == NULL) {
6189 show_oops(t, "activate_uri_entry_cb no uri");
6190 return;
6193 uri += strspn(uri, "\t ");
6195 /* if xxxt:// treat specially */
6196 if (parse_xtp_url(t, uri))
6197 return;
6199 /* otherwise continue to load page normally */
6200 load_uri(t, (gchar *)uri);
6201 focus_webview(t);
6204 void
6205 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6207 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6208 char *newuri = NULL;
6209 gchar *enc_search;
6211 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6213 if (t == NULL) {
6214 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6215 return;
6218 if (search_string == NULL) {
6219 show_oops(t, "no search_string");
6220 return;
6223 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6225 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6226 newuri = g_strdup_printf(search_string, enc_search);
6227 g_free(enc_search);
6229 marks_clear(t);
6230 webkit_web_view_load_uri(t->wv, newuri);
6231 focus_webview(t);
6233 if (newuri)
6234 g_free(newuri);
6237 void
6238 check_and_set_cookie(const gchar *uri, struct tab *t)
6240 struct domain *d = NULL;
6241 int es = 0;
6243 if (uri == NULL || t == NULL)
6244 return;
6246 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6247 es = 0;
6248 else
6249 es = 1;
6251 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6252 es ? "enable" : "disable", uri);
6254 g_object_set(G_OBJECT(t->settings),
6255 "enable-html5-local-storage", es, (char *)NULL);
6256 webkit_web_view_set_settings(t->wv, t->settings);
6259 void
6260 check_and_set_js(const gchar *uri, struct tab *t)
6262 struct domain *d = NULL;
6263 int es = 0;
6265 if (uri == NULL || t == NULL)
6266 return;
6268 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6269 es = 0;
6270 else
6271 es = 1;
6273 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6274 es ? "enable" : "disable", uri);
6276 g_object_set(G_OBJECT(t->settings),
6277 "enable-scripts", es, (char *)NULL);
6278 g_object_set(G_OBJECT(t->settings),
6279 "javascript-can-open-windows-automatically", es, (char *)NULL);
6280 webkit_web_view_set_settings(t->wv, t->settings);
6282 button_set_stockid(t->js_toggle,
6283 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6286 gboolean
6287 color_address_bar(gpointer p)
6289 GdkColor color;
6290 struct tab *tt, *t = p;
6291 gchar *col_str = XT_COLOR_YELLOW;
6293 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6295 /* make sure t still exists */
6296 if (t == NULL)
6297 goto done;
6298 TAILQ_FOREACH(tt, &tabs, entry)
6299 if (t == tt)
6300 break;
6301 if (t != tt)
6302 goto done;
6304 switch (load_compare_cert(t, NULL)) {
6305 case CERT_LOCAL:
6306 col_str = XT_COLOR_BLUE;
6307 break;
6308 case CERT_TRUSTED:
6309 col_str = XT_COLOR_GREEN;
6310 break;
6311 case CERT_UNTRUSTED:
6312 col_str = XT_COLOR_YELLOW;
6313 break;
6314 case CERT_BAD:
6315 col_str = XT_COLOR_RED;
6316 break;
6319 gdk_color_parse(col_str, &color);
6320 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6322 if (!strcmp(col_str, XT_COLOR_WHITE))
6323 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6324 else
6325 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6327 col_str = NULL;
6328 done:
6329 return (FALSE /* kill thread */);
6332 void
6333 show_ca_status(struct tab *t, const char *uri)
6335 GdkColor color;
6336 gchar *col_str = XT_COLOR_WHITE;
6338 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6339 ssl_strict_certs, ssl_ca_file, uri);
6341 if (t == NULL)
6342 return;
6344 if (uri == NULL)
6345 goto done;
6346 if (ssl_ca_file == NULL) {
6347 if (g_str_has_prefix(uri, "http://"))
6348 goto done;
6349 if (g_str_has_prefix(uri, "https://")) {
6350 col_str = XT_COLOR_RED;
6351 goto done;
6353 return;
6355 if (g_str_has_prefix(uri, "http://") ||
6356 !g_str_has_prefix(uri, "https://"))
6357 goto done;
6359 /* thread the coloring of the address bar */
6360 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6361 color_address_bar, t, NULL);
6362 return;
6364 done:
6365 if (col_str) {
6366 gdk_color_parse(col_str, &color);
6367 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6369 if (!strcmp(col_str, XT_COLOR_WHITE))
6370 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6371 else
6372 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6376 void
6377 free_favicon(struct tab *t)
6379 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6380 __func__, t->icon_download, t->icon_request);
6382 if (t->icon_request)
6383 g_object_unref(t->icon_request);
6384 if (t->icon_dest_uri)
6385 g_free(t->icon_dest_uri);
6387 t->icon_request = NULL;
6388 t->icon_dest_uri = NULL;
6391 void
6392 xt_icon_from_name(struct tab *t, gchar *name)
6394 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6395 GTK_ENTRY_ICON_PRIMARY, "text-html");
6396 if (show_url == 0)
6397 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6398 GTK_ENTRY_ICON_PRIMARY, "text-html");
6399 else
6400 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6401 GTK_ENTRY_ICON_PRIMARY, NULL);
6404 void
6405 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6407 GdkPixbuf *pb_scaled;
6409 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6410 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6411 GDK_INTERP_BILINEAR);
6412 else
6413 pb_scaled = pb;
6415 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6416 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6417 if (show_url == 0)
6418 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6419 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6420 else
6421 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6422 GTK_ENTRY_ICON_PRIMARY, NULL);
6424 if (pb_scaled != pb)
6425 g_object_unref(pb_scaled);
6428 void
6429 xt_icon_from_file(struct tab *t, char *file)
6431 GdkPixbuf *pb;
6433 if (g_str_has_prefix(file, "file://"))
6434 file += strlen("file://");
6436 pb = gdk_pixbuf_new_from_file(file, NULL);
6437 if (pb) {
6438 xt_icon_from_pixbuf(t, pb);
6439 g_object_unref(pb);
6440 } else
6441 xt_icon_from_name(t, "text-html");
6444 gboolean
6445 is_valid_icon(char *file)
6447 gboolean valid = 0;
6448 const char *mime_type;
6449 GFileInfo *fi;
6450 GFile *gf;
6452 gf = g_file_new_for_path(file);
6453 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6454 NULL, NULL);
6455 mime_type = g_file_info_get_content_type(fi);
6456 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6457 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6458 g_strcmp0(mime_type, "image/png") == 0 ||
6459 g_strcmp0(mime_type, "image/gif") == 0 ||
6460 g_strcmp0(mime_type, "application/octet-stream") == 0;
6461 g_object_unref(fi);
6462 g_object_unref(gf);
6464 return (valid);
6467 void
6468 set_favicon_from_file(struct tab *t, char *file)
6470 struct stat sb;
6472 if (t == NULL || file == NULL)
6473 return;
6475 if (g_str_has_prefix(file, "file://"))
6476 file += strlen("file://");
6477 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6479 if (!stat(file, &sb)) {
6480 if (sb.st_size == 0 || !is_valid_icon(file)) {
6481 /* corrupt icon so trash it */
6482 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6483 __func__, file);
6484 unlink(file);
6485 /* no need to set icon to default here */
6486 return;
6489 xt_icon_from_file(t, file);
6492 void
6493 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6494 WebKitWebView *wv)
6496 WebKitDownloadStatus status = webkit_download_get_status(download);
6497 struct tab *tt = NULL, *t = NULL;
6500 * find the webview instead of passing in the tab as it could have been
6501 * deleted from underneath us.
6503 TAILQ_FOREACH(tt, &tabs, entry) {
6504 if (tt->wv == wv) {
6505 t = tt;
6506 break;
6509 if (t == NULL)
6510 return;
6512 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6513 __func__, t->tab_id, status);
6515 switch (status) {
6516 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6517 /* -1 */
6518 t->icon_download = NULL;
6519 free_favicon(t);
6520 break;
6521 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6522 /* 0 */
6523 break;
6524 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6525 /* 1 */
6526 break;
6527 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6528 /* 2 */
6529 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6530 __func__, t->tab_id);
6531 t->icon_download = NULL;
6532 free_favicon(t);
6533 break;
6534 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6535 /* 3 */
6537 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6538 __func__, t->icon_dest_uri);
6539 set_favicon_from_file(t, t->icon_dest_uri);
6540 /* these will be freed post callback */
6541 t->icon_request = NULL;
6542 t->icon_download = NULL;
6543 break;
6544 default:
6545 break;
6549 void
6550 abort_favicon_download(struct tab *t)
6552 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6554 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6555 if (t->icon_download) {
6556 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6557 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6558 webkit_download_cancel(t->icon_download);
6559 t->icon_download = NULL;
6560 } else
6561 free_favicon(t);
6562 #endif
6564 xt_icon_from_name(t, "text-html");
6567 void
6568 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6570 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6572 if (uri == NULL || t == NULL)
6573 return;
6575 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6576 /* take icon from WebKitIconDatabase */
6577 GdkPixbuf *pb;
6579 pb = webkit_web_view_get_icon_pixbuf(wv);
6580 if (pb) {
6581 xt_icon_from_pixbuf(t, pb);
6582 g_object_unref(pb);
6583 } else
6584 xt_icon_from_name(t, "text-html");
6585 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6586 /* download icon to cache dir */
6587 gchar *name_hash, file[PATH_MAX];
6588 struct stat sb;
6590 if (t->icon_request) {
6591 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6592 return;
6595 /* check to see if we got the icon in cache */
6596 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6597 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6598 g_free(name_hash);
6600 if (!stat(file, &sb)) {
6601 if (sb.st_size > 0) {
6602 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6603 __func__, file);
6604 set_favicon_from_file(t, file);
6605 return;
6608 /* corrupt icon so trash it */
6609 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6610 __func__, file);
6611 unlink(file);
6614 /* create download for icon */
6615 t->icon_request = webkit_network_request_new(uri);
6616 if (t->icon_request == NULL) {
6617 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6618 __func__, uri);
6619 return;
6622 t->icon_download = webkit_download_new(t->icon_request);
6623 if (t->icon_download == NULL)
6624 return;
6626 /* we have to free icon_dest_uri later */
6627 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6628 webkit_download_set_destination_uri(t->icon_download,
6629 t->icon_dest_uri);
6631 if (webkit_download_get_status(t->icon_download) ==
6632 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6633 g_object_unref(t->icon_request);
6634 g_free(t->icon_dest_uri);
6635 t->icon_request = NULL;
6636 t->icon_dest_uri = NULL;
6637 return;
6640 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6641 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6643 webkit_download_start(t->icon_download);
6644 #endif
6647 void
6648 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6650 const gchar *uri = NULL, *title = NULL;
6651 struct history *h, find;
6652 struct karg a;
6653 GdkColor color;
6655 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6656 webkit_web_view_get_load_status(wview),
6657 get_uri(t) ? get_uri(t) : "NOTHING");
6659 if (t == NULL) {
6660 show_oops(NULL, "notify_load_status_cb invalid parameters");
6661 return;
6664 switch (webkit_web_view_get_load_status(wview)) {
6665 case WEBKIT_LOAD_PROVISIONAL:
6666 /* 0 */
6667 abort_favicon_download(t);
6668 #if GTK_CHECK_VERSION(2, 20, 0)
6669 gtk_widget_show(t->spinner);
6670 gtk_spinner_start(GTK_SPINNER(t->spinner));
6671 #endif
6672 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6674 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6676 /* assume we are a new address */
6677 gdk_color_parse("white", &color);
6678 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6679 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6681 /* take focus if we are visible */
6682 focus_webview(t);
6683 t->focus_wv = 1;
6685 break;
6687 case WEBKIT_LOAD_COMMITTED:
6688 /* 1 */
6689 uri = get_uri(t);
6690 if (uri == NULL)
6691 return;
6692 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6694 if (t->status) {
6695 g_free(t->status);
6696 t->status = NULL;
6698 set_status(t, (char *)uri, XT_STATUS_LOADING);
6700 /* check if js white listing is enabled */
6701 if (enable_cookie_whitelist)
6702 check_and_set_cookie(uri, t);
6703 if (enable_js_whitelist)
6704 check_and_set_js(uri, t);
6706 if (t->styled)
6707 apply_style(t);
6710 /* we know enough to autosave the session */
6711 if (session_autosave) {
6712 a.s = NULL;
6713 save_tabs(t, &a);
6716 show_ca_status(t, uri);
6717 break;
6719 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6720 /* 3 */
6721 break;
6723 case WEBKIT_LOAD_FINISHED:
6724 /* 2 */
6725 uri = get_uri(t);
6726 if (uri == NULL)
6727 return;
6729 if (!strncmp(uri, "http://", strlen("http://")) ||
6730 !strncmp(uri, "https://", strlen("https://")) ||
6731 !strncmp(uri, "file://", strlen("file://"))) {
6732 find.uri = uri;
6733 h = RB_FIND(history_list, &hl, &find);
6734 if (!h) {
6735 title = get_title(t, FALSE);
6736 h = g_malloc(sizeof *h);
6737 h->uri = g_strdup(uri);
6738 h->title = g_strdup(title);
6739 RB_INSERT(history_list, &hl, h);
6740 completion_add_uri(h->uri);
6741 update_history_tabs(NULL);
6745 set_status(t, (char *)uri, XT_STATUS_URI);
6746 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6747 case WEBKIT_LOAD_FAILED:
6748 /* 4 */
6749 #endif
6750 #if GTK_CHECK_VERSION(2, 20, 0)
6751 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6752 gtk_widget_hide(t->spinner);
6753 #endif
6754 default:
6755 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6756 break;
6759 if (t->item)
6760 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6761 else
6762 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6763 can_go_back_for_real(t));
6765 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6766 can_go_forward_for_real(t));
6769 #if 0
6770 gboolean
6771 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6772 gchar *uri, gpointer web_error,struct tab *t)
6775 * XXX this function is wrong
6776 * it overwrites perfectly good urls with garbage on load errors
6777 * those happen often when popups fail to resolve dns
6779 if (t->tmp_uri)
6780 g_free(t->tmp_uri);
6781 t->tmp_uri = g_strdup(uri);
6782 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6783 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6784 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6785 set_status(t, uri, XT_STATUS_NOTHING);
6787 return (FALSE);
6789 #endif
6791 void
6792 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6794 const gchar *title = NULL, *win_title = NULL;
6796 title = get_title(t, FALSE);
6797 win_title = get_title(t, TRUE);
6798 gtk_label_set_text(GTK_LABEL(t->label), title);
6799 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6800 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6801 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6804 void
6805 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6807 run_script(t, JS_HINTING);
6810 void
6811 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6813 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6814 progress == 100 ? 0 : (double)progress / 100);
6815 if (show_url == 0) {
6816 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6817 progress == 100 ? 0 : (double)progress / 100);
6820 update_statusbar_position(NULL, NULL);
6824 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6825 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6826 WebKitWebPolicyDecision *pd, struct tab *t)
6828 char *uri;
6829 WebKitWebNavigationReason reason;
6830 struct domain *d = NULL;
6832 if (t == NULL) {
6833 show_oops(NULL, "webview_npd_cb invalid parameters");
6834 return (FALSE);
6837 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6838 t->ctrl_click,
6839 webkit_network_request_get_uri(request));
6841 uri = (char *)webkit_network_request_get_uri(request);
6843 /* if this is an xtp url, we don't load anything else */
6844 if (parse_xtp_url(t, uri))
6845 return (TRUE);
6847 if (t->ctrl_click) {
6848 t->ctrl_click = 0;
6849 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6850 webkit_web_policy_decision_ignore(pd);
6851 return (TRUE); /* we made the decission */
6855 * This is a little hairy but it comes down to this:
6856 * when we run in whitelist mode we have to assist the browser in
6857 * opening the URL that it would have opened in a new tab.
6859 reason = webkit_web_navigation_action_get_reason(na);
6860 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6861 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6862 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6863 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6864 load_uri(t, uri);
6865 webkit_web_policy_decision_use(pd);
6866 return (TRUE); /* we made the decision */
6869 return (FALSE);
6872 WebKitWebView *
6873 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6875 struct tab *tt;
6876 struct domain *d = NULL;
6877 const gchar *uri;
6878 WebKitWebView *webview = NULL;
6880 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6881 webkit_web_view_get_uri(wv));
6883 if (tabless) {
6884 /* open in current tab */
6885 webview = t->wv;
6886 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6887 uri = webkit_web_view_get_uri(wv);
6888 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6889 return (NULL);
6891 tt = create_new_tab(NULL, NULL, 1, -1);
6892 webview = tt->wv;
6893 } else if (enable_scripts == 1) {
6894 tt = create_new_tab(NULL, NULL, 1, -1);
6895 webview = tt->wv;
6898 return (webview);
6901 gboolean
6902 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6904 const gchar *uri;
6905 struct domain *d = NULL;
6907 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6909 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6910 uri = webkit_web_view_get_uri(wv);
6911 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6912 return (FALSE);
6914 delete_tab(t);
6915 } else if (enable_scripts == 1)
6916 delete_tab(t);
6918 return (TRUE);
6922 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6924 /* we can not eat the event without throwing gtk off so defer it */
6926 /* catch middle click */
6927 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6928 t->ctrl_click = 1;
6929 goto done;
6932 /* catch ctrl click */
6933 if (e->type == GDK_BUTTON_RELEASE &&
6934 CLEAN(e->state) == GDK_CONTROL_MASK)
6935 t->ctrl_click = 1;
6936 else
6937 t->ctrl_click = 0;
6938 done:
6939 return (XT_CB_PASSTHROUGH);
6943 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6945 struct mime_type *m;
6947 m = find_mime_type(mime_type);
6948 if (m == NULL)
6949 return (1);
6950 if (m->mt_download)
6951 return (1);
6953 switch (fork()) {
6954 case -1:
6955 show_oops(t, "can't fork mime handler");
6956 return (1);
6957 /* NOTREACHED */
6958 case 0:
6959 break;
6960 default:
6961 return (0);
6964 /* child */
6965 execlp(m->mt_action, m->mt_action,
6966 webkit_network_request_get_uri(request), (void *)NULL);
6968 _exit(0);
6970 /* NOTREACHED */
6971 return (0);
6974 const gchar *
6975 get_mime_type(char *file)
6977 const char *mime_type;
6978 GFileInfo *fi;
6979 GFile *gf;
6981 if (g_str_has_prefix(file, "file://"))
6982 file += strlen("file://");
6984 gf = g_file_new_for_path(file);
6985 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6986 NULL, NULL);
6987 mime_type = g_file_info_get_content_type(fi);
6988 g_object_unref(fi);
6989 g_object_unref(gf);
6991 return (mime_type);
6995 run_download_mimehandler(char *mime_type, char *file)
6997 struct mime_type *m;
6999 m = find_mime_type(mime_type);
7000 if (m == NULL)
7001 return (1);
7003 switch (fork()) {
7004 case -1:
7005 show_oops(NULL, "can't fork download mime handler");
7006 return (1);
7007 /* NOTREACHED */
7008 case 0:
7009 break;
7010 default:
7011 return (0);
7014 /* child */
7015 if (g_str_has_prefix(file, "file://"))
7016 file += strlen("file://");
7017 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7019 _exit(0);
7021 /* NOTREACHED */
7022 return (0);
7025 void
7026 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7027 WebKitWebView *wv)
7029 WebKitDownloadStatus status;
7030 const gchar *file = NULL, *mime = NULL;
7032 if (download == NULL)
7033 return;
7034 status = webkit_download_get_status(download);
7035 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7036 return;
7038 file = webkit_download_get_destination_uri(download);
7039 if (file == NULL)
7040 return;
7041 mime = get_mime_type((char *)file);
7042 if (mime == NULL)
7043 return;
7045 run_download_mimehandler((char *)mime, (char *)file);
7049 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7050 WebKitNetworkRequest *request, char *mime_type,
7051 WebKitWebPolicyDecision *decision, struct tab *t)
7053 if (t == NULL) {
7054 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7055 return (FALSE);
7058 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7059 t->tab_id, mime_type);
7061 if (run_mimehandler(t, mime_type, request) == 0) {
7062 webkit_web_policy_decision_ignore(decision);
7063 focus_webview(t);
7064 return (TRUE);
7067 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7068 webkit_web_policy_decision_download(decision);
7069 return (TRUE);
7072 return (FALSE);
7076 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7077 struct tab *t)
7079 struct stat sb;
7080 const gchar *suggested_name;
7081 gchar *filename = NULL;
7082 char *uri = NULL;
7083 struct download *download_entry;
7084 int i, ret = TRUE;
7086 if (wk_download == NULL || t == NULL) {
7087 show_oops(NULL, "%s invalid parameters", __func__);
7088 return (FALSE);
7091 suggested_name = webkit_download_get_suggested_filename(wk_download);
7092 if (suggested_name == NULL)
7093 return (FALSE); /* abort download */
7095 i = 0;
7096 do {
7097 if (filename) {
7098 g_free(filename);
7099 filename = NULL;
7101 if (i) {
7102 g_free(uri);
7103 uri = NULL;
7104 filename = g_strdup_printf("%d%s", i, suggested_name);
7106 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7107 filename : suggested_name);
7108 i++;
7109 } while (!stat(uri + strlen("file://"), &sb));
7111 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7112 "local %s\n", __func__, t->tab_id, filename, uri);
7114 webkit_download_set_destination_uri(wk_download, uri);
7116 if (webkit_download_get_status(wk_download) ==
7117 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7118 show_oops(t, "%s: download failed to start", __func__);
7119 ret = FALSE;
7120 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7121 } else {
7122 /* connect "download first" mime handler */
7123 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7124 G_CALLBACK(download_status_changed_cb), NULL);
7126 download_entry = g_malloc(sizeof(struct download));
7127 download_entry->download = wk_download;
7128 download_entry->tab = t;
7129 download_entry->id = next_download_id++;
7130 RB_INSERT(download_list, &downloads, download_entry);
7131 /* get from history */
7132 g_object_ref(wk_download);
7133 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7134 show_oops(t, "Download of '%s' started...",
7135 basename((char *)webkit_download_get_destination_uri(wk_download)));
7138 if (uri)
7139 g_free(uri);
7141 if (filename)
7142 g_free(filename);
7144 /* sync other download manager tabs */
7145 update_download_tabs(NULL);
7148 * NOTE: never redirect/render the current tab before this
7149 * function returns. This will cause the download to never start.
7151 return (ret); /* start download */
7154 void
7155 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7157 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7159 if (t == NULL) {
7160 show_oops(NULL, "webview_hover_cb");
7161 return;
7164 if (uri)
7165 set_status(t, uri, XT_STATUS_LINK);
7166 else {
7167 if (t->status)
7168 set_status(t, t->status, XT_STATUS_NOTHING);
7173 mark(struct tab *t, struct karg *arg)
7175 char mark;
7176 int index;
7178 mark = arg->s[1];
7179 if ((index = marktoindex(mark)) == -1)
7180 return -1;
7182 if (arg->i == XT_MARK_SET)
7183 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7184 else if (arg->i == XT_MARK_GOTO) {
7185 if (t->mark[index] == XT_INVALID_MARK) {
7186 show_oops(t, "mark '%c' does not exist", mark);
7187 return -1;
7189 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7190 something changes the document size */
7191 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7194 return 0;
7197 void
7198 marks_clear(struct tab *t)
7200 int i;
7202 for (i = 0; i < LENGTH(t->mark); i++)
7203 t->mark[i] = XT_INVALID_MARK;
7207 qmarks_load(void)
7209 char file[PATH_MAX];
7210 char *line = NULL, *p, mark;
7211 int index, i;
7212 FILE *f;
7213 size_t linelen;
7215 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7216 if ((f = fopen(file, "r+")) == NULL) {
7217 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7218 return (1);
7221 for (i = 1; ; i++) {
7222 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7223 if (feof(f) || ferror(f))
7224 break;
7225 if (strlen(line) == 0 || line[0] == '#') {
7226 free(line);
7227 line = NULL;
7228 continue;
7231 p = strtok(line, " \t");
7233 if (p == NULL || strlen(p) != 1 ||
7234 (index = marktoindex(*p)) == -1) {
7235 warnx("corrupt quickmarks file, line %d", i);
7236 break;
7239 mark = *p;
7240 p = strtok(NULL, " \t");
7241 if (qmarks[index] != NULL)
7242 g_free(qmarks[index]);
7243 qmarks[index] = g_strdup(p);
7246 fclose(f);
7248 return (0);
7252 qmarks_save(void)
7254 char file[PATH_MAX];
7255 int i;
7256 FILE *f;
7258 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7259 if ((f = fopen(file, "r+")) == NULL) {
7260 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7261 return (1);
7264 for (i = 0; i < XT_NOMARKS; i++)
7265 if (qmarks[i] != NULL)
7266 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7268 fclose(f);
7270 return (0);
7274 qmark(struct tab *t, struct karg *arg)
7276 char mark;
7277 int index;
7279 mark = arg->s[strlen(arg->s)-1];
7280 index = marktoindex(mark);
7281 if (index == -1)
7282 return (-1);
7284 switch (arg->i) {
7285 case XT_QMARK_SET:
7286 if (qmarks[index] != NULL)
7287 g_free(qmarks[index]);
7289 qmarks_load(); /* sync if multiple instances */
7290 qmarks[index] = g_strdup(get_uri(t));
7291 qmarks_save();
7292 break;
7293 case XT_QMARK_OPEN:
7294 if (qmarks[index] != NULL)
7295 load_uri(t, qmarks[index]);
7296 else {
7297 show_oops(t, "quickmark \"%c\" does not exist",
7298 mark);
7299 return (-1);
7301 break;
7302 case XT_QMARK_TAB:
7303 if (qmarks[index] != NULL)
7304 create_new_tab(qmarks[index], NULL, 1, -1);
7305 else {
7306 show_oops(t, "quickmark \"%c\" does not exist",
7307 mark);
7308 return (-1);
7310 break;
7313 return (0);
7317 go_up(struct tab *t, struct karg *args)
7319 int levels;
7320 char *uri;
7321 char *tmp;
7323 levels = atoi(args->s);
7324 if (levels == 0)
7325 levels = 1;
7327 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7328 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7329 return 1;
7330 tmp += strlen(XT_PROTO_DELIM);
7332 /* if an uri starts with a slash, leave it alone (for file:///) */
7333 if (tmp[0] == '/')
7334 tmp++;
7336 while (levels--) {
7337 char *p;
7339 p = strrchr(tmp, '/');
7340 if (p != NULL)
7341 *p = '\0';
7342 else
7343 break;
7346 load_uri(t, uri);
7347 g_free(uri);
7349 return (0);
7353 gototab(struct tab *t, struct karg *args)
7355 int tab;
7356 struct karg arg = {0, NULL, -1};
7358 tab = atoi(args->s);
7360 arg.i = XT_TAB_NEXT;
7361 arg.precount = tab;
7363 movetab(t, &arg);
7365 return (0);
7369 zoom_amount(struct tab *t, struct karg *arg)
7371 struct karg narg = {0, NULL, -1};
7373 narg.i = atoi(arg->s);
7374 resizetab(t, &narg);
7376 return 0;
7380 flip_colon(struct tab *t, struct karg *arg)
7382 struct karg narg = {0, NULL, -1};
7383 char *p;
7385 if (t == NULL || arg == NULL)
7386 return (1);
7388 p = strstr(arg->s, ":");
7389 if (p == NULL)
7390 return (1);
7391 *p = '\0';
7393 narg.i = ':';
7394 narg.s = arg->s;
7395 command(t, &narg);
7397 return (0);
7400 /* buffer commands receive the regex that triggered them in arg.s */
7401 char bcmd[XT_BUFCMD_SZ];
7402 struct buffercmd {
7403 char *regex;
7404 int precount;
7405 #define XT_PRE_NO (0)
7406 #define XT_PRE_YES (1)
7407 #define XT_PRE_MAYBE (2)
7408 char *cmd;
7409 int (*func)(struct tab *, struct karg *);
7410 int arg;
7411 regex_t cregex;
7412 } buffercmds[] = {
7413 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7414 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7415 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7416 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7417 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7418 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7419 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7420 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7421 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7422 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7423 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7424 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7425 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7426 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7427 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7428 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7429 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7430 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7433 void
7434 buffercmd_init(void)
7436 int i;
7438 for (i = 0; i < LENGTH(buffercmds); i++)
7439 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7440 REG_EXTENDED | REG_NOSUB))
7441 startpage_add("invalid buffercmd regex %s",
7442 buffercmds[i].regex);
7445 void
7446 buffercmd_abort(struct tab *t)
7448 int i;
7450 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7451 for (i = 0; i < LENGTH(bcmd); i++)
7452 bcmd[i] = '\0';
7454 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7455 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7458 void
7459 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7461 struct karg arg = {0, NULL, -1};
7463 arg.i = cmd->arg;
7464 arg.s = g_strdup(bcmd);
7466 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7467 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7468 cmd->func(t, &arg);
7470 if (arg.s)
7471 g_free(arg.s);
7473 buffercmd_abort(t);
7476 gboolean
7477 buffercmd_addkey(struct tab *t, guint keyval)
7479 int i, c, match ;
7480 char s[XT_BUFCMD_SZ];
7482 if (keyval == GDK_Escape) {
7483 buffercmd_abort(t);
7484 return (XT_CB_HANDLED);
7487 /* key with modifier or non-ascii character */
7488 if (!isascii(keyval))
7489 return (XT_CB_PASSTHROUGH);
7491 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7492 "to buffer \"%s\"\n", keyval, bcmd);
7494 for (i = 0; i < LENGTH(bcmd); i++)
7495 if (bcmd[i] == '\0') {
7496 bcmd[i] = keyval;
7497 break;
7500 /* buffer full, ignore input */
7501 if (i >= LENGTH(bcmd) -1) {
7502 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7503 buffercmd_abort(t);
7504 return (XT_CB_HANDLED);
7507 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7509 /* find exact match */
7510 for (i = 0; i < LENGTH(buffercmds); i++)
7511 if (regexec(&buffercmds[i].cregex, bcmd,
7512 (size_t) 0, NULL, 0) == 0) {
7513 buffercmd_execute(t, &buffercmds[i]);
7514 goto done;
7517 /* find non exact matches to see if we need to abort ot not */
7518 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7519 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7520 c = -1;
7521 s[0] = '\0';
7522 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7523 if (isdigit(bcmd[0])) {
7524 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7525 continue;
7526 } else {
7527 c = 0;
7528 if (sscanf(bcmd, "%s", s) == 0)
7529 continue;
7531 } else if (buffercmds[i].precount == XT_PRE_YES) {
7532 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7533 continue;
7534 } else {
7535 if (sscanf(bcmd, "%s", s) == 0)
7536 continue;
7538 if (c == -1 && buffercmds[i].precount)
7539 continue;
7540 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7541 match++;
7543 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7544 i, match, buffercmds[i].cmd, c, s);
7546 if (match == 0) {
7547 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7548 buffercmd_abort(t);
7551 done:
7552 return (XT_CB_HANDLED);
7555 gboolean
7556 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7558 struct key_binding *k;
7560 /* handle keybindings if buffercmd is empty.
7561 if not empty, allow commands like C-n */
7562 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7563 TAILQ_FOREACH(k, &kbl, entry)
7564 if (e->keyval == k->key
7565 && (entry ? k->use_in_entry : 1)) {
7566 if (k->mask == 0) {
7567 if ((e->state & (CTRL | MOD1)) == 0)
7568 return (cmd_execute(t, k->cmd));
7569 } else if ((e->state & k->mask) == k->mask) {
7570 return (cmd_execute(t, k->cmd));
7574 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7575 return buffercmd_addkey(t, e->keyval);
7577 return (XT_CB_PASSTHROUGH);
7581 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7583 char s[2], buf[128];
7584 const char *errstr = NULL;
7586 /* don't use w directly; use t->whatever instead */
7588 if (t == NULL) {
7589 show_oops(NULL, "wv_keypress_after_cb");
7590 return (XT_CB_PASSTHROUGH);
7593 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7594 e->keyval, e->state, t);
7596 if (t->hints_on) {
7597 /* ESC */
7598 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7599 disable_hints(t);
7600 return (XT_CB_HANDLED);
7603 /* RETURN */
7604 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7605 if (errstr) {
7606 /* we have a string */
7607 } else {
7608 /* we have a number */
7609 snprintf(buf, sizeof buf,
7610 "vimprobable_fire(%s)", t->hint_num);
7611 run_script(t, buf);
7613 disable_hints(t);
7616 /* BACKSPACE */
7617 /* XXX unfuck this */
7618 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7619 if (t->hint_mode == XT_HINT_NUMERICAL) {
7620 /* last input was numerical */
7621 int l;
7622 l = strlen(t->hint_num);
7623 if (l > 0) {
7624 l--;
7625 if (l == 0) {
7626 disable_hints(t);
7627 enable_hints(t);
7628 } else {
7629 t->hint_num[l] = '\0';
7630 goto num;
7633 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7634 /* last input was alphanumerical */
7635 int l;
7636 l = strlen(t->hint_buf);
7637 if (l > 0) {
7638 l--;
7639 if (l == 0) {
7640 disable_hints(t);
7641 enable_hints(t);
7642 } else {
7643 t->hint_buf[l] = '\0';
7644 goto anum;
7647 } else {
7648 /* bogus */
7649 disable_hints(t);
7653 /* numerical input */
7654 if (CLEAN(e->state) == 0 &&
7655 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7656 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7657 snprintf(s, sizeof s, "%c", e->keyval);
7658 strlcat(t->hint_num, s, sizeof t->hint_num);
7659 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7660 t->hint_num);
7661 num:
7662 if (errstr) {
7663 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7664 "invalid link number\n");
7665 disable_hints(t);
7666 } else {
7667 snprintf(buf, sizeof buf,
7668 "vimprobable_update_hints(%s)",
7669 t->hint_num);
7670 t->hint_mode = XT_HINT_NUMERICAL;
7671 run_script(t, buf);
7674 /* empty the counter buffer */
7675 bzero(t->hint_buf, sizeof t->hint_buf);
7676 return (XT_CB_HANDLED);
7679 /* alphanumerical input */
7680 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7681 e->keyval <= GDK_z) ||
7682 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7683 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7684 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7685 e->keyval <= GDK_9) ||
7686 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7687 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7688 snprintf(s, sizeof s, "%c", e->keyval);
7689 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7690 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7691 " %s\n", t->hint_buf);
7692 anum:
7693 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7694 run_script(t, buf);
7696 snprintf(buf, sizeof buf,
7697 "vimprobable_show_hints('%s')", t->hint_buf);
7698 t->hint_mode = XT_HINT_ALPHANUM;
7699 run_script(t, buf);
7701 /* empty the counter buffer */
7702 bzero(t->hint_num, sizeof t->hint_num);
7703 return (XT_CB_HANDLED);
7706 return (XT_CB_HANDLED);
7707 } else {
7708 /* prefix input*/
7709 snprintf(s, sizeof s, "%c", e->keyval);
7710 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7711 cmd_prefix = 10 * cmd_prefix + atoi(s);
7714 return (handle_keypress(t, e, 0));
7718 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7720 hide_oops(t);
7722 /* Hide buffers, if they are visible, with escape. */
7723 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7724 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7725 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7726 hide_buffers(t);
7727 return (XT_CB_HANDLED);
7730 return (XT_CB_PASSTHROUGH);
7733 gboolean
7734 search_continue(struct tab *t)
7736 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7737 gboolean rv = FALSE;
7739 if (c[0] == ':')
7740 goto done;
7741 if (strlen(c) == 1) {
7742 webkit_web_view_unmark_text_matches(t->wv);
7743 goto done;
7746 if (c[0] == '/')
7747 t->search_forward = TRUE;
7748 else if (c[0] == '?')
7749 t->search_forward = FALSE;
7750 else
7751 goto done;
7753 rv = TRUE;
7754 done:
7755 return (rv);
7758 gboolean
7759 search_cb(struct tab *t)
7761 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7762 GdkColor color;
7764 if (search_continue(t) == FALSE)
7765 goto done;
7767 /* search */
7768 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7769 TRUE) == FALSE) {
7770 /* not found, mark red */
7771 gdk_color_parse(XT_COLOR_RED, &color);
7772 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7773 /* unmark and remove selection */
7774 webkit_web_view_unmark_text_matches(t->wv);
7775 /* my kingdom for a way to unselect text in webview */
7776 } else {
7777 /* found, highlight all */
7778 webkit_web_view_unmark_text_matches(t->wv);
7779 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7780 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7781 gdk_color_parse(XT_COLOR_WHITE, &color);
7782 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7784 done:
7785 t->search_id = 0;
7786 return (FALSE);
7790 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7792 const gchar *c = gtk_entry_get_text(w);
7794 if (t == NULL) {
7795 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7796 return (XT_CB_PASSTHROUGH);
7799 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7800 e->keyval, e->state, t);
7802 if (search_continue(t) == FALSE)
7803 goto done;
7805 /* if search length is > 4 then no longer play timeout games */
7806 if (strlen(c) > 4) {
7807 if (t->search_id) {
7808 g_source_remove(t->search_id);
7809 t->search_id = 0;
7811 search_cb(t);
7812 goto done;
7815 /* reestablish a new timer if the user types fast */
7816 if (t->search_id)
7817 g_source_remove(t->search_id);
7818 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7820 done:
7821 return (XT_CB_PASSTHROUGH);
7824 gboolean
7825 match_uri(const gchar *uri, const gchar *key) {
7826 gchar *voffset;
7827 size_t len;
7828 gboolean match = FALSE;
7830 len = strlen(key);
7832 if (!strncmp(key, uri, len))
7833 match = TRUE;
7834 else {
7835 voffset = strstr(uri, "/") + 2;
7836 if (!strncmp(key, voffset, len))
7837 match = TRUE;
7838 else if (g_str_has_prefix(voffset, "www.")) {
7839 voffset = voffset + strlen("www.");
7840 if (!strncmp(key, voffset, len))
7841 match = TRUE;
7845 return (match);
7848 void
7849 cmd_getlist(int id, char *key)
7851 int i, dep, c = 0;
7852 struct history *h;
7854 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7855 RB_FOREACH_REVERSE(h, history_list, &hl)
7856 if (match_uri(h->uri, key)) {
7857 cmd_status.list[c] = (char *)h->uri;
7858 if (++c > 255)
7859 break;
7862 cmd_status.len = c;
7863 return;
7866 dep = (id == -1) ? 0 : cmds[id].level + 1;
7868 for (i = id + 1; i < LENGTH(cmds); i++) {
7869 if (cmds[i].level < dep)
7870 break;
7871 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7872 strlen(key)))
7873 cmd_status.list[c++] = cmds[i].cmd;
7877 cmd_status.len = c;
7880 char *
7881 cmd_getnext(int dir)
7883 cmd_status.index += dir;
7885 if (cmd_status.index < 0)
7886 cmd_status.index = cmd_status.len - 1;
7887 else if (cmd_status.index >= cmd_status.len)
7888 cmd_status.index = 0;
7890 return cmd_status.list[cmd_status.index];
7894 cmd_tokenize(char *s, char *tokens[])
7896 int i = 0;
7897 char *tok, *last;
7898 size_t len = strlen(s);
7899 bool blank;
7901 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7902 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7903 tok = strtok_r(NULL, " ", &last), i++)
7904 tokens[i] = tok;
7906 if (blank && i < 3)
7907 tokens[i++] = "";
7909 return (i);
7912 void
7913 cmd_complete(struct tab *t, char *str, int dir)
7915 GtkEntry *w = GTK_ENTRY(t->cmd);
7916 int i, j, levels, c = 0, dep = 0, parent = -1;
7917 int matchcount = 0;
7918 char *tok, *match, *s = g_strdup(str);
7919 char *tokens[3];
7920 char res[XT_MAX_URL_LENGTH + 32] = ":";
7921 char *sc = s;
7923 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7925 /* copy prefix*/
7926 for (i = 0; isdigit(s[i]); i++)
7927 res[i + 1] = s[i];
7929 for (; isspace(s[i]); i++)
7930 res[i + 1] = s[i];
7932 s += i;
7934 levels = cmd_tokenize(s, tokens);
7936 for (i = 0; i < levels - 1; i++) {
7937 tok = tokens[i];
7938 matchcount = 0;
7939 for (j = c; j < LENGTH(cmds); j++) {
7940 if (cmds[j].level < dep)
7941 break;
7942 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7943 strlen(tok))) {
7944 matchcount++;
7945 c = j + 1;
7946 if (strlen(tok) == strlen(cmds[j].cmd)) {
7947 matchcount = 1;
7948 break;
7953 if (matchcount == 1) {
7954 strlcat(res, tok, sizeof res);
7955 strlcat(res, " ", sizeof res);
7956 dep++;
7957 } else {
7958 g_free(sc);
7959 return;
7962 parent = c - 1;
7965 if (cmd_status.index == -1)
7966 cmd_getlist(parent, tokens[i]);
7968 if (cmd_status.len > 0) {
7969 match = cmd_getnext(dir);
7970 strlcat(res, match, sizeof res);
7971 gtk_entry_set_text(w, res);
7972 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7975 g_free(sc);
7978 gboolean
7979 cmd_execute(struct tab *t, char *str)
7981 struct cmd *cmd = NULL;
7982 char *tok, *last, *s = g_strdup(str), *sc;
7983 char prefixstr[4];
7984 int j, len, c = 0, dep = 0, matchcount = 0;
7985 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7986 struct karg arg = {0, NULL, -1};
7988 sc = s;
7990 /* copy prefix*/
7991 for (j = 0; j<3 && isdigit(s[j]); j++)
7992 prefixstr[j]=s[j];
7994 prefixstr[j]='\0';
7996 s += j;
7997 while (isspace(s[0]))
7998 s++;
8000 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8001 prefix = atoi(prefixstr);
8002 else
8003 s = sc;
8005 for (tok = strtok_r(s, " ", &last); tok;
8006 tok = strtok_r(NULL, " ", &last)) {
8007 matchcount = 0;
8008 for (j = c; j < LENGTH(cmds); j++) {
8009 if (cmds[j].level < dep)
8010 break;
8011 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8012 strlen(tok);
8013 if (cmds[j].level == dep &&
8014 !strncmp(tok, cmds[j].cmd, len)) {
8015 matchcount++;
8016 c = j + 1;
8017 cmd = &cmds[j];
8018 if (len == strlen(cmds[j].cmd)) {
8019 matchcount = 1;
8020 break;
8024 if (matchcount == 1) {
8025 if (cmd->type > 0)
8026 goto execute_cmd;
8027 dep++;
8028 } else {
8029 show_oops(t, "Invalid command: %s", str);
8030 goto done;
8033 execute_cmd:
8034 arg.i = cmd->arg;
8036 if (prefix != -1)
8037 arg.precount = prefix;
8038 else if (cmd_prefix > 0)
8039 arg.precount = cmd_prefix;
8041 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8042 show_oops(t, "No prefix allowed: %s", str);
8043 goto done;
8045 if (cmd->type > 1)
8046 arg.s = last ? g_strdup(last) : g_strdup("");
8047 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8048 arg.precount = atoi(arg.s);
8049 if (arg.precount <= 0) {
8050 if (arg.s[0] == '0')
8051 show_oops(t, "Zero count");
8052 else
8053 show_oops(t, "Trailing characters");
8054 goto done;
8058 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8059 __func__, arg.precount, arg.s);
8061 cmd->func(t, &arg);
8063 rv = XT_CB_HANDLED;
8064 done:
8065 if (j > 0)
8066 cmd_prefix = 0;
8067 g_free(sc);
8068 if (arg.s)
8069 g_free(arg.s);
8071 return (rv);
8075 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8077 if (t == NULL) {
8078 show_oops(NULL, "entry_key_cb invalid parameters");
8079 return (XT_CB_PASSTHROUGH);
8082 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8083 e->keyval, e->state, t);
8085 hide_oops(t);
8087 if (e->keyval == GDK_Escape) {
8088 /* don't use focus_webview(t) because we want to type :cmds */
8089 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8092 return (handle_keypress(t, e, 1));
8095 struct command_entry *
8096 history_prev(struct command_list *l, struct command_entry *at)
8098 if (at == NULL)
8099 at = TAILQ_LAST(l, command_list);
8100 else {
8101 at = TAILQ_PREV(at, command_list, entry);
8102 if (at == NULL)
8103 at = TAILQ_LAST(l, command_list);
8106 return (at);
8109 struct command_entry *
8110 history_next(struct command_list *l, struct command_entry *at)
8112 if (at == NULL)
8113 at = TAILQ_FIRST(l);
8114 else {
8115 at = TAILQ_NEXT(at, entry);
8116 if (at == NULL)
8117 at = TAILQ_FIRST(l);
8120 return (at);
8124 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8126 int rv = XT_CB_HANDLED;
8127 const gchar *c = gtk_entry_get_text(w);
8129 if (t == NULL) {
8130 show_oops(NULL, "cmd_keypress_cb parameters");
8131 return (XT_CB_PASSTHROUGH);
8134 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8135 e->keyval, e->state, t);
8137 /* sanity */
8138 if (c == NULL)
8139 e->keyval = GDK_Escape;
8140 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8141 e->keyval = GDK_Escape;
8143 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8144 e->keyval != GDK_ISO_Left_Tab)
8145 cmd_status.index = -1;
8147 switch (e->keyval) {
8148 case GDK_Tab:
8149 if (c[0] == ':')
8150 cmd_complete(t, (char *)&c[1], 1);
8151 goto done;
8152 case GDK_ISO_Left_Tab:
8153 if (c[0] == ':')
8154 cmd_complete(t, (char *)&c[1], -1);
8156 goto done;
8157 case GDK_Down:
8158 if (c[0] != ':') {
8159 if ((search_at = history_next(&shl, search_at))) {
8160 search_at->line[0] = c[0];
8161 gtk_entry_set_text(w, search_at->line);
8162 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8164 } else {
8165 if ((history_at = history_prev(&chl, history_at))) {
8166 history_at->line[0] = c[0];
8167 gtk_entry_set_text(w, history_at->line);
8168 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8172 goto done;
8173 case GDK_Up:
8174 if (c[0] != ':') {
8175 if ((search_at = history_next(&shl, search_at))) {
8176 search_at->line[0] = c[0];
8177 gtk_entry_set_text(w, search_at->line);
8178 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8180 } else {
8181 if ((history_at = history_next(&chl, history_at))) {
8182 history_at->line[0] = c[0];
8183 gtk_entry_set_text(w, history_at->line);
8184 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8188 goto done;
8189 case GDK_BackSpace:
8190 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8191 break;
8192 /* FALLTHROUGH */
8193 case GDK_Escape:
8194 hide_cmd(t);
8195 focus_webview(t);
8197 /* cancel search */
8198 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8199 webkit_web_view_unmark_text_matches(t->wv);
8200 goto done;
8203 rv = XT_CB_PASSTHROUGH;
8204 done:
8205 return (rv);
8208 void
8209 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8211 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8214 void
8215 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8217 /* popup menu enabled */
8218 t->popup = 1;
8222 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8224 if (t == NULL) {
8225 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8226 return (XT_CB_PASSTHROUGH);
8229 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8230 t->tab_id, t->popup);
8232 /* if popup is enabled don't lose focus */
8233 if (t->popup) {
8234 t->popup = 0;
8235 return (XT_CB_PASSTHROUGH);
8238 hide_cmd(t);
8239 hide_oops(t);
8241 if (show_url == 0 || t->focus_wv)
8242 focus_webview(t);
8243 else
8244 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8246 return (XT_CB_PASSTHROUGH);
8249 void
8250 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8252 char *s;
8253 const gchar *c = gtk_entry_get_text(entry);
8255 if (t == NULL) {
8256 show_oops(NULL, "cmd_activate_cb invalid parameters");
8257 return;
8260 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8262 hide_cmd(t);
8264 /* sanity */
8265 if (c == NULL)
8266 goto done;
8267 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8268 goto done;
8269 if (strlen(c) < 2)
8270 goto done;
8271 s = (char *)&c[1];
8273 if (c[0] == '/' || c[0] == '?') {
8274 /* see if there is a timer pending */
8275 if (t->search_id) {
8276 g_source_remove(t->search_id);
8277 t->search_id = 0;
8278 search_cb(t);
8281 if (t->search_text) {
8282 g_free(t->search_text);
8283 t->search_text = NULL;
8286 t->search_text = g_strdup(s);
8287 if (global_search)
8288 g_free(global_search);
8289 global_search = g_strdup(s);
8290 t->search_forward = c[0] == '/';
8292 history_add(&shl, search_file, s, &search_history_count);
8293 goto done;
8296 cmd_execute(t, s);
8298 history_add(&chl, command_file, s, &cmd_history_count);
8299 done:
8300 return;
8303 void
8304 backward_cb(GtkWidget *w, struct tab *t)
8306 struct karg a;
8308 if (t == NULL) {
8309 show_oops(NULL, "backward_cb invalid parameters");
8310 return;
8313 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8315 a.i = XT_NAV_BACK;
8316 navaction(t, &a);
8319 void
8320 forward_cb(GtkWidget *w, struct tab *t)
8322 struct karg a;
8324 if (t == NULL) {
8325 show_oops(NULL, "forward_cb invalid parameters");
8326 return;
8329 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8331 a.i = XT_NAV_FORWARD;
8332 navaction(t, &a);
8335 void
8336 home_cb(GtkWidget *w, struct tab *t)
8338 if (t == NULL) {
8339 show_oops(NULL, "home_cb invalid parameters");
8340 return;
8343 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8345 load_uri(t, home);
8348 void
8349 stop_cb(GtkWidget *w, struct tab *t)
8351 WebKitWebFrame *frame;
8353 if (t == NULL) {
8354 show_oops(NULL, "stop_cb invalid parameters");
8355 return;
8358 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8360 frame = webkit_web_view_get_main_frame(t->wv);
8361 if (frame == NULL) {
8362 show_oops(t, "stop_cb: no frame");
8363 return;
8366 webkit_web_frame_stop_loading(frame);
8367 abort_favicon_download(t);
8370 void
8371 setup_webkit(struct tab *t)
8373 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8374 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8375 FALSE, (char *)NULL);
8376 else
8377 warnx("webkit does not have \"enable-dns-prefetching\" property");
8378 g_object_set(G_OBJECT(t->settings),
8379 "user-agent", t->user_agent, (char *)NULL);
8380 g_object_set(G_OBJECT(t->settings),
8381 "enable-scripts", enable_scripts, (char *)NULL);
8382 g_object_set(G_OBJECT(t->settings),
8383 "enable-plugins", enable_plugins, (char *)NULL);
8384 g_object_set(G_OBJECT(t->settings),
8385 "javascript-can-open-windows-automatically", enable_scripts,
8386 (char *)NULL);
8387 g_object_set(G_OBJECT(t->settings),
8388 "enable-html5-database", FALSE, (char *)NULL);
8389 g_object_set(G_OBJECT(t->settings),
8390 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8391 g_object_set(G_OBJECT(t->settings),
8392 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8393 g_object_set(G_OBJECT(t->settings),
8394 "spell_checking_languages", spell_check_languages, (char *)NULL);
8395 g_object_set(G_OBJECT(t->wv),
8396 "full-content-zoom", TRUE, (char *)NULL);
8398 webkit_web_view_set_settings(t->wv, t->settings);
8401 gboolean
8402 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8404 struct tab *ti, *t = NULL;
8405 gdouble view_size, value, max;
8406 gchar *position;
8408 TAILQ_FOREACH(ti, &tabs, entry)
8409 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8410 t = ti;
8411 break;
8414 if (t == NULL)
8415 return FALSE;
8417 if (adjustment == NULL)
8418 adjustment = gtk_scrolled_window_get_vadjustment(
8419 GTK_SCROLLED_WINDOW(t->browser_win));
8421 view_size = gtk_adjustment_get_page_size(adjustment);
8422 value = gtk_adjustment_get_value(adjustment);
8423 max = gtk_adjustment_get_upper(adjustment) - view_size;
8425 if (max == 0)
8426 position = g_strdup("All");
8427 else if (value == max)
8428 position = g_strdup("Bot");
8429 else if (value == 0)
8430 position = g_strdup("Top");
8431 else
8432 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8434 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8435 g_free(position);
8437 return (TRUE);
8440 GtkWidget *
8441 create_browser(struct tab *t)
8443 GtkWidget *w;
8444 gchar *strval;
8445 GtkAdjustment *adjustment;
8447 if (t == NULL) {
8448 show_oops(NULL, "create_browser invalid parameters");
8449 return (NULL);
8452 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8453 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8454 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8455 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8457 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8458 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8459 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8461 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8462 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8464 /* set defaults */
8465 t->settings = webkit_web_settings_new();
8467 if (user_agent == NULL) {
8468 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8469 (char *)NULL);
8470 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8471 g_free(strval);
8472 } else
8473 t->user_agent = g_strdup(user_agent);
8475 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8477 adjustment =
8478 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8479 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8480 G_CALLBACK(update_statusbar_position), NULL);
8482 setup_webkit(t);
8484 return (w);
8487 GtkWidget *
8488 create_window(void)
8490 GtkWidget *w;
8492 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8493 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8494 gtk_widget_set_name(w, "xxxterm");
8495 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8496 g_signal_connect(G_OBJECT(w), "delete_event",
8497 G_CALLBACK (gtk_main_quit), NULL);
8499 return (w);
8502 GtkWidget *
8503 create_kiosk_toolbar(struct tab *t)
8505 GtkWidget *toolbar = NULL, *b;
8507 b = gtk_hbox_new(FALSE, 0);
8508 toolbar = b;
8509 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8511 /* backward button */
8512 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8513 gtk_widget_set_sensitive(t->backward, FALSE);
8514 g_signal_connect(G_OBJECT(t->backward), "clicked",
8515 G_CALLBACK(backward_cb), t);
8516 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8518 /* forward button */
8519 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8520 gtk_widget_set_sensitive(t->forward, FALSE);
8521 g_signal_connect(G_OBJECT(t->forward), "clicked",
8522 G_CALLBACK(forward_cb), t);
8523 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8525 /* home button */
8526 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8527 gtk_widget_set_sensitive(t->gohome, true);
8528 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8529 G_CALLBACK(home_cb), t);
8530 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8532 /* create widgets but don't use them */
8533 t->uri_entry = gtk_entry_new();
8534 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8535 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8536 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8538 return (toolbar);
8541 GtkWidget *
8542 create_toolbar(struct tab *t)
8544 GtkWidget *toolbar = NULL, *b, *eb1;
8546 b = gtk_hbox_new(FALSE, 0);
8547 toolbar = b;
8548 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8550 /* backward button */
8551 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8552 gtk_widget_set_sensitive(t->backward, FALSE);
8553 g_signal_connect(G_OBJECT(t->backward), "clicked",
8554 G_CALLBACK(backward_cb), t);
8555 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8557 /* forward button */
8558 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8559 gtk_widget_set_sensitive(t->forward, FALSE);
8560 g_signal_connect(G_OBJECT(t->forward), "clicked",
8561 G_CALLBACK(forward_cb), t);
8562 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8563 FALSE, 0);
8565 /* stop button */
8566 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8567 gtk_widget_set_sensitive(t->stop, FALSE);
8568 g_signal_connect(G_OBJECT(t->stop), "clicked",
8569 G_CALLBACK(stop_cb), t);
8570 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8571 FALSE, 0);
8573 /* JS button */
8574 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8575 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8576 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8577 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8578 G_CALLBACK(js_toggle_cb), t);
8579 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8581 t->uri_entry = gtk_entry_new();
8582 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8583 G_CALLBACK(activate_uri_entry_cb), t);
8584 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8585 G_CALLBACK(entry_key_cb), t);
8586 completion_add(t);
8587 eb1 = gtk_hbox_new(FALSE, 0);
8588 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8589 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8590 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8592 /* search entry */
8593 if (search_string) {
8594 GtkWidget *eb2;
8595 t->search_entry = gtk_entry_new();
8596 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8597 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8598 G_CALLBACK(activate_search_entry_cb), t);
8599 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8600 G_CALLBACK(entry_key_cb), t);
8601 gtk_widget_set_size_request(t->search_entry, -1, -1);
8602 eb2 = gtk_hbox_new(FALSE, 0);
8603 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8604 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8606 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8609 return (toolbar);
8612 GtkWidget *
8613 create_buffers(struct tab *t)
8615 GtkCellRenderer *renderer;
8616 GtkWidget *view;
8618 view = gtk_tree_view_new();
8620 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8622 renderer = gtk_cell_renderer_text_new();
8623 gtk_tree_view_insert_column_with_attributes
8624 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8626 renderer = gtk_cell_renderer_text_new();
8627 gtk_tree_view_insert_column_with_attributes
8628 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8629 NULL);
8631 gtk_tree_view_set_model
8632 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8634 return view;
8637 void
8638 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8639 GtkTreeViewColumn *col, struct tab *t)
8641 GtkTreeIter iter;
8642 guint id;
8644 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8646 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8647 path)) {
8648 gtk_tree_model_get
8649 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8650 set_current_tab(id - 1);
8653 hide_buffers(t);
8656 /* after tab reordering/creation/removal */
8657 void
8658 recalc_tabs(void)
8660 struct tab *t;
8661 int maxid = 0;
8663 TAILQ_FOREACH(t, &tabs, entry) {
8664 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8665 if (t->tab_id > maxid)
8666 maxid = t->tab_id;
8668 gtk_widget_show(t->tab_elems.sep);
8671 TAILQ_FOREACH(t, &tabs, entry) {
8672 if (t->tab_id == maxid) {
8673 gtk_widget_hide(t->tab_elems.sep);
8674 break;
8679 /* after active tab change */
8680 void
8681 recolor_compact_tabs(void)
8683 struct tab *t;
8684 int curid = 0;
8685 GdkColor color;
8687 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8688 TAILQ_FOREACH(t, &tabs, entry)
8689 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8690 &color);
8692 curid = gtk_notebook_get_current_page(notebook);
8693 TAILQ_FOREACH(t, &tabs, entry)
8694 if (t->tab_id == curid) {
8695 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8696 gtk_widget_modify_fg(t->tab_elems.label,
8697 GTK_STATE_NORMAL, &color);
8698 break;
8702 void
8703 set_current_tab(int page_num)
8705 buffercmd_abort(get_current_tab());
8706 gtk_notebook_set_current_page(notebook, page_num);
8707 recolor_compact_tabs();
8711 undo_close_tab_save(struct tab *t)
8713 int m, n;
8714 const gchar *uri;
8715 struct undo *u1, *u2;
8716 GList *items;
8717 WebKitWebHistoryItem *item;
8719 if ((uri = get_uri(t)) == NULL)
8720 return (1);
8722 u1 = g_malloc0(sizeof(struct undo));
8723 u1->uri = g_strdup(uri);
8725 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8727 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8728 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8729 u1->back = n;
8731 /* forward history */
8732 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8734 while (items) {
8735 item = items->data;
8736 u1->history = g_list_prepend(u1->history,
8737 webkit_web_history_item_copy(item));
8738 items = g_list_next(items);
8741 /* current item */
8742 if (m) {
8743 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8744 u1->history = g_list_prepend(u1->history,
8745 webkit_web_history_item_copy(item));
8748 /* back history */
8749 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8751 while (items) {
8752 item = items->data;
8753 u1->history = g_list_prepend(u1->history,
8754 webkit_web_history_item_copy(item));
8755 items = g_list_next(items);
8758 TAILQ_INSERT_HEAD(&undos, u1, entry);
8760 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8761 u2 = TAILQ_LAST(&undos, undo_tailq);
8762 TAILQ_REMOVE(&undos, u2, entry);
8763 g_free(u2->uri);
8764 g_list_free(u2->history);
8765 g_free(u2);
8766 } else
8767 undo_count++;
8769 return (0);
8772 void
8773 delete_tab(struct tab *t)
8775 struct karg a;
8777 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8779 if (t == NULL)
8780 return;
8782 buffercmd_abort(t);
8783 TAILQ_REMOVE(&tabs, t, entry);
8785 /* Halt all webkit activity. */
8786 abort_favicon_download(t);
8787 webkit_web_view_stop_loading(t->wv);
8789 /* Save the tab, so we can undo the close. */
8790 undo_close_tab_save(t);
8792 if (browser_mode == XT_BM_KIOSK) {
8793 gtk_widget_destroy(t->uri_entry);
8794 gtk_widget_destroy(t->stop);
8795 gtk_widget_destroy(t->js_toggle);
8798 gtk_widget_destroy(t->tab_elems.eventbox);
8799 gtk_widget_destroy(t->vbox);
8801 /* just in case */
8802 if (t->search_id)
8803 g_source_remove(t->search_id);
8805 g_free(t->user_agent);
8806 g_free(t->stylesheet);
8807 g_free(t->tmp_uri);
8808 g_free(t);
8810 if (TAILQ_EMPTY(&tabs)) {
8811 if (browser_mode == XT_BM_KIOSK)
8812 create_new_tab(home, NULL, 1, -1);
8813 else
8814 create_new_tab(NULL, NULL, 1, -1);
8817 /* recreate session */
8818 if (session_autosave) {
8819 a.s = NULL;
8820 save_tabs(t, &a);
8823 recalc_tabs();
8824 recolor_compact_tabs();
8827 void
8828 update_statusbar_zoom(struct tab *t)
8830 gfloat zoom;
8831 char s[16] = { '\0' };
8833 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8834 if ((zoom <= 0.99 || zoom >= 1.01))
8835 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8836 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8839 void
8840 setzoom_webkit(struct tab *t, int adjust)
8842 #define XT_ZOOMPERCENT 0.04
8844 gfloat zoom;
8846 if (t == NULL) {
8847 show_oops(NULL, "setzoom_webkit invalid parameters");
8848 return;
8851 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8852 if (adjust == XT_ZOOM_IN)
8853 zoom += XT_ZOOMPERCENT;
8854 else if (adjust == XT_ZOOM_OUT)
8855 zoom -= XT_ZOOMPERCENT;
8856 else if (adjust > 0)
8857 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8858 else {
8859 show_oops(t, "setzoom_webkit invalid zoom value");
8860 return;
8863 if (zoom < XT_ZOOMPERCENT)
8864 zoom = XT_ZOOMPERCENT;
8865 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8866 update_statusbar_zoom(t);
8869 gboolean
8870 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8872 struct tab *t = (struct tab *) data;
8874 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8876 switch (event->button) {
8877 case 1:
8878 set_current_tab(t->tab_id);
8879 break;
8880 case 2:
8881 delete_tab(t);
8882 break;
8885 return TRUE;
8888 void
8889 append_tab(struct tab *t)
8891 if (t == NULL)
8892 return;
8894 TAILQ_INSERT_TAIL(&tabs, t, entry);
8895 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8898 GtkWidget *
8899 create_sbe(int width)
8901 GtkWidget *sbe;
8903 sbe = gtk_entry_new();
8904 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8905 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8906 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8907 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8908 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8909 gtk_widget_set_size_request(sbe, width, -1);
8911 return sbe;
8914 struct tab *
8915 create_new_tab(char *title, struct undo *u, int focus, int position)
8917 struct tab *t;
8918 int load = 1, id;
8919 GtkWidget *b, *bb;
8920 WebKitWebHistoryItem *item;
8921 GList *items;
8922 GdkColor color;
8923 char *p;
8924 int sbe_p = 0, sbe_b = 0,
8925 sbe_z = 0;
8927 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8929 if (tabless && !TAILQ_EMPTY(&tabs)) {
8930 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8931 return (NULL);
8934 t = g_malloc0(sizeof *t);
8936 if (title == NULL) {
8937 title = "(untitled)";
8938 load = 0;
8941 t->vbox = gtk_vbox_new(FALSE, 0);
8943 /* label + button for tab */
8944 b = gtk_hbox_new(FALSE, 0);
8945 t->tab_content = b;
8947 #if GTK_CHECK_VERSION(2, 20, 0)
8948 t->spinner = gtk_spinner_new();
8949 #endif
8950 t->label = gtk_label_new(title);
8951 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8952 gtk_widget_set_size_request(t->label, 100, 0);
8953 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8954 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8955 gtk_widget_set_size_request(b, 130, 0);
8957 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8958 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8959 #if GTK_CHECK_VERSION(2, 20, 0)
8960 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8961 #endif
8963 /* toolbar */
8964 if (browser_mode == XT_BM_KIOSK) {
8965 t->toolbar = create_kiosk_toolbar(t);
8966 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8968 } else {
8969 t->toolbar = create_toolbar(t);
8970 if (fancy_bar)
8971 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8972 FALSE, 0);
8975 /* marks */
8976 marks_clear(t);
8978 /* browser */
8979 t->browser_win = create_browser(t);
8980 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8982 /* oops message for user feedback */
8983 t->oops = gtk_entry_new();
8984 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8985 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8986 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8987 gdk_color_parse(XT_COLOR_RED, &color);
8988 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8989 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8990 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8992 /* command entry */
8993 t->cmd = gtk_entry_new();
8994 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8995 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8996 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8997 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8999 /* status bar */
9000 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9002 t->sbe.statusbar = gtk_entry_new();
9003 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9004 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9005 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9006 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9008 /* create these widgets only if specified in statusbar_elems */
9010 t->sbe.position = create_sbe(40);
9011 t->sbe.zoom = create_sbe(40);
9012 t->sbe.buffercmd = create_sbe(60);
9014 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9016 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9017 TRUE, FALSE);
9019 /* gtk widgets cannot be added to a box twice. sbe_* variables
9020 make sure of this */
9021 for (p = statusbar_elems; *p != '\0'; p++) {
9022 switch (*p) {
9023 case '|':
9025 GtkWidget *sep = gtk_vseparator_new();
9027 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9028 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9029 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9030 FALSE, FALSE, FALSE);
9031 break;
9033 case 'P':
9034 if (sbe_p) {
9035 warnx("flag \"%c\" specified more than "
9036 "once in statusbar_elems\n", *p);
9037 break;
9039 sbe_p = 1;
9040 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9041 t->sbe.position, FALSE, FALSE, FALSE);
9042 break;
9043 case 'B':
9044 if (sbe_b) {
9045 warnx("flag \"%c\" specified more than "
9046 "once in statusbar_elems\n", *p);
9047 break;
9049 sbe_b = 1;
9050 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9051 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9052 break;
9053 case 'Z':
9054 if (sbe_z) {
9055 warnx("flag \"%c\" specified more than "
9056 "once in statusbar_elems\n", *p);
9057 break;
9059 sbe_z = 1;
9060 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9061 t->sbe.zoom, FALSE, FALSE, FALSE);
9062 break;
9063 default:
9064 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9065 break;
9069 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9071 /* buffer list */
9072 t->buffers = create_buffers(t);
9073 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9075 /* xtp meaning is normal by default */
9076 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9078 /* set empty favicon */
9079 xt_icon_from_name(t, "text-html");
9081 /* and show it all */
9082 gtk_widget_show_all(b);
9083 gtk_widget_show_all(t->vbox);
9085 /* compact tab bar */
9086 t->tab_elems.label = gtk_label_new(title);
9087 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9088 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9089 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9090 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9092 t->tab_elems.eventbox = gtk_event_box_new();
9093 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9094 t->tab_elems.sep = gtk_vseparator_new();
9096 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9097 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9098 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9099 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9100 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9101 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9103 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9104 TRUE, 0);
9105 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9106 FALSE, 0);
9107 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9108 t->tab_elems.box);
9110 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9111 TRUE, 0);
9112 gtk_widget_show_all(t->tab_elems.eventbox);
9114 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9115 append_tab(t);
9116 else {
9117 id = position >= 0 ? position :
9118 gtk_notebook_get_current_page(notebook) + 1;
9119 if (id > gtk_notebook_get_n_pages(notebook))
9120 append_tab(t);
9121 else {
9122 TAILQ_INSERT_TAIL(&tabs, t, entry);
9123 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9124 gtk_box_reorder_child(GTK_BOX(tab_bar),
9125 t->tab_elems.eventbox, id);
9126 recalc_tabs();
9130 #if GTK_CHECK_VERSION(2, 20, 0)
9131 /* turn spinner off if we are a new tab without uri */
9132 if (!load) {
9133 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9134 gtk_widget_hide(t->spinner);
9136 #endif
9137 /* make notebook tabs reorderable */
9138 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9140 /* compact tabs clickable */
9141 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9142 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9144 g_object_connect(G_OBJECT(t->cmd),
9145 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9146 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9147 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9148 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9149 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9150 (char *)NULL);
9152 /* reuse wv_button_cb to hide oops */
9153 g_object_connect(G_OBJECT(t->oops),
9154 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9155 (char *)NULL);
9157 g_signal_connect(t->buffers,
9158 "row-activated", G_CALLBACK(row_activated_cb), t);
9159 g_object_connect(G_OBJECT(t->buffers),
9160 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
9162 g_object_connect(G_OBJECT(t->wv),
9163 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9164 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9165 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9166 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9167 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9168 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9169 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9170 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9171 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9172 "signal::event", G_CALLBACK(webview_event_cb), t,
9173 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9174 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9175 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9176 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9177 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9178 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9179 (char *)NULL);
9180 g_signal_connect(t->wv,
9181 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9183 * XXX this puts invalid url in uri_entry and that is undesirable
9185 #if 0
9186 g_signal_connect(t->wv,
9187 "load-error", G_CALLBACK(notify_load_error_cb), t);
9188 #endif
9189 g_signal_connect(t->wv,
9190 "notify::title", G_CALLBACK(notify_title_cb), t);
9192 /* hijack the unused keys as if we were the browser */
9193 g_object_connect(G_OBJECT(t->toolbar),
9194 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9195 (char *)NULL);
9197 g_signal_connect(G_OBJECT(bb), "button_press_event",
9198 G_CALLBACK(tab_close_cb), t);
9200 /* setup history */
9201 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9202 /* restore the tab's history */
9203 if (u && u->history) {
9204 items = u->history;
9205 while (items) {
9206 item = items->data;
9207 webkit_web_back_forward_list_add_item(t->bfl, item);
9208 items = g_list_next(items);
9211 item = g_list_nth_data(u->history, u->back);
9212 if (item)
9213 webkit_web_view_go_to_back_forward_item(t->wv, item);
9215 g_list_free(items);
9216 g_list_free(u->history);
9217 } else
9218 webkit_web_back_forward_list_clear(t->bfl);
9220 /* hide stuff */
9221 hide_cmd(t);
9222 hide_oops(t);
9223 hide_buffers(t);
9224 url_set_visibility();
9225 statusbar_set_visibility();
9227 if (focus) {
9228 set_current_tab(t->tab_id);
9229 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9230 t->tab_id);
9233 if (load) {
9234 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9235 load_uri(t, title);
9236 } else {
9237 if (show_url == 1)
9238 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9239 else
9240 focus_webview(t);
9243 recolor_compact_tabs();
9244 setzoom_webkit(t, XT_ZOOM_NORMAL);
9245 return (t);
9248 void
9249 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9250 gpointer *udata)
9252 struct tab *t;
9253 const gchar *uri;
9255 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9257 if (gtk_notebook_get_current_page(notebook) == -1)
9258 recalc_tabs();
9260 TAILQ_FOREACH(t, &tabs, entry) {
9261 if (t->tab_id == pn) {
9262 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9263 "%d\n", pn);
9265 uri = get_title(t, TRUE);
9266 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9268 hide_cmd(t);
9269 hide_oops(t);
9271 if (t->focus_wv) {
9272 /* can't use focus_webview here */
9273 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9279 void
9280 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9281 gpointer *udata)
9283 struct tab *t = NULL, *tt;
9285 recalc_tabs();
9287 TAILQ_FOREACH(tt, &tabs, entry)
9288 if (tt->tab_id == pn) {
9289 t = tt;
9290 break;
9293 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9295 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9296 t->tab_id);
9299 void
9300 menuitem_response(struct tab *t)
9302 gtk_notebook_set_current_page(notebook, t->tab_id);
9305 gboolean
9306 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9308 GtkWidget *menu, *menu_items;
9309 GdkEventButton *bevent;
9310 const gchar *uri;
9311 struct tab *ti;
9313 if (event->type == GDK_BUTTON_PRESS) {
9314 bevent = (GdkEventButton *) event;
9315 menu = gtk_menu_new();
9317 TAILQ_FOREACH(ti, &tabs, entry) {
9318 if ((uri = get_uri(ti)) == NULL)
9319 /* XXX make sure there is something to print */
9320 /* XXX add gui pages in here to look purdy */
9321 uri = "(untitled)";
9322 menu_items = gtk_menu_item_new_with_label(uri);
9323 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9324 gtk_widget_show(menu_items);
9326 g_signal_connect_swapped((menu_items),
9327 "activate", G_CALLBACK(menuitem_response),
9328 (gpointer)ti);
9331 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9332 bevent->button, bevent->time);
9334 /* unref object so it'll free itself when popped down */
9335 #if !GTK_CHECK_VERSION(3, 0, 0)
9336 /* XXX does not need unref with gtk+3? */
9337 g_object_ref_sink(menu);
9338 g_object_unref(menu);
9339 #endif
9341 return (TRUE /* eat event */);
9344 return (FALSE /* propagate */);
9348 icon_size_map(int icon_size)
9350 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9351 icon_size > GTK_ICON_SIZE_DIALOG)
9352 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9354 return (icon_size);
9357 GtkWidget *
9358 create_button(char *name, char *stockid, int size)
9360 GtkWidget *button, *image;
9361 gchar *rcstring;
9362 int gtk_icon_size;
9364 rcstring = g_strdup_printf(
9365 "style \"%s-style\"\n"
9366 "{\n"
9367 " GtkWidget::focus-padding = 0\n"
9368 " GtkWidget::focus-line-width = 0\n"
9369 " xthickness = 0\n"
9370 " ythickness = 0\n"
9371 "}\n"
9372 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9373 gtk_rc_parse_string(rcstring);
9374 g_free(rcstring);
9375 button = gtk_button_new();
9376 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9377 gtk_icon_size = icon_size_map(size ? size : icon_size);
9379 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9380 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9381 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9382 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9383 gtk_widget_set_name(button, name);
9384 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9386 return (button);
9389 void
9390 button_set_stockid(GtkWidget *button, char *stockid)
9392 GtkWidget *image;
9394 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9395 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9396 gtk_button_set_image(GTK_BUTTON(button), image);
9399 void
9400 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9402 gchar *p = NULL;
9403 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9404 gint len;
9406 if (xterm_workaround == 0)
9407 return;
9410 * xterm doesn't play nice with clipboards because it clears the
9411 * primary when clicked. We rely on primary being set to properly
9412 * handle middle mouse button clicks (paste). So when someone clears
9413 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9414 * other application behavior (as in DON'T clear primary).
9417 p = gtk_clipboard_wait_for_text(primary);
9418 if (p == NULL) {
9419 if (gdk_property_get(gdk_get_default_root_window(),
9420 atom,
9421 gdk_atom_intern("STRING", FALSE),
9423 1024 * 1024 /* picked out of my butt */,
9424 FALSE,
9425 NULL,
9426 NULL,
9427 &len,
9428 (guchar **)&p)) {
9429 /* yes sir, we need to NUL the string */
9430 p[len] = '\0';
9431 gtk_clipboard_set_text(primary, p, -1);
9435 if (p)
9436 g_free(p);
9439 void
9440 create_canvas(void)
9442 GtkWidget *vbox;
9443 GList *l = NULL;
9444 GdkPixbuf *pb;
9445 char file[PATH_MAX];
9446 int i;
9448 vbox = gtk_vbox_new(FALSE, 0);
9449 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9450 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9451 #if !GTK_CHECK_VERSION(3, 0, 0)
9452 /* XXX seems to be needed with gtk+2 */
9453 gtk_notebook_set_tab_hborder(notebook, 0);
9454 gtk_notebook_set_tab_vborder(notebook, 0);
9455 #endif
9456 gtk_notebook_set_scrollable(notebook, TRUE);
9457 gtk_notebook_set_show_border(notebook, FALSE);
9458 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9460 abtn = gtk_button_new();
9461 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9462 gtk_widget_set_size_request(arrow, -1, -1);
9463 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9464 gtk_widget_set_size_request(abtn, -1, 20);
9466 #if GTK_CHECK_VERSION(2, 20, 0)
9467 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9468 #endif
9469 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9471 /* compact tab bar */
9472 tab_bar = gtk_hbox_new(TRUE, 0);
9474 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9475 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9476 gtk_widget_set_size_request(vbox, -1, -1);
9478 g_object_connect(G_OBJECT(notebook),
9479 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9480 (char *)NULL);
9481 g_object_connect(G_OBJECT(notebook),
9482 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9483 NULL, (char *)NULL);
9484 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9485 G_CALLBACK(arrow_cb), NULL);
9487 main_window = create_window();
9488 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9490 /* icons */
9491 for (i = 0; i < LENGTH(icons); i++) {
9492 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9493 pb = gdk_pixbuf_new_from_file(file, NULL);
9494 l = g_list_append(l, pb);
9496 gtk_window_set_default_icon_list(l);
9498 /* clipboard work around */
9499 if (xterm_workaround)
9500 g_signal_connect(
9501 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9502 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9504 gtk_widget_show_all(abtn);
9505 gtk_widget_show_all(main_window);
9506 notebook_tab_set_visibility();
9509 void
9510 set_hook(void **hook, char *name)
9512 if (hook == NULL)
9513 errx(1, "set_hook");
9515 if (*hook == NULL) {
9516 *hook = dlsym(RTLD_NEXT, name);
9517 if (*hook == NULL)
9518 errx(1, "can't hook %s", name);
9522 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9523 gboolean
9524 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9526 g_return_val_if_fail(cookie1, FALSE);
9527 g_return_val_if_fail(cookie2, FALSE);
9529 return (!strcmp (cookie1->name, cookie2->name) &&
9530 !strcmp (cookie1->value, cookie2->value) &&
9531 !strcmp (cookie1->path, cookie2->path) &&
9532 !strcmp (cookie1->domain, cookie2->domain));
9535 void
9536 transfer_cookies(void)
9538 GSList *cf;
9539 SoupCookie *sc, *pc;
9541 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9543 for (;cf; cf = cf->next) {
9544 pc = cf->data;
9545 sc = soup_cookie_copy(pc);
9546 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9549 soup_cookies_free(cf);
9552 void
9553 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9555 GSList *cf;
9556 SoupCookie *ci;
9558 print_cookie("soup_cookie_jar_delete_cookie", c);
9560 if (cookies_enabled == 0)
9561 return;
9563 if (jar == NULL || c == NULL)
9564 return;
9566 /* find and remove from persistent jar */
9567 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9569 for (;cf; cf = cf->next) {
9570 ci = cf->data;
9571 if (soup_cookie_equal(ci, c)) {
9572 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9573 break;
9577 soup_cookies_free(cf);
9579 /* delete from session jar */
9580 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9583 void
9584 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9586 struct domain *d = NULL;
9587 SoupCookie *c;
9588 FILE *r_cookie_f;
9590 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9591 jar, p_cookiejar, s_cookiejar);
9593 if (cookies_enabled == 0)
9594 return;
9596 /* see if we are up and running */
9597 if (p_cookiejar == NULL) {
9598 _soup_cookie_jar_add_cookie(jar, cookie);
9599 return;
9601 /* disallow p_cookiejar adds, shouldn't happen */
9602 if (jar == p_cookiejar)
9603 return;
9605 /* sanity */
9606 if (jar == NULL || cookie == NULL)
9607 return;
9609 if (enable_cookie_whitelist &&
9610 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9611 blocked_cookies++;
9612 DNPRINTF(XT_D_COOKIE,
9613 "soup_cookie_jar_add_cookie: reject %s\n",
9614 cookie->domain);
9615 if (save_rejected_cookies) {
9616 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9617 show_oops(NULL, "can't open reject cookie file");
9618 return;
9620 fseek(r_cookie_f, 0, SEEK_END);
9621 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9622 cookie->http_only ? "#HttpOnly_" : "",
9623 cookie->domain,
9624 *cookie->domain == '.' ? "TRUE" : "FALSE",
9625 cookie->path,
9626 cookie->secure ? "TRUE" : "FALSE",
9627 cookie->expires ?
9628 (gulong)soup_date_to_time_t(cookie->expires) :
9630 cookie->name,
9631 cookie->value);
9632 fflush(r_cookie_f);
9633 fclose(r_cookie_f);
9635 if (!allow_volatile_cookies)
9636 return;
9639 if (cookie->expires == NULL && session_timeout) {
9640 soup_cookie_set_expires(cookie,
9641 soup_date_new_from_now(session_timeout));
9642 print_cookie("modified add cookie", cookie);
9645 /* see if we are white listed for persistence */
9646 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9647 /* add to persistent jar */
9648 c = soup_cookie_copy(cookie);
9649 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9650 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9653 /* add to session jar */
9654 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9655 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9658 void
9659 setup_cookies(void)
9661 char file[PATH_MAX];
9663 set_hook((void *)&_soup_cookie_jar_add_cookie,
9664 "soup_cookie_jar_add_cookie");
9665 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9666 "soup_cookie_jar_delete_cookie");
9668 if (cookies_enabled == 0)
9669 return;
9672 * the following code is intricate due to overriding several libsoup
9673 * functions.
9674 * do not alter order of these operations.
9677 /* rejected cookies */
9678 if (save_rejected_cookies)
9679 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9680 XT_REJECT_FILE);
9682 /* persistent cookies */
9683 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9684 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9686 /* session cookies */
9687 s_cookiejar = soup_cookie_jar_new();
9688 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9689 cookie_policy, (void *)NULL);
9690 transfer_cookies();
9692 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9695 void
9696 setup_proxy(char *uri)
9698 if (proxy_uri) {
9699 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9700 soup_uri_free(proxy_uri);
9701 proxy_uri = NULL;
9703 if (http_proxy) {
9704 if (http_proxy != uri) {
9705 g_free(http_proxy);
9706 http_proxy = NULL;
9710 if (uri) {
9711 http_proxy = g_strdup(uri);
9712 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9713 proxy_uri = soup_uri_new(http_proxy);
9714 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9715 g_object_set(session, "proxy-uri", proxy_uri,
9716 (char *)NULL);
9721 set_http_proxy(char *proxy)
9723 SoupURI *uri;
9725 if (proxy == NULL)
9726 return (1);
9728 /* see if we need to clear it instead */
9729 if (strlen(proxy) == 0) {
9730 setup_proxy(NULL);
9731 return (0);
9734 uri = soup_uri_new(proxy);
9735 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9736 return (1);
9738 setup_proxy(proxy);
9740 soup_uri_free(uri);
9742 return (0);
9746 send_cmd_to_socket(char *cmd)
9748 int s, len, rv = 1;
9749 struct sockaddr_un sa;
9751 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9752 warnx("%s: socket", __func__);
9753 return (rv);
9756 sa.sun_family = AF_UNIX;
9757 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9758 work_dir, XT_SOCKET_FILE);
9759 len = SUN_LEN(&sa);
9761 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9762 warnx("%s: connect", __func__);
9763 goto done;
9766 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9767 warnx("%s: send", __func__);
9768 goto done;
9771 rv = 0;
9772 done:
9773 close(s);
9774 return (rv);
9777 gboolean
9778 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9780 int s, n;
9781 char str[XT_MAX_URL_LENGTH];
9782 socklen_t t = sizeof(struct sockaddr_un);
9783 struct sockaddr_un sa;
9784 struct passwd *p;
9785 uid_t uid;
9786 gid_t gid;
9787 struct tab *tt;
9788 gint fd = g_io_channel_unix_get_fd(source);
9790 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9791 warn("accept");
9792 return (FALSE);
9795 if (getpeereid(s, &uid, &gid) == -1) {
9796 warn("getpeereid");
9797 return (FALSE);
9799 if (uid != getuid() || gid != getgid()) {
9800 warnx("unauthorized user");
9801 return (FALSE);
9804 p = getpwuid(uid);
9805 if (p == NULL) {
9806 warnx("not a valid user");
9807 return (FALSE);
9810 n = recv(s, str, sizeof(str), 0);
9811 if (n <= 0)
9812 return (TRUE);
9814 tt = TAILQ_LAST(&tabs, tab_list);
9815 cmd_execute(tt, str);
9816 return (TRUE);
9820 is_running(void)
9822 int s, len, rv = 1;
9823 struct sockaddr_un sa;
9825 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9826 warn("is_running: socket");
9827 return (-1);
9830 sa.sun_family = AF_UNIX;
9831 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9832 work_dir, XT_SOCKET_FILE);
9833 len = SUN_LEN(&sa);
9835 /* connect to see if there is a listener */
9836 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9837 rv = 0; /* not running */
9838 else
9839 rv = 1; /* already running */
9841 close(s);
9843 return (rv);
9847 build_socket(void)
9849 int s, len;
9850 struct sockaddr_un sa;
9852 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9853 warn("build_socket: socket");
9854 return (-1);
9857 sa.sun_family = AF_UNIX;
9858 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9859 work_dir, XT_SOCKET_FILE);
9860 len = SUN_LEN(&sa);
9862 /* connect to see if there is a listener */
9863 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9864 /* no listener so we will */
9865 unlink(sa.sun_path);
9867 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9868 warn("build_socket: bind");
9869 goto done;
9872 if (listen(s, 1) == -1) {
9873 warn("build_socket: listen");
9874 goto done;
9877 return (s);
9880 done:
9881 close(s);
9882 return (-1);
9885 gboolean
9886 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9887 GtkTreeIter *iter, struct tab *t)
9889 gchar *value;
9891 gtk_tree_model_get(model, iter, 0, &value, -1);
9892 load_uri(t, value);
9893 g_free(value);
9895 return (FALSE);
9898 gboolean
9899 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9900 GtkTreeIter *iter, struct tab *t)
9902 gchar *value;
9904 gtk_tree_model_get(model, iter, 0, &value, -1);
9905 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9906 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9907 g_free(value);
9909 return (TRUE);
9912 void
9913 completion_add_uri(const gchar *uri)
9915 GtkTreeIter iter;
9917 /* add uri to list_store */
9918 gtk_list_store_append(completion_model, &iter);
9919 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9922 gboolean
9923 completion_match(GtkEntryCompletion *completion, const gchar *key,
9924 GtkTreeIter *iter, gpointer user_data)
9926 gchar *value;
9927 gboolean match = FALSE;
9929 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9930 -1);
9932 if (value == NULL)
9933 return FALSE;
9935 match = match_uri(value, key);
9937 g_free(value);
9938 return (match);
9941 void
9942 completion_add(struct tab *t)
9944 /* enable completion for tab */
9945 t->completion = gtk_entry_completion_new();
9946 gtk_entry_completion_set_text_column(t->completion, 0);
9947 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9948 gtk_entry_completion_set_model(t->completion,
9949 GTK_TREE_MODEL(completion_model));
9950 gtk_entry_completion_set_match_func(t->completion, completion_match,
9951 NULL, NULL);
9952 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9953 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9954 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9955 G_CALLBACK(completion_select_cb), t);
9956 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9957 G_CALLBACK(completion_hover_cb), t);
9960 void
9961 xxx_dir(char *dir)
9963 struct stat sb;
9965 if (stat(dir, &sb)) {
9966 if (mkdir(dir, S_IRWXU) == -1)
9967 err(1, "mkdir %s", dir);
9968 if (stat(dir, &sb))
9969 err(1, "stat %s", dir);
9971 if (S_ISDIR(sb.st_mode) == 0)
9972 errx(1, "%s not a dir", dir);
9973 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9974 warnx("fixing invalid permissions on %s", dir);
9975 if (chmod(dir, S_IRWXU) == -1)
9976 err(1, "chmod %s", dir);
9980 void
9981 usage(void)
9983 fprintf(stderr,
9984 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9985 exit(0);
9990 main(int argc, char *argv[])
9992 struct stat sb;
9993 int c, s, optn = 0, opte = 0, focus = 1;
9994 char conf[PATH_MAX] = { '\0' };
9995 char file[PATH_MAX];
9996 char *env_proxy = NULL;
9997 char *cmd = NULL;
9998 FILE *f = NULL;
9999 struct karg a;
10000 struct sigaction sact;
10001 GIOChannel *channel;
10002 struct rlimit rlp;
10004 start_argv = argv;
10006 /* prepare gtk */
10007 if (!g_thread_supported()) {
10008 g_thread_init(NULL);
10009 gdk_threads_init();
10011 gtk_init(&argc, &argv);
10013 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10015 RB_INIT(&hl);
10016 RB_INIT(&js_wl);
10017 RB_INIT(&downloads);
10019 TAILQ_INIT(&tabs);
10020 TAILQ_INIT(&mtl);
10021 TAILQ_INIT(&aliases);
10022 TAILQ_INIT(&undos);
10023 TAILQ_INIT(&kbl);
10024 TAILQ_INIT(&spl);
10025 TAILQ_INIT(&chl);
10026 TAILQ_INIT(&shl);
10028 /* fiddle with ulimits */
10029 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10030 warn("getrlimit");
10031 else {
10032 /* just use them all */
10033 rlp.rlim_cur = rlp.rlim_max;
10034 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10035 warn("setrlimit");
10036 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10037 warn("getrlimit");
10038 else if (rlp.rlim_cur <= 256)
10039 startpage_add("%s requires at least 256 file "
10040 "descriptors, currently it has up to %d available",
10041 __progname, rlp.rlim_cur);
10044 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10045 switch (c) {
10046 case 'S':
10047 show_url = 0;
10048 break;
10049 case 'T':
10050 show_tabs = 0;
10051 break;
10052 case 'V':
10053 errx(0 , "Version: %s", version);
10054 break;
10055 case 'f':
10056 strlcpy(conf, optarg, sizeof(conf));
10057 break;
10058 case 's':
10059 strlcpy(named_session, optarg, sizeof(named_session));
10060 break;
10061 case 't':
10062 tabless = 1;
10063 break;
10064 case 'n':
10065 optn = 1;
10066 break;
10067 case 'e':
10068 opte = 1;
10069 break;
10070 default:
10071 usage();
10072 /* NOTREACHED */
10075 argc -= optind;
10076 argv += optind;
10078 init_keybindings();
10080 gnutls_global_init();
10082 /* generate session keys for xtp pages */
10083 generate_xtp_session_key(&dl_session_key);
10084 generate_xtp_session_key(&hl_session_key);
10085 generate_xtp_session_key(&cl_session_key);
10086 generate_xtp_session_key(&fl_session_key);
10088 /* signals */
10089 bzero(&sact, sizeof(sact));
10090 sigemptyset(&sact.sa_mask);
10091 sact.sa_handler = sigchild;
10092 sact.sa_flags = SA_NOCLDSTOP;
10093 sigaction(SIGCHLD, &sact, NULL);
10095 /* set download dir */
10096 pwd = getpwuid(getuid());
10097 if (pwd == NULL)
10098 errx(1, "invalid user %d", getuid());
10099 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10101 /* compile buffer command regexes */
10102 buffercmd_init();
10104 /* set default string settings */
10105 home = g_strdup("https://www.cyphertite.com");
10106 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10107 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10108 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10109 cmd_font_name = g_strdup("monospace normal 9");
10110 oops_font_name = g_strdup("monospace normal 9");
10111 statusbar_font_name = g_strdup("monospace normal 9");
10112 tabbar_font_name = g_strdup("monospace normal 9");
10113 statusbar_elems = g_strdup("BP");
10115 /* read config file */
10116 if (strlen(conf) == 0)
10117 snprintf(conf, sizeof conf, "%s/.%s",
10118 pwd->pw_dir, XT_CONF_FILE);
10119 config_parse(conf, 0);
10121 /* init fonts */
10122 cmd_font = pango_font_description_from_string(cmd_font_name);
10123 oops_font = pango_font_description_from_string(oops_font_name);
10124 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10125 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10127 /* working directory */
10128 if (strlen(work_dir) == 0)
10129 snprintf(work_dir, sizeof work_dir, "%s/%s",
10130 pwd->pw_dir, XT_DIR);
10131 xxx_dir(work_dir);
10133 /* icon cache dir */
10134 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10135 xxx_dir(cache_dir);
10137 /* certs dir */
10138 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10139 xxx_dir(certs_dir);
10141 /* sessions dir */
10142 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10143 work_dir, XT_SESSIONS_DIR);
10144 xxx_dir(sessions_dir);
10146 /* runtime settings that can override config file */
10147 if (runtime_settings[0] != '\0')
10148 config_parse(runtime_settings, 1);
10150 /* download dir */
10151 if (!strcmp(download_dir, pwd->pw_dir))
10152 strlcat(download_dir, "/downloads", sizeof download_dir);
10153 xxx_dir(download_dir);
10155 /* favorites file */
10156 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10157 if (stat(file, &sb)) {
10158 warnx("favorites file doesn't exist, creating it");
10159 if ((f = fopen(file, "w")) == NULL)
10160 err(1, "favorites");
10161 fclose(f);
10164 /* quickmarks file */
10165 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10166 if (stat(file, &sb)) {
10167 warnx("quickmarks file doesn't exist, creating it");
10168 if ((f = fopen(file, "w")) == NULL)
10169 err(1, "quickmarks");
10170 fclose(f);
10173 /* search history */
10174 if (history_autosave) {
10175 snprintf(search_file, sizeof search_file, "%s/%s",
10176 work_dir, XT_SEARCH_FILE);
10177 if (stat(search_file, &sb)) {
10178 warnx("search history file doesn't exist, creating it");
10179 if ((f = fopen(search_file, "w")) == NULL)
10180 err(1, "search_history");
10181 fclose(f);
10183 history_read(&shl, search_file, &search_history_count);
10186 /* command history */
10187 if (history_autosave) {
10188 snprintf(command_file, sizeof command_file, "%s/%s",
10189 work_dir, XT_COMMAND_FILE);
10190 if (stat(command_file, &sb)) {
10191 warnx("command history file doesn't exist, creating it");
10192 if ((f = fopen(command_file, "w")) == NULL)
10193 err(1, "command_history");
10194 fclose(f);
10196 history_read(&chl, command_file, &cmd_history_count);
10199 /* cookies */
10200 session = webkit_get_default_session();
10201 setup_cookies();
10203 /* certs */
10204 if (ssl_ca_file) {
10205 if (stat(ssl_ca_file, &sb)) {
10206 warnx("no CA file: %s", ssl_ca_file);
10207 g_free(ssl_ca_file);
10208 ssl_ca_file = NULL;
10209 } else
10210 g_object_set(session,
10211 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10212 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10213 (void *)NULL);
10216 /* guess_search regex */
10217 if (url_regex == NULL)
10218 url_regex = g_strdup(XT_URL_REGEX);
10219 if (url_regex)
10220 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10221 startpage_add("invalid url regex %s", url_regex);
10223 /* proxy */
10224 env_proxy = getenv("http_proxy");
10225 if (env_proxy)
10226 setup_proxy(env_proxy);
10227 else
10228 setup_proxy(http_proxy);
10230 if (opte) {
10231 send_cmd_to_socket(argv[0]);
10232 exit(0);
10235 /* set some connection parameters */
10236 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10237 g_object_set(session, "max-conns-per-host", max_host_connections,
10238 (char *)NULL);
10240 /* see if there is already an xxxterm running */
10241 if (single_instance && is_running()) {
10242 optn = 1;
10243 warnx("already running");
10246 if (optn) {
10247 while (argc) {
10248 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10249 send_cmd_to_socket(cmd);
10250 if (cmd)
10251 g_free(cmd);
10253 argc--;
10254 argv++;
10256 exit(0);
10259 /* uri completion */
10260 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10262 /* buffers */
10263 buffers_store = gtk_list_store_new
10264 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10266 qmarks_load();
10268 /* go graphical */
10269 create_canvas();
10270 notebook_tab_set_visibility();
10272 if (save_global_history)
10273 restore_global_history();
10275 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10276 restore_saved_tabs();
10277 else {
10278 a.s = named_session;
10279 a.i = XT_SES_DONOTHING;
10280 open_tabs(NULL, &a);
10283 /* see if we have an exception */
10284 if (!TAILQ_EMPTY(&spl)) {
10285 create_new_tab("about:startpage", NULL, focus, -1);
10286 focus = 0;
10289 while (argc) {
10290 create_new_tab(argv[0], NULL, focus, -1);
10291 focus = 0;
10293 argc--;
10294 argv++;
10297 if (TAILQ_EMPTY(&tabs))
10298 create_new_tab(home, NULL, 1, -1);
10300 if (enable_socket)
10301 if ((s = build_socket()) != -1) {
10302 channel = g_io_channel_unix_new(s);
10303 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10306 gtk_main();
10308 gnutls_global_deinit();
10310 if (url_regex)
10311 regfree(&url_re);
10313 return (0);