add basic command history
[xxxterm.git] / xxxterm.c
blobdcbc1e69f56a624b0f33748cf6d5489cd5ef413c
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_CB_HANDLED (TRUE)
324 #define XT_CB_PASSTHROUGH (FALSE)
325 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
326 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
327 #define XT_DLMAN_REFRESH "10"
328 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
329 "td{overflow: hidden;" \
330 " padding: 2px 2px 2px 2px;" \
331 " border: 1px solid black;" \
332 " vertical-align:top;" \
333 " word-wrap: break-word}\n" \
334 "tr:hover{background: #ffff99}\n" \
335 "th{background-color: #cccccc;" \
336 " border: 1px solid black}\n" \
337 "table{width: 100%%;" \
338 " border: 1px black solid;" \
339 " border-collapse:collapse}\n" \
340 ".progress-outer{" \
341 "border: 1px solid black;" \
342 " height: 8px;" \
343 " width: 90%%}\n" \
344 ".progress-inner{float: left;" \
345 " height: 8px;" \
346 " background: green}\n" \
347 ".dlstatus{font-size: small;" \
348 " text-align: center}\n" \
349 "</style>\n"
350 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
351 #define XT_MAX_UNDO_CLOSE_TAB (32)
352 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
353 #define XT_PRINT_EXTRA_MARGIN 10
354 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
355 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
357 /* colors */
358 #define XT_COLOR_RED "#cc0000"
359 #define XT_COLOR_YELLOW "#ffff66"
360 #define XT_COLOR_BLUE "lightblue"
361 #define XT_COLOR_GREEN "#99ff66"
362 #define XT_COLOR_WHITE "white"
363 #define XT_COLOR_BLACK "black"
365 #define XT_COLOR_CT_BACKGROUND "#000000"
366 #define XT_COLOR_CT_INACTIVE "#dddddd"
367 #define XT_COLOR_CT_ACTIVE "#bbbb00"
368 #define XT_COLOR_CT_SEPARATOR "#555555"
370 #define XT_COLOR_SB_SEPARATOR "#555555"
372 #define XT_PROTO_DELIM "://"
375 * xxxterm "protocol" (xtp)
376 * We use this for managing stuff like downloads and favorites. They
377 * make magical HTML pages in memory which have xxxt:// links in order
378 * to communicate with xxxterm's internals. These links take the format:
379 * xxxt://class/session_key/action/arg
381 * Don't begin xtp class/actions as 0. atoi returns that on error.
383 * Typically we have not put addition of items in this framework, as
384 * adding items is either done via an ex-command or via a keybinding instead.
387 #define XT_XTP_STR "xxxt://"
389 /* XTP classes (xxxt://<class>) */
390 #define XT_XTP_INVALID 0 /* invalid */
391 #define XT_XTP_DL 1 /* downloads */
392 #define XT_XTP_HL 2 /* history */
393 #define XT_XTP_CL 3 /* cookies */
394 #define XT_XTP_FL 4 /* favorites */
396 /* XTP download actions */
397 #define XT_XTP_DL_LIST 1
398 #define XT_XTP_DL_CANCEL 2
399 #define XT_XTP_DL_REMOVE 3
401 /* XTP history actions */
402 #define XT_XTP_HL_LIST 1
403 #define XT_XTP_HL_REMOVE 2
405 /* XTP cookie actions */
406 #define XT_XTP_CL_LIST 1
407 #define XT_XTP_CL_REMOVE 2
409 /* XTP cookie actions */
410 #define XT_XTP_FL_LIST 1
411 #define XT_XTP_FL_REMOVE 2
413 /* actions */
414 #define XT_MOVE_INVALID (0)
415 #define XT_MOVE_DOWN (1)
416 #define XT_MOVE_UP (2)
417 #define XT_MOVE_BOTTOM (3)
418 #define XT_MOVE_TOP (4)
419 #define XT_MOVE_PAGEDOWN (5)
420 #define XT_MOVE_PAGEUP (6)
421 #define XT_MOVE_HALFDOWN (7)
422 #define XT_MOVE_HALFUP (8)
423 #define XT_MOVE_LEFT (9)
424 #define XT_MOVE_FARLEFT (10)
425 #define XT_MOVE_RIGHT (11)
426 #define XT_MOVE_FARRIGHT (12)
427 #define XT_MOVE_PERCENT (13)
429 #define XT_QMARK_SET (0)
430 #define XT_QMARK_OPEN (1)
431 #define XT_QMARK_TAB (2)
433 #define XT_MARK_SET (0)
434 #define XT_MARK_GOTO (1)
436 #define XT_TAB_LAST (-4)
437 #define XT_TAB_FIRST (-3)
438 #define XT_TAB_PREV (-2)
439 #define XT_TAB_NEXT (-1)
440 #define XT_TAB_INVALID (0)
441 #define XT_TAB_NEW (1)
442 #define XT_TAB_DELETE (2)
443 #define XT_TAB_DELQUIT (3)
444 #define XT_TAB_OPEN (4)
445 #define XT_TAB_UNDO_CLOSE (5)
446 #define XT_TAB_SHOW (6)
447 #define XT_TAB_HIDE (7)
448 #define XT_TAB_NEXTSTYLE (8)
450 #define XT_NAV_INVALID (0)
451 #define XT_NAV_BACK (1)
452 #define XT_NAV_FORWARD (2)
453 #define XT_NAV_RELOAD (3)
455 #define XT_FOCUS_INVALID (0)
456 #define XT_FOCUS_URI (1)
457 #define XT_FOCUS_SEARCH (2)
459 #define XT_SEARCH_INVALID (0)
460 #define XT_SEARCH_NEXT (1)
461 #define XT_SEARCH_PREV (2)
463 #define XT_PASTE_CURRENT_TAB (0)
464 #define XT_PASTE_NEW_TAB (1)
466 #define XT_ZOOM_IN (-1)
467 #define XT_ZOOM_OUT (-2)
468 #define XT_ZOOM_NORMAL (100)
470 #define XT_URL_SHOW (1)
471 #define XT_URL_HIDE (2)
473 #define XT_WL_TOGGLE (1<<0)
474 #define XT_WL_ENABLE (1<<1)
475 #define XT_WL_DISABLE (1<<2)
476 #define XT_WL_FQDN (1<<3) /* default */
477 #define XT_WL_TOPLEVEL (1<<4)
478 #define XT_WL_PERSISTENT (1<<5)
479 #define XT_WL_SESSION (1<<6)
480 #define XT_WL_RELOAD (1<<7)
482 #define XT_SHOW (1<<7)
483 #define XT_DELETE (1<<8)
484 #define XT_SAVE (1<<9)
485 #define XT_OPEN (1<<10)
487 #define XT_CMD_OPEN (0)
488 #define XT_CMD_OPEN_CURRENT (1)
489 #define XT_CMD_TABNEW (2)
490 #define XT_CMD_TABNEW_CURRENT (3)
492 #define XT_STATUS_NOTHING (0)
493 #define XT_STATUS_LINK (1)
494 #define XT_STATUS_URI (2)
495 #define XT_STATUS_LOADING (3)
497 #define XT_SES_DONOTHING (0)
498 #define XT_SES_CLOSETABS (1)
500 #define XT_BM_NORMAL (0)
501 #define XT_BM_WHITELIST (1)
502 #define XT_BM_KIOSK (2)
504 #define XT_PREFIX (1<<0)
505 #define XT_USERARG (1<<1)
506 #define XT_URLARG (1<<2)
507 #define XT_INTARG (1<<3)
509 #define XT_TABS_NORMAL 0
510 #define XT_TABS_COMPACT 1
512 #define XT_BUFCMD_SZ (8)
514 /* mime types */
515 struct mime_type {
516 char *mt_type;
517 char *mt_action;
518 int mt_default;
519 int mt_download;
520 TAILQ_ENTRY(mime_type) entry;
522 TAILQ_HEAD(mime_type_list, mime_type);
524 /* uri aliases */
525 struct alias {
526 char *a_name;
527 char *a_uri;
528 TAILQ_ENTRY(alias) entry;
530 TAILQ_HEAD(alias_list, alias);
532 /* settings that require restart */
533 int tabless = 0; /* allow only 1 tab */
534 int enable_socket = 0;
535 int single_instance = 0; /* only allow one xxxterm to run */
536 int fancy_bar = 1; /* fancy toolbar */
537 int browser_mode = XT_BM_NORMAL;
538 int enable_localstorage = 0;
539 char *statusbar_elems = NULL;
541 /* runtime settings */
542 int show_tabs = 1; /* show tabs on notebook */
543 int tab_style = XT_TABS_NORMAL; /* tab bar style */
544 int show_url = 1; /* show url toolbar on notebook */
545 int show_statusbar = 0; /* vimperator style status bar */
546 int ctrl_click_focus = 0; /* ctrl click gets focus */
547 int cookies_enabled = 1; /* enable cookies */
548 int read_only_cookies = 0; /* enable to not write cookies */
549 int enable_scripts = 1;
550 int enable_plugins = 0;
551 gfloat default_zoom_level = 1.0;
552 char default_script[PATH_MAX];
553 int window_height = 768;
554 int window_width = 1024;
555 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
556 int refresh_interval = 10; /* download refresh interval */
557 int enable_cookie_whitelist = 0;
558 int enable_js_whitelist = 0;
559 int session_timeout = 3600; /* cookie session timeout */
560 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
561 char *ssl_ca_file = NULL;
562 char *resource_dir = NULL;
563 gboolean ssl_strict_certs = FALSE;
564 int append_next = 1; /* append tab after current tab */
565 char *home = NULL;
566 char *search_string = NULL;
567 char *http_proxy = NULL;
568 char download_dir[PATH_MAX];
569 char runtime_settings[PATH_MAX]; /* override of settings */
570 int allow_volatile_cookies = 0;
571 int save_global_history = 0; /* save global history to disk */
572 char *user_agent = NULL;
573 int save_rejected_cookies = 0;
574 int session_autosave = 0;
575 int guess_search = 0;
576 int dns_prefetch = FALSE;
577 gint max_connections = 25;
578 gint max_host_connections = 5;
579 gint enable_spell_checking = 0;
580 char *spell_check_languages = NULL;
581 int xterm_workaround = 0;
582 char *url_regex = NULL;
584 char *cmd_font_name = NULL;
585 char *oops_font_name = NULL;
586 char *statusbar_font_name = NULL;
587 char *tabbar_font_name = NULL;
588 PangoFontDescription *cmd_font;
589 PangoFontDescription *oops_font;
590 PangoFontDescription *statusbar_font;
591 PangoFontDescription *tabbar_font;
592 char *qmarks[XT_NOMARKS];
594 int btn_down; /* M1 down in any wv */
595 regex_t url_re; /* guess_search regex */
597 struct settings;
598 struct key_binding;
599 int set_browser_mode(struct settings *, char *);
600 int set_cookie_policy(struct settings *, char *);
601 int set_download_dir(struct settings *, char *);
602 int set_default_script(struct settings *, char *);
603 int set_runtime_dir(struct settings *, char *);
604 int set_tab_style(struct settings *, char *);
605 int set_work_dir(struct settings *, char *);
606 int add_alias(struct settings *, char *);
607 int add_mime_type(struct settings *, char *);
608 int add_cookie_wl(struct settings *, char *);
609 int add_js_wl(struct settings *, char *);
610 int add_kb(struct settings *, char *);
611 void button_set_stockid(GtkWidget *, char *);
612 GtkWidget * create_button(char *, char *, int);
614 char *get_browser_mode(struct settings *);
615 char *get_cookie_policy(struct settings *);
616 char *get_download_dir(struct settings *);
617 char *get_default_script(struct settings *);
618 char *get_runtime_dir(struct settings *);
619 char *get_tab_style(struct settings *);
620 char *get_work_dir(struct settings *);
621 void startpage_add(const char *, ...);
623 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
624 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
625 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
626 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
627 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
629 void recalc_tabs(void);
630 void recolor_compact_tabs(void);
631 void set_current_tab(int page_num);
632 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
633 void marks_clear(struct tab *t);
635 int set_http_proxy(char *);
637 struct special {
638 int (*set)(struct settings *, char *);
639 char *(*get)(struct settings *);
640 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
643 struct special s_browser_mode = {
644 set_browser_mode,
645 get_browser_mode,
646 NULL
649 struct special s_cookie = {
650 set_cookie_policy,
651 get_cookie_policy,
652 NULL
655 struct special s_alias = {
656 add_alias,
657 NULL,
658 walk_alias
661 struct special s_mime = {
662 add_mime_type,
663 NULL,
664 walk_mime_type
667 struct special s_js = {
668 add_js_wl,
669 NULL,
670 walk_js_wl
673 struct special s_kb = {
674 add_kb,
675 NULL,
676 walk_kb
679 struct special s_cookie_wl = {
680 add_cookie_wl,
681 NULL,
682 walk_cookie_wl
685 struct special s_default_script = {
686 set_default_script,
687 get_default_script,
688 NULL
691 struct special s_download_dir = {
692 set_download_dir,
693 get_download_dir,
694 NULL
697 struct special s_work_dir = {
698 set_work_dir,
699 get_work_dir,
700 NULL
703 struct special s_tab_style = {
704 set_tab_style,
705 get_tab_style,
706 NULL
709 struct settings {
710 char *name;
711 int type;
712 #define XT_S_INVALID (0)
713 #define XT_S_INT (1)
714 #define XT_S_STR (2)
715 #define XT_S_FLOAT (3)
716 uint32_t flags;
717 #define XT_SF_RESTART (1<<0)
718 #define XT_SF_RUNTIME (1<<1)
719 int *ival;
720 char **sval;
721 struct special *s;
722 gfloat *fval;
723 int (*activate)(char *);
724 } rs[] = {
725 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
726 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
727 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
728 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
729 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
730 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
731 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
732 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
733 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
734 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
735 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
736 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
737 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
738 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
739 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
740 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
741 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
742 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
743 { "home", XT_S_STR, 0, NULL, &home, NULL },
744 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
745 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
746 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
747 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
748 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
749 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
750 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
751 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
752 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
753 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
754 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
755 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
756 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
757 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
758 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
759 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
760 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
761 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
762 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
763 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
764 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
765 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
766 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
767 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
768 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
769 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
770 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
772 /* font settings */
773 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
774 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
775 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
776 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
778 /* runtime settings */
779 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
780 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
781 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
782 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
783 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
786 int about(struct tab *, struct karg *);
787 int blank(struct tab *, struct karg *);
788 int ca_cmd(struct tab *, struct karg *);
789 int cookie_show_wl(struct tab *, struct karg *);
790 int js_show_wl(struct tab *, struct karg *);
791 int help(struct tab *, struct karg *);
792 int set(struct tab *, struct karg *);
793 int stats(struct tab *, struct karg *);
794 int marco(struct tab *, struct karg *);
795 int startpage(struct tab *, struct karg *);
796 const char * marco_message(int *);
797 int xtp_page_cl(struct tab *, struct karg *);
798 int xtp_page_dl(struct tab *, struct karg *);
799 int xtp_page_fl(struct tab *, struct karg *);
800 int xtp_page_hl(struct tab *, struct karg *);
801 void xt_icon_from_file(struct tab *, char *);
802 const gchar *get_uri(struct tab *);
803 const gchar *get_title(struct tab *, bool);
805 #define XT_URI_ABOUT ("about:")
806 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
807 #define XT_URI_ABOUT_ABOUT ("about")
808 #define XT_URI_ABOUT_BLANK ("blank")
809 #define XT_URI_ABOUT_CERTS ("certs")
810 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
811 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
812 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
813 #define XT_URI_ABOUT_FAVORITES ("favorites")
814 #define XT_URI_ABOUT_HELP ("help")
815 #define XT_URI_ABOUT_HISTORY ("history")
816 #define XT_URI_ABOUT_JSWL ("jswl")
817 #define XT_URI_ABOUT_SET ("set")
818 #define XT_URI_ABOUT_STATS ("stats")
819 #define XT_URI_ABOUT_MARCO ("marco")
820 #define XT_URI_ABOUT_STARTPAGE ("startpage")
822 struct about_type {
823 char *name;
824 int (*func)(struct tab *, struct karg *);
825 } about_list[] = {
826 { XT_URI_ABOUT_ABOUT, about },
827 { XT_URI_ABOUT_BLANK, blank },
828 { XT_URI_ABOUT_CERTS, ca_cmd },
829 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
830 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
831 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
832 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
833 { XT_URI_ABOUT_HELP, help },
834 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
835 { XT_URI_ABOUT_JSWL, js_show_wl },
836 { XT_URI_ABOUT_SET, set },
837 { XT_URI_ABOUT_STATS, stats },
838 { XT_URI_ABOUT_MARCO, marco },
839 { XT_URI_ABOUT_STARTPAGE, startpage },
842 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
843 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
844 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
845 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
846 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
847 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
848 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
850 /* globals */
851 extern char *__progname;
852 char **start_argv;
853 struct passwd *pwd;
854 GtkWidget *main_window;
855 GtkNotebook *notebook;
856 GtkWidget *tab_bar;
857 GtkWidget *arrow, *abtn;
858 struct tab_list tabs;
859 struct history_list hl;
860 struct download_list downloads;
861 struct domain_list c_wl;
862 struct domain_list js_wl;
863 struct undo_tailq undos;
864 struct keybinding_list kbl;
865 struct sp_list spl;
866 struct command_list chl;
867 struct command_entry *history_at;
868 int undo_count;
869 int updating_dl_tabs = 0;
870 int updating_hl_tabs = 0;
871 int updating_cl_tabs = 0;
872 int updating_fl_tabs = 0;
873 int cmd_history_count = 0;
874 char *global_search;
875 uint64_t blocked_cookies = 0;
876 char named_session[PATH_MAX];
877 GtkListStore *completion_model;
878 GtkListStore *buffers_store;
880 void xxx_dir(char *);
881 int icon_size_map(int);
882 void completion_add(struct tab *);
883 void completion_add_uri(const gchar *);
885 void
886 cmd_history_delete(void)
888 struct command_entry *c;
890 c = TAILQ_LAST(&chl, command_list);
891 if (c == NULL)
892 return;
894 TAILQ_REMOVE(&chl, c, entry);
895 cmd_history_count--;
896 g_free(c->line);
897 g_free(c);
900 void
901 cmd_history_add(char *l)
903 struct command_entry *c;
905 if (l == NULL)
906 return;
908 c = g_malloc0(sizeof *c);
909 c->line = g_strdup_printf(":%s", l);
911 cmd_history_count++;
912 TAILQ_INSERT_HEAD(&chl, c, entry);
914 if (cmd_history_count > 1000)
915 cmd_history_delete();
918 /* marks and quickmarks array storage.
919 * first a-z, then A-Z, then 0-9 */
920 char
921 indextomark(int i)
923 if (i < 0)
924 return 0;
926 if (i >= 0 && i <= 'z' - 'a')
927 return 'a' + i;
929 i -= 'z' - 'a' + 1;
930 if (i >= 0 && i <= 'Z' - 'A')
931 return 'A' + i;
933 i -= 'Z' - 'A' + 1;
934 if (i >= 10)
935 return 0;
937 return i + '0';
941 marktoindex(char m)
943 int ret = 0;
945 if (m >= 'a' && m <= 'z')
946 return ret + m - 'a';
948 ret += 'z' - 'a' + 1;
949 if (m >= 'A' && m <= 'Z')
950 return ret + m - 'A';
952 ret += 'Z' - 'A' + 1;
953 if (m >= '0' && m <= '9')
954 return ret + m - '0';
956 return -1;
960 void
961 sigchild(int sig)
963 int saved_errno, status;
964 pid_t pid;
966 saved_errno = errno;
968 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
969 if (pid == -1) {
970 if (errno == EINTR)
971 continue;
972 if (errno != ECHILD) {
974 clog_warn("sigchild: waitpid:");
977 break;
980 if (WIFEXITED(status)) {
981 if (WEXITSTATUS(status) != 0) {
983 clog_warnx("sigchild: child exit status: %d",
984 WEXITSTATUS(status));
987 } else {
989 clog_warnx("sigchild: child is terminated abnormally");
994 errno = saved_errno;
998 is_g_object_setting(GObject *o, char *str)
1000 guint n_props = 0, i;
1001 GParamSpec **proplist;
1003 if (! G_IS_OBJECT(o))
1004 return (0);
1006 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1007 &n_props);
1009 for (i=0; i < n_props; i++) {
1010 if (! strcmp(proplist[i]->name, str))
1011 return (1);
1013 return (0);
1016 gchar *
1017 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1019 gchar *r;
1021 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1022 "<head>\n"
1023 "<title>%s</title>\n"
1024 "%s"
1025 "%s"
1026 "</head>\n"
1027 "<body>\n"
1028 "<h1>%s</h1>\n"
1029 "%s\n</body>\n"
1030 "</html>",
1031 title,
1032 addstyles ? XT_PAGE_STYLE : "",
1033 head,
1034 title,
1035 body);
1037 return r;
1041 * Display a web page from a HTML string in memory, rather than from a URL
1043 void
1044 load_webkit_string(struct tab *t, const char *str, gchar *title)
1046 char file[PATH_MAX];
1047 int i;
1049 /* we set this to indicate we want to manually do navaction */
1050 if (t->bfl)
1051 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1053 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1054 if (title) {
1055 /* set t->xtp_meaning */
1056 for (i = 0; i < LENGTH(about_list); i++)
1057 if (!strcmp(title, about_list[i].name)) {
1058 t->xtp_meaning = i;
1059 break;
1062 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1063 #if GTK_CHECK_VERSION(2, 20, 0)
1064 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1065 gtk_widget_hide(t->spinner);
1066 #endif
1067 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1068 xt_icon_from_file(t, file);
1072 struct tab *
1073 get_current_tab(void)
1075 struct tab *t;
1077 TAILQ_FOREACH(t, &tabs, entry) {
1078 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1079 return (t);
1082 warnx("%s: no current tab", __func__);
1084 return (NULL);
1087 void
1088 set_status(struct tab *t, gchar *s, int status)
1090 gchar *type = NULL;
1092 if (s == NULL)
1093 return;
1095 switch (status) {
1096 case XT_STATUS_LOADING:
1097 type = g_strdup_printf("Loading: %s", s);
1098 s = type;
1099 break;
1100 case XT_STATUS_LINK:
1101 type = g_strdup_printf("Link: %s", s);
1102 if (!t->status)
1103 t->status = g_strdup(gtk_entry_get_text(
1104 GTK_ENTRY(t->sbe.statusbar)));
1105 s = type;
1106 break;
1107 case XT_STATUS_URI:
1108 type = g_strdup_printf("%s", s);
1109 if (!t->status) {
1110 t->status = g_strdup(type);
1112 s = type;
1113 if (!t->status)
1114 t->status = g_strdup(s);
1115 break;
1116 case XT_STATUS_NOTHING:
1117 /* FALL THROUGH */
1118 default:
1119 break;
1121 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1122 if (type)
1123 g_free(type);
1126 void
1127 hide_cmd(struct tab *t)
1129 history_at = NULL; /* just in case */
1130 gtk_widget_hide(t->cmd);
1133 void
1134 show_cmd(struct tab *t)
1136 history_at = NULL;
1138 gtk_widget_hide(t->oops);
1139 gtk_widget_show(t->cmd);
1142 void
1143 hide_buffers(struct tab *t)
1145 gtk_widget_hide(t->buffers);
1146 gtk_list_store_clear(buffers_store);
1149 enum {
1150 COL_ID = 0,
1151 COL_TITLE,
1152 NUM_COLS
1156 sort_tabs_by_page_num(struct tab ***stabs)
1158 int num_tabs = 0;
1159 struct tab *t;
1161 num_tabs = gtk_notebook_get_n_pages(notebook);
1163 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1165 TAILQ_FOREACH(t, &tabs, entry)
1166 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1168 return (num_tabs);
1171 void
1172 buffers_make_list(void)
1174 int i, num_tabs;
1175 const gchar *title = NULL;
1176 GtkTreeIter iter;
1177 struct tab **stabs = NULL;
1179 num_tabs = sort_tabs_by_page_num(&stabs);
1181 for (i = 0; i < num_tabs; i++)
1182 if (stabs[i]) {
1183 gtk_list_store_append(buffers_store, &iter);
1184 title = get_title(stabs[i], FALSE);
1185 gtk_list_store_set(buffers_store, &iter,
1186 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1187 * rather than 0. */
1188 COL_TITLE, title,
1189 -1);
1192 g_free(stabs);
1195 void
1196 show_buffers(struct tab *t)
1198 buffers_make_list();
1199 gtk_widget_show(t->buffers);
1200 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1203 void
1204 toggle_buffers(struct tab *t)
1206 if (gtk_widget_get_visible(t->buffers))
1207 hide_buffers(t);
1208 else
1209 show_buffers(t);
1213 buffers(struct tab *t, struct karg *args)
1215 show_buffers(t);
1217 return (0);
1220 void
1221 hide_oops(struct tab *t)
1223 gtk_widget_hide(t->oops);
1226 void
1227 show_oops(struct tab *at, const char *fmt, ...)
1229 va_list ap;
1230 char *msg = NULL;
1231 struct tab *t = NULL;
1233 if (fmt == NULL)
1234 return;
1236 if (at == NULL) {
1237 if ((t = get_current_tab()) == NULL)
1238 return;
1239 } else
1240 t = at;
1242 va_start(ap, fmt);
1243 if (vasprintf(&msg, fmt, ap) == -1)
1244 errx(1, "show_oops failed");
1245 va_end(ap);
1247 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1248 gtk_widget_hide(t->cmd);
1249 gtk_widget_show(t->oops);
1251 if (msg)
1252 free(msg);
1255 char *
1256 get_as_string(struct settings *s)
1258 char *r = NULL;
1260 if (s == NULL)
1261 return (NULL);
1263 if (s->s) {
1264 if (s->s->get)
1265 r = s->s->get(s);
1266 else
1267 warnx("get_as_string skip %s\n", s->name);
1268 } else if (s->type == XT_S_INT)
1269 r = g_strdup_printf("%d", *s->ival);
1270 else if (s->type == XT_S_STR)
1271 r = g_strdup(*s->sval);
1272 else if (s->type == XT_S_FLOAT)
1273 r = g_strdup_printf("%f", *s->fval);
1274 else
1275 r = g_strdup_printf("INVALID TYPE");
1277 return (r);
1280 void
1281 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1283 int i;
1284 char *s;
1286 for (i = 0; i < LENGTH(rs); i++) {
1287 if (rs[i].s && rs[i].s->walk)
1288 rs[i].s->walk(&rs[i], cb, cb_args);
1289 else {
1290 s = get_as_string(&rs[i]);
1291 cb(&rs[i], s, cb_args);
1292 g_free(s);
1298 set_browser_mode(struct settings *s, char *val)
1300 if (!strcmp(val, "whitelist")) {
1301 browser_mode = XT_BM_WHITELIST;
1302 allow_volatile_cookies = 0;
1303 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1304 cookies_enabled = 1;
1305 enable_cookie_whitelist = 1;
1306 read_only_cookies = 0;
1307 save_rejected_cookies = 0;
1308 session_timeout = 3600;
1309 enable_scripts = 0;
1310 enable_js_whitelist = 1;
1311 enable_localstorage = 0;
1312 } else if (!strcmp(val, "normal")) {
1313 browser_mode = XT_BM_NORMAL;
1314 allow_volatile_cookies = 0;
1315 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1316 cookies_enabled = 1;
1317 enable_cookie_whitelist = 0;
1318 read_only_cookies = 0;
1319 save_rejected_cookies = 0;
1320 session_timeout = 3600;
1321 enable_scripts = 1;
1322 enable_js_whitelist = 0;
1323 enable_localstorage = 1;
1324 } else if (!strcmp(val, "kiosk")) {
1325 browser_mode = XT_BM_KIOSK;
1326 allow_volatile_cookies = 0;
1327 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1328 cookies_enabled = 1;
1329 enable_cookie_whitelist = 0;
1330 read_only_cookies = 0;
1331 save_rejected_cookies = 0;
1332 session_timeout = 3600;
1333 enable_scripts = 1;
1334 enable_js_whitelist = 0;
1335 enable_localstorage = 1;
1336 show_tabs = 0;
1337 tabless = 1;
1338 } else
1339 return (1);
1341 return (0);
1344 char *
1345 get_browser_mode(struct settings *s)
1347 char *r = NULL;
1349 if (browser_mode == XT_BM_WHITELIST)
1350 r = g_strdup("whitelist");
1351 else if (browser_mode == XT_BM_NORMAL)
1352 r = g_strdup("normal");
1353 else if (browser_mode == XT_BM_KIOSK)
1354 r = g_strdup("kiosk");
1355 else
1356 return (NULL);
1358 return (r);
1362 set_cookie_policy(struct settings *s, char *val)
1364 if (!strcmp(val, "no3rdparty"))
1365 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1366 else if (!strcmp(val, "accept"))
1367 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1368 else if (!strcmp(val, "reject"))
1369 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1370 else
1371 return (1);
1373 return (0);
1376 char *
1377 get_cookie_policy(struct settings *s)
1379 char *r = NULL;
1381 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1382 r = g_strdup("no3rdparty");
1383 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1384 r = g_strdup("accept");
1385 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1386 r = g_strdup("reject");
1387 else
1388 return (NULL);
1390 return (r);
1393 char *
1394 get_default_script(struct settings *s)
1396 if (default_script[0] == '\0')
1397 return (0);
1398 return (g_strdup(default_script));
1402 set_default_script(struct settings *s, char *val)
1404 if (val[0] == '~')
1405 snprintf(default_script, sizeof default_script, "%s/%s",
1406 pwd->pw_dir, &val[1]);
1407 else
1408 strlcpy(default_script, val, sizeof default_script);
1410 return (0);
1413 char *
1414 get_download_dir(struct settings *s)
1416 if (download_dir[0] == '\0')
1417 return (0);
1418 return (g_strdup(download_dir));
1422 set_download_dir(struct settings *s, char *val)
1424 if (val[0] == '~')
1425 snprintf(download_dir, sizeof download_dir, "%s/%s",
1426 pwd->pw_dir, &val[1]);
1427 else
1428 strlcpy(download_dir, val, sizeof download_dir);
1430 return (0);
1434 * Session IDs.
1435 * We use these to prevent people putting xxxt:// URLs on
1436 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1438 #define XT_XTP_SES_KEY_SZ 8
1439 #define XT_XTP_SES_KEY_HEX_FMT \
1440 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1441 char *dl_session_key; /* downloads */
1442 char *hl_session_key; /* history list */
1443 char *cl_session_key; /* cookie list */
1444 char *fl_session_key; /* favorites list */
1446 char work_dir[PATH_MAX];
1447 char certs_dir[PATH_MAX];
1448 char cache_dir[PATH_MAX];
1449 char sessions_dir[PATH_MAX];
1450 char cookie_file[PATH_MAX];
1451 SoupURI *proxy_uri = NULL;
1452 SoupSession *session;
1453 SoupCookieJar *s_cookiejar;
1454 SoupCookieJar *p_cookiejar;
1455 char rc_fname[PATH_MAX];
1457 struct mime_type_list mtl;
1458 struct alias_list aliases;
1460 /* protos */
1461 struct tab *create_new_tab(char *, struct undo *, int, int);
1462 void delete_tab(struct tab *);
1463 void setzoom_webkit(struct tab *, int);
1464 int run_script(struct tab *, char *);
1465 int download_rb_cmp(struct download *, struct download *);
1466 gboolean cmd_execute(struct tab *t, char *str);
1469 history_rb_cmp(struct history *h1, struct history *h2)
1471 return (strcmp(h1->uri, h2->uri));
1473 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1476 domain_rb_cmp(struct domain *d1, struct domain *d2)
1478 return (strcmp(d1->d, d2->d));
1480 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1482 char *
1483 get_work_dir(struct settings *s)
1485 if (work_dir[0] == '\0')
1486 return (0);
1487 return (g_strdup(work_dir));
1491 set_work_dir(struct settings *s, char *val)
1493 if (val[0] == '~')
1494 snprintf(work_dir, sizeof work_dir, "%s/%s",
1495 pwd->pw_dir, &val[1]);
1496 else
1497 strlcpy(work_dir, val, sizeof work_dir);
1499 return (0);
1502 char *
1503 get_tab_style(struct settings *s)
1505 if (tab_style == XT_TABS_NORMAL)
1506 return (g_strdup("normal"));
1507 else
1508 return (g_strdup("compact"));
1512 set_tab_style(struct settings *s, char *val)
1514 if (!strcmp(val, "normal"))
1515 tab_style = XT_TABS_NORMAL;
1516 else if (!strcmp(val, "compact"))
1517 tab_style = XT_TABS_COMPACT;
1518 else
1519 return (1);
1521 return (0);
1525 * generate a session key to secure xtp commands.
1526 * pass in a ptr to the key in question and it will
1527 * be modified in place.
1529 void
1530 generate_xtp_session_key(char **key)
1532 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1534 /* free old key */
1535 if (*key)
1536 g_free(*key);
1538 /* make a new one */
1539 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1540 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1541 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1542 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1544 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1548 * validate a xtp session key.
1549 * return 1 if OK
1552 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1554 if (strcmp(trusted, untrusted) != 0) {
1555 show_oops(t, "%s: xtp session key mismatch possible spoof",
1556 __func__);
1557 return (0);
1560 return (1);
1564 download_rb_cmp(struct download *e1, struct download *e2)
1566 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1568 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1570 struct valid_url_types {
1571 char *type;
1572 } vut[] = {
1573 { "http://" },
1574 { "https://" },
1575 { "ftp://" },
1576 { "file://" },
1577 { XT_XTP_STR },
1581 valid_url_type(char *url)
1583 int i;
1585 for (i = 0; i < LENGTH(vut); i++)
1586 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1587 return (0);
1589 return (1);
1592 void
1593 print_cookie(char *msg, SoupCookie *c)
1595 if (c == NULL)
1596 return;
1598 if (msg)
1599 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1600 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1601 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1602 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1603 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1604 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1605 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1606 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1607 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1608 DNPRINTF(XT_D_COOKIE, "====================================\n");
1611 void
1612 walk_alias(struct settings *s,
1613 void (*cb)(struct settings *, char *, void *), void *cb_args)
1615 struct alias *a;
1616 char *str;
1618 if (s == NULL || cb == NULL) {
1619 show_oops(NULL, "walk_alias invalid parameters");
1620 return;
1623 TAILQ_FOREACH(a, &aliases, entry) {
1624 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1625 cb(s, str, cb_args);
1626 g_free(str);
1630 char *
1631 match_alias(char *url_in)
1633 struct alias *a;
1634 char *arg;
1635 char *url_out = NULL, *search, *enc_arg;
1637 search = g_strdup(url_in);
1638 arg = search;
1639 if (strsep(&arg, " \t") == NULL) {
1640 show_oops(NULL, "match_alias: NULL URL");
1641 goto done;
1644 TAILQ_FOREACH(a, &aliases, entry) {
1645 if (!strcmp(search, a->a_name))
1646 break;
1649 if (a != NULL) {
1650 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1651 a->a_name);
1652 if (arg != NULL) {
1653 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1654 url_out = g_strdup_printf(a->a_uri, enc_arg);
1655 g_free(enc_arg);
1656 } else
1657 url_out = g_strdup_printf(a->a_uri, "");
1659 done:
1660 g_free(search);
1661 return (url_out);
1664 char *
1665 guess_url_type(char *url_in)
1667 struct stat sb;
1668 char *url_out = NULL, *enc_search = NULL;
1670 url_out = match_alias(url_in);
1671 if (url_out != NULL)
1672 return (url_out);
1674 if (guess_search && url_regex &&
1675 !(g_str_has_prefix(url_in, "http://") ||
1676 g_str_has_prefix(url_in, "https://"))) {
1677 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1678 /* invalid URI so search instead */
1679 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1680 url_out = g_strdup_printf(search_string, enc_search);
1681 g_free(enc_search);
1682 goto done;
1686 /* XXX not sure about this heuristic */
1687 if (stat(url_in, &sb) == 0)
1688 url_out = g_strdup_printf("file://%s", url_in);
1689 else
1690 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1691 done:
1692 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1694 return (url_out);
1697 void
1698 load_uri(struct tab *t, gchar *uri)
1700 struct karg args;
1701 gchar *newuri = NULL;
1702 int i;
1704 if (uri == NULL)
1705 return;
1707 /* Strip leading spaces. */
1708 while (*uri && isspace(*uri))
1709 uri++;
1711 if (strlen(uri) == 0) {
1712 blank(t, NULL);
1713 return;
1716 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1718 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1719 for (i = 0; i < LENGTH(about_list); i++)
1720 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1721 bzero(&args, sizeof args);
1722 about_list[i].func(t, &args);
1723 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1724 FALSE);
1725 return;
1727 show_oops(t, "invalid about page");
1728 return;
1731 if (valid_url_type(uri)) {
1732 newuri = guess_url_type(uri);
1733 uri = newuri;
1736 set_status(t, (char *)uri, XT_STATUS_LOADING);
1737 marks_clear(t);
1738 webkit_web_view_load_uri(t->wv, uri);
1740 if (newuri)
1741 g_free(newuri);
1744 const gchar *
1745 get_uri(struct tab *t)
1747 const gchar *uri = NULL;
1749 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1750 return t->tmp_uri;
1751 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1752 uri = webkit_web_view_get_uri(t->wv);
1753 } else {
1754 /* use tmp_uri to make sure it is g_freed */
1755 if (t->tmp_uri)
1756 g_free(t->tmp_uri);
1757 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1758 about_list[t->xtp_meaning].name);
1759 uri = t->tmp_uri;
1761 return uri;
1764 const gchar *
1765 get_title(struct tab *t, bool window)
1767 const gchar *set = NULL, *title = NULL;
1768 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1770 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1771 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1772 goto notitle;
1774 title = webkit_web_view_get_title(t->wv);
1775 if ((set = title ? title : get_uri(t)))
1776 return set;
1778 notitle:
1779 set = window ? XT_NAME : "(untitled)";
1781 return set;
1785 add_alias(struct settings *s, char *line)
1787 char *l, *alias;
1788 struct alias *a = NULL;
1790 if (s == NULL || line == NULL) {
1791 show_oops(NULL, "add_alias invalid parameters");
1792 return (1);
1795 l = line;
1796 a = g_malloc(sizeof(*a));
1798 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1799 show_oops(NULL, "add_alias: incomplete alias definition");
1800 goto bad;
1802 if (strlen(alias) == 0 || strlen(l) == 0) {
1803 show_oops(NULL, "add_alias: invalid alias definition");
1804 goto bad;
1807 a->a_name = g_strdup(alias);
1808 a->a_uri = g_strdup(l);
1810 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1812 TAILQ_INSERT_TAIL(&aliases, a, entry);
1814 return (0);
1815 bad:
1816 if (a)
1817 g_free(a);
1818 return (1);
1822 add_mime_type(struct settings *s, char *line)
1824 char *mime_type;
1825 char *l;
1826 struct mime_type *m = NULL;
1827 int downloadfirst = 0;
1829 /* XXX this could be smarter */
1831 if (line == NULL || strlen(line) == 0) {
1832 show_oops(NULL, "add_mime_type invalid parameters");
1833 return (1);
1836 l = line;
1837 if (*l == '@') {
1838 downloadfirst = 1;
1839 l++;
1841 m = g_malloc(sizeof(*m));
1843 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1844 show_oops(NULL, "add_mime_type: invalid mime_type");
1845 goto bad;
1847 if (mime_type[strlen(mime_type) - 1] == '*') {
1848 mime_type[strlen(mime_type) - 1] = '\0';
1849 m->mt_default = 1;
1850 } else
1851 m->mt_default = 0;
1853 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1854 show_oops(NULL, "add_mime_type: invalid mime_type");
1855 goto bad;
1858 m->mt_type = g_strdup(mime_type);
1859 m->mt_action = g_strdup(l);
1860 m->mt_download = downloadfirst;
1862 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1863 m->mt_type, m->mt_action, m->mt_default);
1865 TAILQ_INSERT_TAIL(&mtl, m, entry);
1867 return (0);
1868 bad:
1869 if (m)
1870 g_free(m);
1871 return (1);
1874 struct mime_type *
1875 find_mime_type(char *mime_type)
1877 struct mime_type *m, *def = NULL, *rv = NULL;
1879 TAILQ_FOREACH(m, &mtl, entry) {
1880 if (m->mt_default &&
1881 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1882 def = m;
1884 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1885 rv = m;
1886 break;
1890 if (rv == NULL)
1891 rv = def;
1893 return (rv);
1896 void
1897 walk_mime_type(struct settings *s,
1898 void (*cb)(struct settings *, char *, void *), void *cb_args)
1900 struct mime_type *m;
1901 char *str;
1903 if (s == NULL || cb == NULL) {
1904 show_oops(NULL, "walk_mime_type invalid parameters");
1905 return;
1908 TAILQ_FOREACH(m, &mtl, entry) {
1909 str = g_strdup_printf("%s%s --> %s",
1910 m->mt_type,
1911 m->mt_default ? "*" : "",
1912 m->mt_action);
1913 cb(s, str, cb_args);
1914 g_free(str);
1918 void
1919 wl_add(char *str, struct domain_list *wl, int handy)
1921 struct domain *d;
1922 int add_dot = 0;
1923 char *p;
1925 if (str == NULL || wl == NULL || strlen(str) < 2)
1926 return;
1928 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1930 /* treat *.moo.com the same as .moo.com */
1931 if (str[0] == '*' && str[1] == '.')
1932 str = &str[1];
1933 else if (str[0] == '.')
1934 str = &str[0];
1935 else
1936 add_dot = 1;
1938 /* slice off port number */
1939 p = g_strrstr(str, ":");
1940 if (p)
1941 *p = '\0';
1943 d = g_malloc(sizeof *d);
1944 if (add_dot)
1945 d->d = g_strdup_printf(".%s", str);
1946 else
1947 d->d = g_strdup(str);
1948 d->handy = handy;
1950 if (RB_INSERT(domain_list, wl, d))
1951 goto unwind;
1953 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1954 return;
1955 unwind:
1956 if (d) {
1957 if (d->d)
1958 g_free(d->d);
1959 g_free(d);
1964 add_cookie_wl(struct settings *s, char *entry)
1966 wl_add(entry, &c_wl, 1);
1967 return (0);
1970 void
1971 walk_cookie_wl(struct settings *s,
1972 void (*cb)(struct settings *, char *, void *), void *cb_args)
1974 struct domain *d;
1976 if (s == NULL || cb == NULL) {
1977 show_oops(NULL, "walk_cookie_wl invalid parameters");
1978 return;
1981 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1982 cb(s, d->d, cb_args);
1985 void
1986 walk_js_wl(struct settings *s,
1987 void (*cb)(struct settings *, char *, void *), void *cb_args)
1989 struct domain *d;
1991 if (s == NULL || cb == NULL) {
1992 show_oops(NULL, "walk_js_wl invalid parameters");
1993 return;
1996 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1997 cb(s, d->d, cb_args);
2001 add_js_wl(struct settings *s, char *entry)
2003 wl_add(entry, &js_wl, 1 /* persistent */);
2004 return (0);
2007 struct domain *
2008 wl_find(const gchar *search, struct domain_list *wl)
2010 int i;
2011 struct domain *d = NULL, dfind;
2012 gchar *s = NULL;
2014 if (search == NULL || wl == NULL)
2015 return (NULL);
2016 if (strlen(search) < 2)
2017 return (NULL);
2019 if (search[0] != '.')
2020 s = g_strdup_printf(".%s", search);
2021 else
2022 s = g_strdup(search);
2024 for (i = strlen(s) - 1; i >= 0; i--) {
2025 if (s[i] == '.') {
2026 dfind.d = &s[i];
2027 d = RB_FIND(domain_list, wl, &dfind);
2028 if (d)
2029 goto done;
2033 done:
2034 if (s)
2035 g_free(s);
2037 return (d);
2040 struct domain *
2041 wl_find_uri(const gchar *s, struct domain_list *wl)
2043 int i;
2044 char *ss;
2045 struct domain *r;
2047 if (s == NULL || wl == NULL)
2048 return (NULL);
2050 if (!strncmp(s, "http://", strlen("http://")))
2051 s = &s[strlen("http://")];
2052 else if (!strncmp(s, "https://", strlen("https://")))
2053 s = &s[strlen("https://")];
2055 if (strlen(s) < 2)
2056 return (NULL);
2058 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2059 /* chop string at first slash */
2060 if (s[i] == '/' || s[i] == '\0') {
2061 ss = g_strdup(s);
2062 ss[i] = '\0';
2063 r = wl_find(ss, wl);
2064 g_free(ss);
2065 return (r);
2068 return (NULL);
2072 settings_add(char *var, char *val)
2074 int i, rv, *p;
2075 gfloat *f;
2076 char **s;
2078 /* get settings */
2079 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2080 if (strcmp(var, rs[i].name))
2081 continue;
2083 if (rs[i].s) {
2084 if (rs[i].s->set(&rs[i], val))
2085 errx(1, "invalid value for %s: %s", var, val);
2086 rv = 1;
2087 break;
2088 } else
2089 switch (rs[i].type) {
2090 case XT_S_INT:
2091 p = rs[i].ival;
2092 *p = atoi(val);
2093 rv = 1;
2094 break;
2095 case XT_S_STR:
2096 s = rs[i].sval;
2097 if (s == NULL)
2098 errx(1, "invalid sval for %s",
2099 rs[i].name);
2100 if (*s)
2101 g_free(*s);
2102 *s = g_strdup(val);
2103 rv = 1;
2104 break;
2105 case XT_S_FLOAT:
2106 f = rs[i].fval;
2107 *f = atof(val);
2108 rv = 1;
2109 break;
2110 case XT_S_INVALID:
2111 default:
2112 errx(1, "invalid type for %s", var);
2114 break;
2116 return (rv);
2119 #define WS "\n= \t"
2120 void
2121 config_parse(char *filename, int runtime)
2123 FILE *config, *f;
2124 char *line, *cp, *var, *val;
2125 size_t len, lineno = 0;
2126 int handled;
2127 char file[PATH_MAX];
2128 struct stat sb;
2130 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2132 if (filename == NULL)
2133 return;
2135 if (runtime && runtime_settings[0] != '\0') {
2136 snprintf(file, sizeof file, "%s/%s",
2137 work_dir, runtime_settings);
2138 if (stat(file, &sb)) {
2139 warnx("runtime file doesn't exist, creating it");
2140 if ((f = fopen(file, "w")) == NULL)
2141 err(1, "runtime");
2142 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2143 fclose(f);
2145 } else
2146 strlcpy(file, filename, sizeof file);
2148 if ((config = fopen(file, "r")) == NULL) {
2149 warn("config_parse: cannot open %s", filename);
2150 return;
2153 for (;;) {
2154 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2155 if (feof(config) || ferror(config))
2156 break;
2158 cp = line;
2159 cp += (long)strspn(cp, WS);
2160 if (cp[0] == '\0') {
2161 /* empty line */
2162 free(line);
2163 continue;
2166 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2167 startpage_add("invalid configuration file entry: %s",
2168 line);
2170 cp += (long)strspn(cp, WS);
2172 if ((val = strsep(&cp, "\0")) == NULL)
2173 break;
2175 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2176 handled = settings_add(var, val);
2177 if (handled == 0)
2178 startpage_add("invalid configuration file entry: %s=%s",
2179 var, val);
2181 free(line);
2184 fclose(config);
2187 char *
2188 js_ref_to_string(JSContextRef context, JSValueRef ref)
2190 char *s = NULL;
2191 size_t l;
2192 JSStringRef jsref;
2194 jsref = JSValueToStringCopy(context, ref, NULL);
2195 if (jsref == NULL)
2196 return (NULL);
2198 l = JSStringGetMaximumUTF8CStringSize(jsref);
2199 s = g_malloc(l);
2200 if (s)
2201 JSStringGetUTF8CString(jsref, s, l);
2202 JSStringRelease(jsref);
2204 return (s);
2207 void
2208 disable_hints(struct tab *t)
2210 bzero(t->hint_buf, sizeof t->hint_buf);
2211 bzero(t->hint_num, sizeof t->hint_num);
2212 run_script(t, "vimprobable_clear()");
2213 t->hints_on = 0;
2214 t->hint_mode = XT_HINT_NONE;
2217 void
2218 enable_hints(struct tab *t)
2220 bzero(t->hint_buf, sizeof t->hint_buf);
2221 run_script(t, "vimprobable_show_hints()");
2222 t->hints_on = 1;
2223 t->hint_mode = XT_HINT_NONE;
2226 #define XT_JS_OPEN ("open;")
2227 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2228 #define XT_JS_FIRE ("fire;")
2229 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2230 #define XT_JS_FOUND ("found;")
2231 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2234 run_script(struct tab *t, char *s)
2236 JSGlobalContextRef ctx;
2237 WebKitWebFrame *frame;
2238 JSStringRef str;
2239 JSValueRef val, exception;
2240 char *es, buf[128];
2242 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2243 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2245 frame = webkit_web_view_get_main_frame(t->wv);
2246 ctx = webkit_web_frame_get_global_context(frame);
2248 str = JSStringCreateWithUTF8CString(s);
2249 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2250 NULL, 0, &exception);
2251 JSStringRelease(str);
2253 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2254 if (val == NULL) {
2255 es = js_ref_to_string(ctx, exception);
2256 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2257 g_free(es);
2258 return (1);
2259 } else {
2260 es = js_ref_to_string(ctx, val);
2261 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2263 /* handle return value right here */
2264 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2265 disable_hints(t);
2266 marks_clear(t);
2267 load_uri(t, &es[XT_JS_OPEN_LEN]);
2270 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2271 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2272 &es[XT_JS_FIRE_LEN]);
2273 run_script(t, buf);
2274 disable_hints(t);
2277 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2278 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2279 disable_hints(t);
2282 g_free(es);
2285 return (0);
2289 hint(struct tab *t, struct karg *args)
2292 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2294 if (t->hints_on == 0)
2295 enable_hints(t);
2296 else
2297 disable_hints(t);
2299 return (0);
2302 void
2303 apply_style(struct tab *t)
2305 g_object_set(G_OBJECT(t->settings),
2306 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2310 userstyle(struct tab *t, struct karg *args)
2312 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2314 if (t->styled) {
2315 t->styled = 0;
2316 g_object_set(G_OBJECT(t->settings),
2317 "user-stylesheet-uri", NULL, (char *)NULL);
2318 } else {
2319 t->styled = 1;
2320 apply_style(t);
2322 return (0);
2326 * Doesn't work fully, due to the following bug:
2327 * https://bugs.webkit.org/show_bug.cgi?id=51747
2330 restore_global_history(void)
2332 char file[PATH_MAX];
2333 FILE *f;
2334 struct history *h;
2335 gchar *uri;
2336 gchar *title;
2337 const char delim[3] = {'\\', '\\', '\0'};
2339 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2341 if ((f = fopen(file, "r")) == NULL) {
2342 warnx("%s: fopen", __func__);
2343 return (1);
2346 for (;;) {
2347 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2348 if (feof(f) || ferror(f))
2349 break;
2351 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2352 if (feof(f) || ferror(f)) {
2353 free(uri);
2354 warnx("%s: broken history file\n", __func__);
2355 return (1);
2358 if (uri && strlen(uri) && title && strlen(title)) {
2359 webkit_web_history_item_new_with_data(uri, title);
2360 h = g_malloc(sizeof(struct history));
2361 h->uri = g_strdup(uri);
2362 h->title = g_strdup(title);
2363 RB_INSERT(history_list, &hl, h);
2364 completion_add_uri(h->uri);
2365 } else {
2366 warnx("%s: failed to restore history\n", __func__);
2367 free(uri);
2368 free(title);
2369 return (1);
2372 free(uri);
2373 free(title);
2374 uri = NULL;
2375 title = NULL;
2378 return (0);
2382 save_global_history_to_disk(struct tab *t)
2384 char file[PATH_MAX];
2385 FILE *f;
2386 struct history *h;
2388 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2390 if ((f = fopen(file, "w")) == NULL) {
2391 show_oops(t, "%s: global history file: %s",
2392 __func__, strerror(errno));
2393 return (1);
2396 RB_FOREACH_REVERSE(h, history_list, &hl) {
2397 if (h->uri && h->title)
2398 fprintf(f, "%s\n%s\n", h->uri, h->title);
2401 fclose(f);
2403 return (0);
2407 quit(struct tab *t, struct karg *args)
2409 if (save_global_history)
2410 save_global_history_to_disk(t);
2412 gtk_main_quit();
2414 return (1);
2418 open_tabs(struct tab *t, struct karg *a)
2420 char file[PATH_MAX];
2421 FILE *f = NULL;
2422 char *uri = NULL;
2423 int rv = 1;
2424 struct tab *ti, *tt;
2426 if (a == NULL)
2427 goto done;
2429 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2430 if ((f = fopen(file, "r")) == NULL)
2431 goto done;
2433 ti = TAILQ_LAST(&tabs, tab_list);
2435 for (;;) {
2436 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2437 if (feof(f) || ferror(f))
2438 break;
2440 /* retrieve session name */
2441 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2442 strlcpy(named_session,
2443 &uri[strlen(XT_SAVE_SESSION_ID)],
2444 sizeof named_session);
2445 continue;
2448 if (uri && strlen(uri))
2449 create_new_tab(uri, NULL, 1, -1);
2451 free(uri);
2452 uri = NULL;
2455 /* close open tabs */
2456 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2457 for (;;) {
2458 tt = TAILQ_FIRST(&tabs);
2459 if (tt != ti) {
2460 delete_tab(tt);
2461 continue;
2463 delete_tab(tt);
2464 break;
2466 recalc_tabs();
2469 rv = 0;
2470 done:
2471 if (f)
2472 fclose(f);
2474 return (rv);
2478 restore_saved_tabs(void)
2480 char file[PATH_MAX];
2481 int unlink_file = 0;
2482 struct stat sb;
2483 struct karg a;
2484 int rv = 0;
2486 snprintf(file, sizeof file, "%s/%s",
2487 sessions_dir, XT_RESTART_TABS_FILE);
2488 if (stat(file, &sb) == -1)
2489 a.s = XT_SAVED_TABS_FILE;
2490 else {
2491 unlink_file = 1;
2492 a.s = XT_RESTART_TABS_FILE;
2495 a.i = XT_SES_DONOTHING;
2496 rv = open_tabs(NULL, &a);
2498 if (unlink_file)
2499 unlink(file);
2501 return (rv);
2505 save_tabs(struct tab *t, struct karg *a)
2507 char file[PATH_MAX];
2508 FILE *f;
2509 int num_tabs = 0, i;
2510 struct tab **stabs = NULL;
2512 if (a == NULL)
2513 return (1);
2514 if (a->s == NULL)
2515 snprintf(file, sizeof file, "%s/%s",
2516 sessions_dir, named_session);
2517 else
2518 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2520 if ((f = fopen(file, "w")) == NULL) {
2521 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2522 return (1);
2525 /* save session name */
2526 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2528 /* Save tabs, in the order they are arranged in the notebook. */
2529 num_tabs = sort_tabs_by_page_num(&stabs);
2531 for (i = 0; i < num_tabs; i++)
2532 if (stabs[i]) {
2533 if (get_uri(stabs[i]) != NULL)
2534 fprintf(f, "%s\n", get_uri(stabs[i]));
2535 else if (gtk_entry_get_text(GTK_ENTRY(
2536 stabs[i]->uri_entry)))
2537 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2538 stabs[i]->uri_entry)));
2541 g_free(stabs);
2543 /* try and make sure this gets to disk NOW. XXX Backup first? */
2544 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2545 show_oops(t, "May not have managed to save session: %s",
2546 strerror(errno));
2549 fclose(f);
2551 return (0);
2555 save_tabs_and_quit(struct tab *t, struct karg *args)
2557 struct karg a;
2559 a.s = NULL;
2560 save_tabs(t, &a);
2561 quit(t, NULL);
2563 return (1);
2567 run_page_script(struct tab *t, struct karg *args)
2569 const gchar *uri;
2570 char *tmp, script[PATH_MAX];
2572 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2573 if (tmp[0] == '\0') {
2574 show_oops(t, "no script specified");
2575 return (1);
2578 if ((uri = get_uri(t)) == NULL) {
2579 show_oops(t, "tab is empty, not running script");
2580 return (1);
2583 if (tmp[0] == '~')
2584 snprintf(script, sizeof script, "%s/%s",
2585 pwd->pw_dir, &tmp[1]);
2586 else
2587 strlcpy(script, tmp, sizeof script);
2589 switch (fork()) {
2590 case -1:
2591 show_oops(t, "can't fork to run script");
2592 return (1);
2593 /* NOTREACHED */
2594 case 0:
2595 break;
2596 default:
2597 return (0);
2600 /* child */
2601 execlp(script, script, uri, (void *)NULL);
2603 _exit(0);
2605 /* NOTREACHED */
2607 return (0);
2611 yank_uri(struct tab *t, struct karg *args)
2613 const gchar *uri;
2614 GtkClipboard *clipboard;
2616 if ((uri = get_uri(t)) == NULL)
2617 return (1);
2619 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2620 gtk_clipboard_set_text(clipboard, uri, -1);
2622 return (0);
2626 paste_uri(struct tab *t, struct karg *args)
2628 GtkClipboard *clipboard;
2629 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2630 gint len;
2631 gchar *p = NULL, *uri;
2633 /* try primary clipboard first */
2634 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2635 p = gtk_clipboard_wait_for_text(clipboard);
2637 /* if it failed get whatever text is in cut_buffer0 */
2638 if (p == NULL && xterm_workaround)
2639 if (gdk_property_get(gdk_get_default_root_window(),
2640 atom,
2641 gdk_atom_intern("STRING", FALSE),
2643 1024 * 1024 /* picked out of my butt */,
2644 FALSE,
2645 NULL,
2646 NULL,
2647 &len,
2648 (guchar **)&p)) {
2649 /* yes sir, we need to NUL the string */
2650 p[len] = '\0';
2653 if (p) {
2654 uri = p;
2655 while (*uri && isspace(*uri))
2656 uri++;
2657 if (strlen(uri) == 0) {
2658 show_oops(t, "empty paste buffer");
2659 goto done;
2661 if (guess_search == 0 && valid_url_type(uri)) {
2662 /* we can be clever and paste this in search box */
2663 show_oops(t, "not a valid URL");
2664 goto done;
2667 if (args->i == XT_PASTE_CURRENT_TAB)
2668 load_uri(t, uri);
2669 else if (args->i == XT_PASTE_NEW_TAB)
2670 create_new_tab(uri, NULL, 1, -1);
2673 done:
2674 if (p)
2675 g_free(p);
2677 return (0);
2680 gchar *
2681 find_domain(const gchar *s, int toplevel)
2683 SoupURI *uri;
2684 gchar *ret, *p;
2686 if (s == NULL)
2687 return (NULL);
2689 uri = soup_uri_new(s);
2691 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2692 return (NULL);
2695 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2696 if ((p = strrchr(uri->host, '.')) != NULL) {
2697 while(--p >= uri->host && *p != '.');
2698 p++;
2699 } else
2700 p = uri->host;
2701 } else
2702 p = uri->host;
2704 if (uri->port == 80)
2705 ret = g_strdup_printf(".%s", p);
2706 else
2707 ret = g_strdup_printf(".%s:%d", p, uri->port);
2709 soup_uri_free(uri);
2711 return ret;
2715 toggle_cwl(struct tab *t, struct karg *args)
2717 struct domain *d;
2718 const gchar *uri;
2719 char *dom = NULL;
2720 int es;
2722 if (args == NULL)
2723 return (1);
2725 uri = get_uri(t);
2726 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2728 if (uri == NULL || dom == NULL ||
2729 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2730 show_oops(t, "Can't toggle domain in cookie white list");
2731 goto done;
2733 d = wl_find(dom, &c_wl);
2735 if (d == NULL)
2736 es = 0;
2737 else
2738 es = 1;
2740 if (args->i & XT_WL_TOGGLE)
2741 es = !es;
2742 else if ((args->i & XT_WL_ENABLE) && es != 1)
2743 es = 1;
2744 else if ((args->i & XT_WL_DISABLE) && es != 0)
2745 es = 0;
2747 if (es)
2748 /* enable cookies for domain */
2749 wl_add(dom, &c_wl, 0);
2750 else
2751 /* disable cookies for domain */
2752 RB_REMOVE(domain_list, &c_wl, d);
2754 if (args->i & XT_WL_RELOAD)
2755 webkit_web_view_reload(t->wv);
2757 done:
2758 g_free(dom);
2759 return (0);
2763 toggle_js(struct tab *t, struct karg *args)
2765 int es;
2766 const gchar *uri;
2767 struct domain *d;
2768 char *dom = NULL;
2770 if (args == NULL)
2771 return (1);
2773 g_object_get(G_OBJECT(t->settings),
2774 "enable-scripts", &es, (char *)NULL);
2775 if (args->i & XT_WL_TOGGLE)
2776 es = !es;
2777 else if ((args->i & XT_WL_ENABLE) && es != 1)
2778 es = 1;
2779 else if ((args->i & XT_WL_DISABLE) && es != 0)
2780 es = 0;
2781 else
2782 return (1);
2784 uri = get_uri(t);
2785 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2787 if (uri == NULL || dom == NULL ||
2788 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2789 show_oops(t, "Can't toggle domain in JavaScript white list");
2790 goto done;
2793 if (es) {
2794 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2795 wl_add(dom, &js_wl, 0 /* session */);
2796 } else {
2797 d = wl_find(dom, &js_wl);
2798 if (d)
2799 RB_REMOVE(domain_list, &js_wl, d);
2800 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2802 g_object_set(G_OBJECT(t->settings),
2803 "enable-scripts", es, (char *)NULL);
2804 g_object_set(G_OBJECT(t->settings),
2805 "javascript-can-open-windows-automatically", es, (char *)NULL);
2806 webkit_web_view_set_settings(t->wv, t->settings);
2808 if (args->i & XT_WL_RELOAD)
2809 webkit_web_view_reload(t->wv);
2810 done:
2811 if (dom)
2812 g_free(dom);
2813 return (0);
2816 void
2817 js_toggle_cb(GtkWidget *w, struct tab *t)
2819 struct karg a;
2821 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2822 toggle_cwl(t, &a);
2824 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2825 toggle_js(t, &a);
2829 toggle_src(struct tab *t, struct karg *args)
2831 gboolean mode;
2833 if (t == NULL)
2834 return (0);
2836 mode = webkit_web_view_get_view_source_mode(t->wv);
2837 webkit_web_view_set_view_source_mode(t->wv, !mode);
2838 webkit_web_view_reload(t->wv);
2840 return (0);
2843 void
2844 focus_webview(struct tab *t)
2846 if (t == NULL)
2847 return;
2849 /* only grab focus if we are visible */
2850 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2851 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2855 focus(struct tab *t, struct karg *args)
2857 if (t == NULL || args == NULL)
2858 return (1);
2860 if (show_url == 0)
2861 return (0);
2863 if (args->i == XT_FOCUS_URI)
2864 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2865 else if (args->i == XT_FOCUS_SEARCH)
2866 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2868 return (0);
2872 stats(struct tab *t, struct karg *args)
2874 char *page, *body, *s, line[64 * 1024];
2875 uint64_t line_count = 0;
2876 FILE *r_cookie_f;
2878 if (t == NULL)
2879 show_oops(NULL, "stats invalid parameters");
2881 line[0] = '\0';
2882 if (save_rejected_cookies) {
2883 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2884 for (;;) {
2885 s = fgets(line, sizeof line, r_cookie_f);
2886 if (s == NULL || feof(r_cookie_f) ||
2887 ferror(r_cookie_f))
2888 break;
2889 line_count++;
2891 fclose(r_cookie_f);
2892 snprintf(line, sizeof line,
2893 "<br/>Cookies blocked(*) total: %llu", line_count);
2894 } else
2895 show_oops(t, "Can't open blocked cookies file: %s",
2896 strerror(errno));
2899 body = g_strdup_printf(
2900 "Cookies blocked(*) this session: %llu"
2901 "%s"
2902 "<p><small><b>*</b> results vary based on settings</small></p>",
2903 blocked_cookies,
2904 line);
2906 page = get_html_page("Statistics", body, "", 0);
2907 g_free(body);
2909 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2910 g_free(page);
2912 return (0);
2916 marco(struct tab *t, struct karg *args)
2918 char *page, line[64 * 1024];
2919 int len;
2921 if (t == NULL)
2922 show_oops(NULL, "marco invalid parameters");
2924 line[0] = '\0';
2925 snprintf(line, sizeof line, "%s", marco_message(&len));
2927 page = get_html_page("Marco Sez...", line, "", 0);
2929 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2930 g_free(page);
2932 return (0);
2936 blank(struct tab *t, struct karg *args)
2938 if (t == NULL)
2939 show_oops(NULL, "blank invalid parameters");
2941 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2943 return (0);
2947 about(struct tab *t, struct karg *args)
2949 char *page, *body;
2951 if (t == NULL)
2952 show_oops(NULL, "about invalid parameters");
2954 body = g_strdup_printf("<b>Version: %s</b><p>"
2955 "Authors:"
2956 "<ul>"
2957 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2958 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2959 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2960 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2961 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2962 "</ul>"
2963 "Copyrights and licenses can be found on the XXXTerm "
2964 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2965 version
2968 page = get_html_page("About", body, "", 0);
2969 g_free(body);
2971 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2972 g_free(page);
2974 return (0);
2978 help(struct tab *t, struct karg *args)
2980 char *page, *head, *body;
2982 if (t == NULL)
2983 show_oops(NULL, "help invalid parameters");
2985 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2986 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2987 "</head>\n";
2988 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2989 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2990 "cgi-bin/man-cgi?xxxterm</a>";
2992 page = get_html_page(XT_NAME, body, head, FALSE);
2994 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2995 g_free(page);
2997 return (0);
3001 startpage(struct tab *t, struct karg *args)
3003 char *page, *body, *b;
3004 struct sp *s;
3006 if (t == NULL)
3007 show_oops(NULL, "startpage invalid parameters");
3009 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3011 TAILQ_FOREACH(s, &spl, entry) {
3012 b = body;
3013 body = g_strdup_printf("%s%s<br>", body, s->line);
3014 g_free(b);
3017 page = get_html_page("Startup Exception", body, "", 0);
3018 g_free(body);
3020 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3021 g_free(page);
3023 return (0);
3026 void
3027 startpage_add(const char *fmt, ...)
3029 va_list ap;
3030 char *msg;
3031 struct sp *s;
3033 if (fmt == NULL)
3034 return;
3036 va_start(ap, fmt);
3037 if (vasprintf(&msg, fmt, ap) == -1)
3038 errx(1, "startpage_add failed");
3039 va_end(ap);
3041 s = g_malloc0(sizeof *s);
3042 s->line = msg;
3044 TAILQ_INSERT_TAIL(&spl, s, entry);
3048 * update all favorite tabs apart from one. Pass NULL if
3049 * you want to update all.
3051 void
3052 update_favorite_tabs(struct tab *apart_from)
3054 struct tab *t;
3055 if (!updating_fl_tabs) {
3056 updating_fl_tabs = 1; /* stop infinite recursion */
3057 TAILQ_FOREACH(t, &tabs, entry)
3058 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3059 && (t != apart_from))
3060 xtp_page_fl(t, NULL);
3061 updating_fl_tabs = 0;
3065 /* show a list of favorites (bookmarks) */
3067 xtp_page_fl(struct tab *t, struct karg *args)
3069 char file[PATH_MAX];
3070 FILE *f;
3071 char *uri = NULL, *title = NULL;
3072 size_t len, lineno = 0;
3073 int i, failed = 0;
3074 char *body, *tmp, *page = NULL;
3075 const char delim[3] = {'\\', '\\', '\0'};
3077 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3079 if (t == NULL)
3080 warn("%s: bad param", __func__);
3082 /* new session key */
3083 if (!updating_fl_tabs)
3084 generate_xtp_session_key(&fl_session_key);
3086 /* open favorites */
3087 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3088 if ((f = fopen(file, "r")) == NULL) {
3089 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3090 return (1);
3093 /* body */
3094 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3095 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3096 "<th style='width: 40px'>Rm</th></tr>\n");
3098 for (i = 1;;) {
3099 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3100 if (feof(f) || ferror(f))
3101 break;
3102 if (strlen(title) == 0 || title[0] == '#') {
3103 free(title);
3104 title = NULL;
3105 continue;
3108 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3109 if (feof(f) || ferror(f)) {
3110 show_oops(t, "favorites file corrupt");
3111 failed = 1;
3112 break;
3115 tmp = body;
3116 body = g_strdup_printf("%s<tr>"
3117 "<td>%d</td>"
3118 "<td><a href='%s'>%s</a></td>"
3119 "<td style='text-align: center'>"
3120 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3121 "</tr>\n",
3122 body, i, uri, title,
3123 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3125 g_free(tmp);
3127 free(uri);
3128 uri = NULL;
3129 free(title);
3130 title = NULL;
3131 i++;
3133 fclose(f);
3135 /* if none, say so */
3136 if (i == 1) {
3137 tmp = body;
3138 body = g_strdup_printf("%s<tr>"
3139 "<td colspan='3' style='text-align: center'>"
3140 "No favorites - To add one use the 'favadd' command."
3141 "</td></tr>", body);
3142 g_free(tmp);
3145 tmp = body;
3146 body = g_strdup_printf("%s</table>", body);
3147 g_free(tmp);
3149 if (uri)
3150 free(uri);
3151 if (title)
3152 free(title);
3154 /* render */
3155 if (!failed) {
3156 page = get_html_page("Favorites", body, "", 1);
3157 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3158 g_free(page);
3161 update_favorite_tabs(t);
3163 if (body)
3164 g_free(body);
3166 return (failed);
3169 void
3170 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3171 size_t cert_count, char *title)
3173 gnutls_datum_t cinfo;
3174 char *tmp, *body;
3175 int i;
3177 body = g_strdup("");
3179 for (i = 0; i < cert_count; i++) {
3180 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3181 &cinfo))
3182 return;
3184 tmp = body;
3185 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3186 body, i, cinfo.data);
3187 gnutls_free(cinfo.data);
3188 g_free(tmp);
3191 tmp = get_html_page(title, body, "", 0);
3192 g_free(body);
3194 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3195 g_free(tmp);
3199 ca_cmd(struct tab *t, struct karg *args)
3201 FILE *f = NULL;
3202 int rv = 1, certs = 0, certs_read;
3203 struct stat sb;
3204 gnutls_datum_t dt;
3205 gnutls_x509_crt_t *c = NULL;
3206 char *certs_buf = NULL, *s;
3208 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3209 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3210 return (1);
3213 if (fstat(fileno(f), &sb) == -1) {
3214 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3215 goto done;
3218 certs_buf = g_malloc(sb.st_size + 1);
3219 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3220 show_oops(t, "Can't read CA file: %s", strerror(errno));
3221 goto done;
3223 certs_buf[sb.st_size] = '\0';
3225 s = certs_buf;
3226 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3227 certs++;
3228 s += strlen("BEGIN CERTIFICATE");
3231 bzero(&dt, sizeof dt);
3232 dt.data = (unsigned char *)certs_buf;
3233 dt.size = sb.st_size;
3234 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3235 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3236 GNUTLS_X509_FMT_PEM, 0);
3237 if (certs_read <= 0) {
3238 show_oops(t, "No cert(s) available");
3239 goto done;
3241 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3242 done:
3243 if (c)
3244 g_free(c);
3245 if (certs_buf)
3246 g_free(certs_buf);
3247 if (f)
3248 fclose(f);
3250 return (rv);
3254 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3255 size_t domain_sz)
3257 SoupURI *su = NULL;
3258 struct addrinfo hints, *res = NULL, *ai;
3259 int rv = -1, s = -1, on, error;
3260 char port[8];
3262 if (uri && !g_str_has_prefix(uri, "https://")) {
3263 show_oops(t, "invalid URI");
3264 goto done;
3267 su = soup_uri_new(uri);
3268 if (su == NULL) {
3269 show_oops(t, "invalid soup URI");
3270 goto done;
3272 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3273 show_oops(t, "invalid HTTPS URI");
3274 goto done;
3277 snprintf(port, sizeof port, "%d", su->port);
3278 bzero(&hints, sizeof(struct addrinfo));
3279 hints.ai_flags = AI_CANONNAME;
3280 hints.ai_family = AF_UNSPEC;
3281 hints.ai_socktype = SOCK_STREAM;
3283 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3284 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3285 goto done;
3288 for (ai = res; ai; ai = ai->ai_next) {
3289 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3290 continue;
3292 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3293 if (s == -1) {
3294 show_oops(t, "socket failed: %s", strerror(errno));
3295 goto done;
3297 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3298 sizeof(on)) == -1) {
3299 show_oops(t, "setsockopt failed: %s", strerror(errno));
3300 goto done;
3302 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3303 show_oops(t, "connect failed: %s", strerror(errno));
3304 goto done;
3307 break;
3310 if (domain)
3311 strlcpy(domain, su->host, domain_sz);
3312 rv = s;
3313 done:
3314 if (su)
3315 soup_uri_free(su);
3316 if (res)
3317 freeaddrinfo(res);
3318 if (rv == -1 && s != -1)
3319 close(s);
3321 return (rv);
3325 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3327 if (gsession)
3328 gnutls_deinit(gsession);
3329 if (xcred)
3330 gnutls_certificate_free_credentials(xcred);
3332 return (0);
3336 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3337 gnutls_certificate_credentials_t *xc)
3339 gnutls_certificate_credentials_t xcred;
3340 gnutls_session_t gsession;
3341 int rv = 1;
3343 if (gs == NULL || xc == NULL)
3344 goto done;
3346 *gs = NULL;
3347 *xc = NULL;
3349 gnutls_certificate_allocate_credentials(&xcred);
3350 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3351 GNUTLS_X509_FMT_PEM);
3353 gnutls_init(&gsession, GNUTLS_CLIENT);
3354 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3355 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3356 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3357 if ((rv = gnutls_handshake(gsession)) < 0) {
3358 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3360 gnutls_error_is_fatal(rv),
3361 gnutls_strerror_name(rv));
3362 stop_tls(gsession, xcred);
3363 goto done;
3366 gnutls_credentials_type_t cred;
3367 cred = gnutls_auth_get_type(gsession);
3368 if (cred != GNUTLS_CRD_CERTIFICATE) {
3369 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3370 stop_tls(gsession, xcred);
3371 goto done;
3374 *gs = gsession;
3375 *xc = xcred;
3376 rv = 0;
3377 done:
3378 return (rv);
3382 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3383 size_t *cert_count)
3385 unsigned int len;
3386 const gnutls_datum_t *cl;
3387 gnutls_x509_crt_t *all_certs;
3388 int i, rv = 1;
3390 if (certs == NULL || cert_count == NULL)
3391 goto done;
3392 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3393 goto done;
3394 cl = gnutls_certificate_get_peers(gsession, &len);
3395 if (len == 0)
3396 goto done;
3398 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3399 for (i = 0; i < len; i++) {
3400 gnutls_x509_crt_init(&all_certs[i]);
3401 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3402 GNUTLS_X509_FMT_PEM < 0)) {
3403 g_free(all_certs);
3404 goto done;
3408 *certs = all_certs;
3409 *cert_count = len;
3410 rv = 0;
3411 done:
3412 return (rv);
3415 void
3416 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3418 int i;
3420 for (i = 0; i < cert_count; i++)
3421 gnutls_x509_crt_deinit(certs[i]);
3422 g_free(certs);
3425 void
3426 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3428 GdkColor c_text, c_base;
3430 gdk_color_parse(text, &c_text);
3431 gdk_color_parse(base, &c_base);
3433 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3434 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3435 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3436 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3438 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3439 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3440 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3441 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3444 void
3445 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3446 size_t cert_count, char *domain)
3448 size_t cert_buf_sz;
3449 char cert_buf[64 * 1024], file[PATH_MAX];
3450 int i;
3451 FILE *f;
3452 GdkColor color;
3454 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3455 return;
3457 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3458 if ((f = fopen(file, "w")) == NULL) {
3459 show_oops(t, "Can't create cert file %s %s",
3460 file, strerror(errno));
3461 return;
3464 for (i = 0; i < cert_count; i++) {
3465 cert_buf_sz = sizeof cert_buf;
3466 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3467 cert_buf, &cert_buf_sz)) {
3468 show_oops(t, "gnutls_x509_crt_export failed");
3469 goto done;
3471 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3472 show_oops(t, "Can't write certs: %s", strerror(errno));
3473 goto done;
3477 /* not the best spot but oh well */
3478 gdk_color_parse("lightblue", &color);
3479 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3480 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3481 done:
3482 fclose(f);
3485 enum cert_trust {
3486 CERT_LOCAL,
3487 CERT_TRUSTED,
3488 CERT_UNTRUSTED,
3489 CERT_BAD
3492 enum cert_trust
3493 load_compare_cert(struct tab *t, struct karg *args)
3495 const gchar *uri;
3496 char domain[8182], file[PATH_MAX];
3497 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3498 int s = -1, i, error;
3499 FILE *f = NULL;
3500 size_t cert_buf_sz, cert_count;
3501 enum cert_trust rv = CERT_UNTRUSTED;
3502 char serr[80];
3503 gnutls_session_t gsession;
3504 gnutls_x509_crt_t *certs;
3505 gnutls_certificate_credentials_t xcred;
3507 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3509 if (t == NULL)
3510 return (rv);
3512 if ((uri = get_uri(t)) == NULL)
3513 return (rv);
3514 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3516 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3517 return (rv);
3518 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3520 /* go ssl/tls */
3521 if (start_tls(t, s, &gsession, &xcred))
3522 goto done;
3523 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3525 /* verify certs in case cert file doesn't exist */
3526 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3527 GNUTLS_E_SUCCESS) {
3528 show_oops(t, "Invalid certificates");
3529 goto done;
3532 /* get certs */
3533 if (get_connection_certs(gsession, &certs, &cert_count)) {
3534 show_oops(t, "Can't get connection certificates");
3535 goto done;
3538 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3539 if ((f = fopen(file, "r")) == NULL) {
3540 if (!error)
3541 rv = CERT_TRUSTED;
3542 goto freeit;
3545 for (i = 0; i < cert_count; i++) {
3546 cert_buf_sz = sizeof cert_buf;
3547 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3548 cert_buf, &cert_buf_sz)) {
3549 goto freeit;
3551 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3552 rv = CERT_BAD; /* critical */
3553 goto freeit;
3555 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3556 rv = CERT_BAD; /* critical */
3557 goto freeit;
3559 rv = CERT_LOCAL;
3562 freeit:
3563 if (f)
3564 fclose(f);
3565 free_connection_certs(certs, cert_count);
3566 done:
3567 /* we close the socket first for speed */
3568 if (s != -1)
3569 close(s);
3571 /* only complain if we didn't save it locally */
3572 if (error && rv != CERT_LOCAL) {
3573 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3574 if (error & GNUTLS_CERT_INVALID)
3575 strlcat(serr, "invalid, ", sizeof serr);
3576 if (error & GNUTLS_CERT_REVOKED)
3577 strlcat(serr, "revoked, ", sizeof serr);
3578 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3579 strlcat(serr, "signer not found, ", sizeof serr);
3580 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3581 strlcat(serr, "not signed by CA, ", sizeof serr);
3582 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3583 strlcat(serr, "insecure algorithm, ", sizeof serr);
3584 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3585 strlcat(serr, "not activated, ", sizeof serr);
3586 if (error & GNUTLS_CERT_EXPIRED)
3587 strlcat(serr, "expired, ", sizeof serr);
3588 for (i = strlen(serr) - 1; i > 0; i--)
3589 if (serr[i] == ',') {
3590 serr[i] = '\0';
3591 break;
3593 show_oops(t, serr);
3596 stop_tls(gsession, xcred);
3598 return (rv);
3602 cert_cmd(struct tab *t, struct karg *args)
3604 const gchar *uri;
3605 char domain[8182];
3606 int s = -1;
3607 size_t cert_count;
3608 gnutls_session_t gsession;
3609 gnutls_x509_crt_t *certs;
3610 gnutls_certificate_credentials_t xcred;
3612 if (t == NULL)
3613 return (1);
3615 if (ssl_ca_file == NULL) {
3616 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3617 return (1);
3620 if ((uri = get_uri(t)) == NULL) {
3621 show_oops(t, "Invalid URI");
3622 return (1);
3625 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3626 show_oops(t, "Invalid certificate URI: %s", uri);
3627 return (1);
3630 /* go ssl/tls */
3631 if (start_tls(t, s, &gsession, &xcred))
3632 goto done;
3634 /* get certs */
3635 if (get_connection_certs(gsession, &certs, &cert_count)) {
3636 show_oops(t, "get_connection_certs failed");
3637 goto done;
3640 if (args->i & XT_SHOW)
3641 show_certs(t, certs, cert_count, "Certificate Chain");
3642 else if (args->i & XT_SAVE)
3643 save_certs(t, certs, cert_count, domain);
3645 free_connection_certs(certs, cert_count);
3646 done:
3647 /* we close the socket first for speed */
3648 if (s != -1)
3649 close(s);
3650 stop_tls(gsession, xcred);
3652 return (0);
3656 remove_cookie(int index)
3658 int i, rv = 1;
3659 GSList *cf;
3660 SoupCookie *c;
3662 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3664 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3666 for (i = 1; cf; cf = cf->next, i++) {
3667 if (i != index)
3668 continue;
3669 c = cf->data;
3670 print_cookie("remove cookie", c);
3671 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3672 rv = 0;
3673 break;
3676 soup_cookies_free(cf);
3678 return (rv);
3682 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3684 struct domain *d;
3685 char *tmp, *body;
3687 body = g_strdup("");
3689 /* p list */
3690 if (args->i & XT_WL_PERSISTENT) {
3691 tmp = body;
3692 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3693 g_free(tmp);
3694 RB_FOREACH(d, domain_list, wl) {
3695 if (d->handy == 0)
3696 continue;
3697 tmp = body;
3698 body = g_strdup_printf("%s%s<br/>", body, d->d);
3699 g_free(tmp);
3703 /* s list */
3704 if (args->i & XT_WL_SESSION) {
3705 tmp = body;
3706 body = g_strdup_printf("%s<h2>Session</h2>", body);
3707 g_free(tmp);
3708 RB_FOREACH(d, domain_list, wl) {
3709 if (d->handy == 1)
3710 continue;
3711 tmp = body;
3712 body = g_strdup_printf("%s%s<br/>", body, d->d);
3713 g_free(tmp);
3717 tmp = get_html_page(title, body, "", 0);
3718 g_free(body);
3719 if (wl == &js_wl)
3720 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3721 else
3722 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3723 g_free(tmp);
3724 return (0);
3728 wl_save(struct tab *t, struct karg *args, int js)
3730 char file[PATH_MAX];
3731 FILE *f;
3732 char *line = NULL, *lt = NULL, *dom = NULL;
3733 size_t linelen;
3734 const gchar *uri;
3735 struct karg a;
3736 struct domain *d;
3737 GSList *cf;
3738 SoupCookie *ci, *c;
3739 char *p;
3741 if (t == NULL || args == NULL)
3742 return (1);
3744 if (runtime_settings[0] == '\0')
3745 return (1);
3747 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3748 if ((f = fopen(file, "r+")) == NULL)
3749 return (1);
3751 uri = get_uri(t);
3752 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3753 if (uri == NULL || dom == NULL ||
3754 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3755 show_oops(t, "Can't add domain to %s white list",
3756 js ? "JavaScript" : "cookie");
3757 goto done;
3760 /* we don't want to save :port number */
3761 p = g_strrstr(dom, ":");
3762 if (p)
3763 *p = '\0';
3765 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3767 while (!feof(f)) {
3768 line = fparseln(f, &linelen, NULL, NULL, 0);
3769 if (line == NULL)
3770 continue;
3771 if (!strcmp(line, lt))
3772 goto done;
3773 free(line);
3774 line = NULL;
3777 fprintf(f, "%s\n", lt);
3779 a.i = XT_WL_ENABLE;
3780 a.i |= args->i;
3781 if (js) {
3782 d = wl_find(dom, &js_wl);
3783 if (!d) {
3784 settings_add("js_wl", dom);
3785 d = wl_find(dom, &js_wl);
3787 toggle_js(t, &a);
3788 } else {
3789 d = wl_find(dom, &c_wl);
3790 if (!d) {
3791 settings_add("cookie_wl", dom);
3792 d = wl_find(dom, &c_wl);
3794 toggle_cwl(t, &a);
3796 /* find and add to persistent jar */
3797 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3798 for (;cf; cf = cf->next) {
3799 ci = cf->data;
3800 if (!strcmp(dom, ci->domain) ||
3801 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3802 c = soup_cookie_copy(ci);
3803 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3806 soup_cookies_free(cf);
3808 if (d)
3809 d->handy = 1;
3811 done:
3812 if (line)
3813 free(line);
3814 if (dom)
3815 g_free(dom);
3816 if (lt)
3817 g_free(lt);
3818 fclose(f);
3820 return (0);
3824 js_show_wl(struct tab *t, struct karg *args)
3826 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3827 wl_show(t, args, "JavaScript White List", &js_wl);
3829 return (0);
3833 cookie_show_wl(struct tab *t, struct karg *args)
3835 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3836 wl_show(t, args, "Cookie White List", &c_wl);
3838 return (0);
3842 cookie_cmd(struct tab *t, struct karg *args)
3844 if (args->i & XT_SHOW)
3845 wl_show(t, args, "Cookie White List", &c_wl);
3846 else if (args->i & XT_WL_TOGGLE) {
3847 args->i |= XT_WL_RELOAD;
3848 toggle_cwl(t, args);
3849 } else if (args->i & XT_SAVE) {
3850 args->i |= XT_WL_RELOAD;
3851 wl_save(t, args, 0);
3852 } else if (args->i & XT_DELETE)
3853 show_oops(t, "'cookie delete' currently unimplemented");
3855 return (0);
3859 js_cmd(struct tab *t, struct karg *args)
3861 if (args->i & XT_SHOW)
3862 wl_show(t, args, "JavaScript White List", &js_wl);
3863 else if (args->i & XT_SAVE) {
3864 args->i |= XT_WL_RELOAD;
3865 wl_save(t, args, 1);
3866 } else if (args->i & XT_WL_TOGGLE) {
3867 args->i |= XT_WL_RELOAD;
3868 toggle_js(t, args);
3869 } else if (args->i & XT_DELETE)
3870 show_oops(t, "'js delete' currently unimplemented");
3872 return (0);
3876 toplevel_cmd(struct tab *t, struct karg *args)
3878 js_toggle_cb(t->js_toggle, t);
3880 return (0);
3884 add_favorite(struct tab *t, struct karg *args)
3886 char file[PATH_MAX];
3887 FILE *f;
3888 char *line = NULL;
3889 size_t urilen, linelen;
3890 const gchar *uri, *title;
3892 if (t == NULL)
3893 return (1);
3895 /* don't allow adding of xtp pages to favorites */
3896 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3897 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3898 return (1);
3901 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3902 if ((f = fopen(file, "r+")) == NULL) {
3903 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3904 return (1);
3907 title = get_title(t, FALSE);
3908 uri = get_uri(t);
3910 if (title == NULL || uri == NULL) {
3911 show_oops(t, "can't add page to favorites");
3912 goto done;
3915 urilen = strlen(uri);
3917 for (;;) {
3918 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3919 if (feof(f) || ferror(f))
3920 break;
3922 if (linelen == urilen && !strcmp(line, uri))
3923 goto done;
3925 free(line);
3926 line = NULL;
3929 fprintf(f, "\n%s\n%s", title, uri);
3930 done:
3931 if (line)
3932 free(line);
3933 fclose(f);
3935 update_favorite_tabs(NULL);
3937 return (0);
3941 navaction(struct tab *t, struct karg *args)
3943 WebKitWebHistoryItem *item;
3945 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3946 t->tab_id, args->i);
3948 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3950 if (t->item) {
3951 if (args->i == XT_NAV_BACK)
3952 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3953 else
3954 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3955 if (item == NULL)
3956 return (XT_CB_PASSTHROUGH);
3957 webkit_web_view_go_to_back_forward_item(t->wv, item);
3958 t->item = NULL;
3959 return (XT_CB_PASSTHROUGH);
3962 switch (args->i) {
3963 case XT_NAV_BACK:
3964 marks_clear(t);
3965 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3966 if (item)
3967 webkit_web_view_go_to_back_forward_item(t->wv, item);
3968 break;
3969 case XT_NAV_FORWARD:
3970 marks_clear(t);
3971 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3972 if (item)
3973 webkit_web_view_go_to_back_forward_item(t->wv, item);
3974 break;
3975 case XT_NAV_RELOAD:
3976 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3977 if (item)
3978 webkit_web_view_go_to_back_forward_item(t->wv, item);
3979 break;
3981 return (XT_CB_PASSTHROUGH);
3985 move(struct tab *t, struct karg *args)
3987 GtkAdjustment *adjust;
3988 double pi, si, pos, ps, upper, lower, max;
3989 double percent;
3991 switch (args->i) {
3992 case XT_MOVE_DOWN:
3993 case XT_MOVE_UP:
3994 case XT_MOVE_BOTTOM:
3995 case XT_MOVE_TOP:
3996 case XT_MOVE_PAGEDOWN:
3997 case XT_MOVE_PAGEUP:
3998 case XT_MOVE_HALFDOWN:
3999 case XT_MOVE_HALFUP:
4000 case XT_MOVE_PERCENT:
4001 adjust = t->adjust_v;
4002 break;
4003 default:
4004 adjust = t->adjust_h;
4005 break;
4008 pos = gtk_adjustment_get_value(adjust);
4009 ps = gtk_adjustment_get_page_size(adjust);
4010 upper = gtk_adjustment_get_upper(adjust);
4011 lower = gtk_adjustment_get_lower(adjust);
4012 si = gtk_adjustment_get_step_increment(adjust);
4013 pi = gtk_adjustment_get_page_increment(adjust);
4014 max = upper - ps;
4016 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4017 "max %f si %f pi %f\n",
4018 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4019 pos, ps, upper, lower, max, si, pi);
4021 switch (args->i) {
4022 case XT_MOVE_DOWN:
4023 case XT_MOVE_RIGHT:
4024 pos += si;
4025 gtk_adjustment_set_value(adjust, MIN(pos, max));
4026 break;
4027 case XT_MOVE_UP:
4028 case XT_MOVE_LEFT:
4029 pos -= si;
4030 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4031 break;
4032 case XT_MOVE_BOTTOM:
4033 case XT_MOVE_FARRIGHT:
4034 gtk_adjustment_set_value(adjust, max);
4035 break;
4036 case XT_MOVE_TOP:
4037 case XT_MOVE_FARLEFT:
4038 gtk_adjustment_set_value(adjust, lower);
4039 break;
4040 case XT_MOVE_PAGEDOWN:
4041 pos += pi;
4042 gtk_adjustment_set_value(adjust, MIN(pos, max));
4043 break;
4044 case XT_MOVE_PAGEUP:
4045 pos -= pi;
4046 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4047 break;
4048 case XT_MOVE_HALFDOWN:
4049 pos += pi / 2;
4050 gtk_adjustment_set_value(adjust, MIN(pos, max));
4051 break;
4052 case XT_MOVE_HALFUP:
4053 pos -= pi / 2;
4054 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4055 break;
4056 case XT_MOVE_PERCENT:
4057 percent = atoi(args->s) / 100.0;
4058 pos = max * percent;
4059 if (pos < 0.0 || pos > max)
4060 break;
4061 gtk_adjustment_set_value(adjust, pos);
4062 break;
4063 default:
4064 return (XT_CB_PASSTHROUGH);
4067 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4069 return (XT_CB_HANDLED);
4072 void
4073 url_set_visibility(void)
4075 struct tab *t;
4077 TAILQ_FOREACH(t, &tabs, entry)
4078 if (show_url == 0) {
4079 gtk_widget_hide(t->toolbar);
4080 focus_webview(t);
4081 } else
4082 gtk_widget_show(t->toolbar);
4085 void
4086 notebook_tab_set_visibility(void)
4088 if (show_tabs == 0) {
4089 gtk_widget_hide(tab_bar);
4090 gtk_notebook_set_show_tabs(notebook, FALSE);
4091 } else {
4092 if (tab_style == XT_TABS_NORMAL) {
4093 gtk_widget_hide(tab_bar);
4094 gtk_notebook_set_show_tabs(notebook, TRUE);
4095 } else if (tab_style == XT_TABS_COMPACT) {
4096 gtk_widget_show(tab_bar);
4097 gtk_notebook_set_show_tabs(notebook, FALSE);
4102 void
4103 statusbar_set_visibility(void)
4105 struct tab *t;
4107 TAILQ_FOREACH(t, &tabs, entry)
4108 if (show_statusbar == 0) {
4109 gtk_widget_hide(t->statusbar_box);
4110 focus_webview(t);
4111 } else
4112 gtk_widget_show(t->statusbar_box);
4115 void
4116 url_set(struct tab *t, int enable_url_entry)
4118 GdkPixbuf *pixbuf;
4119 int progress;
4121 show_url = enable_url_entry;
4123 if (enable_url_entry) {
4124 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4125 GTK_ENTRY_ICON_PRIMARY, NULL);
4126 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4127 } else {
4128 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4129 GTK_ENTRY_ICON_PRIMARY);
4130 progress =
4131 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4132 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4133 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4134 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4135 progress);
4140 fullscreen(struct tab *t, struct karg *args)
4142 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4144 if (t == NULL)
4145 return (XT_CB_PASSTHROUGH);
4147 if (show_url == 0) {
4148 url_set(t, 1);
4149 show_tabs = 1;
4150 } else {
4151 url_set(t, 0);
4152 show_tabs = 0;
4155 url_set_visibility();
4156 notebook_tab_set_visibility();
4158 return (XT_CB_HANDLED);
4162 statustoggle(struct tab *t, struct karg *args)
4164 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4166 if (show_statusbar == 1) {
4167 show_statusbar = 0;
4168 statusbar_set_visibility();
4169 } else if (show_statusbar == 0) {
4170 show_statusbar = 1;
4171 statusbar_set_visibility();
4173 return (XT_CB_HANDLED);
4177 urlaction(struct tab *t, struct karg *args)
4179 int rv = XT_CB_HANDLED;
4181 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4183 if (t == NULL)
4184 return (XT_CB_PASSTHROUGH);
4186 switch (args->i) {
4187 case XT_URL_SHOW:
4188 if (show_url == 0) {
4189 url_set(t, 1);
4190 url_set_visibility();
4192 break;
4193 case XT_URL_HIDE:
4194 if (show_url == 1) {
4195 url_set(t, 0);
4196 url_set_visibility();
4198 break;
4200 return (rv);
4204 tabaction(struct tab *t, struct karg *args)
4206 int rv = XT_CB_HANDLED;
4207 char *url = args->s;
4208 struct undo *u;
4209 struct tab *tt;
4211 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4213 if (t == NULL)
4214 return (XT_CB_PASSTHROUGH);
4216 switch (args->i) {
4217 case XT_TAB_NEW:
4218 if (strlen(url) > 0)
4219 create_new_tab(url, NULL, 1, args->precount);
4220 else
4221 create_new_tab(NULL, NULL, 1, args->precount);
4222 break;
4223 case XT_TAB_DELETE:
4224 if (args->precount < 0)
4225 delete_tab(t);
4226 else
4227 TAILQ_FOREACH(tt, &tabs, entry)
4228 if (tt->tab_id == args->precount - 1) {
4229 delete_tab(tt);
4230 break;
4232 break;
4233 case XT_TAB_DELQUIT:
4234 if (gtk_notebook_get_n_pages(notebook) > 1)
4235 delete_tab(t);
4236 else
4237 quit(t, args);
4238 break;
4239 case XT_TAB_OPEN:
4240 if (strlen(url) > 0)
4242 else {
4243 rv = XT_CB_PASSTHROUGH;
4244 goto done;
4246 load_uri(t, url);
4247 break;
4248 case XT_TAB_SHOW:
4249 if (show_tabs == 0) {
4250 show_tabs = 1;
4251 notebook_tab_set_visibility();
4253 break;
4254 case XT_TAB_HIDE:
4255 if (show_tabs == 1) {
4256 show_tabs = 0;
4257 notebook_tab_set_visibility();
4259 break;
4260 case XT_TAB_NEXTSTYLE:
4261 if (tab_style == XT_TABS_NORMAL) {
4262 tab_style = XT_TABS_COMPACT;
4263 recolor_compact_tabs();
4265 else
4266 tab_style = XT_TABS_NORMAL;
4267 notebook_tab_set_visibility();
4268 break;
4269 case XT_TAB_UNDO_CLOSE:
4270 if (undo_count == 0) {
4271 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4272 __func__);
4273 goto done;
4274 } else {
4275 undo_count--;
4276 u = TAILQ_FIRST(&undos);
4277 create_new_tab(u->uri, u, 1, -1);
4279 TAILQ_REMOVE(&undos, u, entry);
4280 g_free(u->uri);
4281 /* u->history is freed in create_new_tab() */
4282 g_free(u);
4284 break;
4285 default:
4286 rv = XT_CB_PASSTHROUGH;
4287 goto done;
4290 done:
4291 if (args->s) {
4292 g_free(args->s);
4293 args->s = NULL;
4296 return (rv);
4300 resizetab(struct tab *t, struct karg *args)
4302 if (t == NULL || args == NULL) {
4303 show_oops(NULL, "resizetab invalid parameters");
4304 return (XT_CB_PASSTHROUGH);
4307 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4308 t->tab_id, args->i);
4310 setzoom_webkit(t, args->i);
4312 return (XT_CB_HANDLED);
4316 movetab(struct tab *t, struct karg *args)
4318 int n, dest;
4320 if (t == NULL || args == NULL) {
4321 show_oops(NULL, "movetab invalid parameters");
4322 return (XT_CB_PASSTHROUGH);
4325 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4326 t->tab_id, args->i);
4328 if (args->i >= XT_TAB_INVALID)
4329 return (XT_CB_PASSTHROUGH);
4331 if (TAILQ_EMPTY(&tabs))
4332 return (XT_CB_PASSTHROUGH);
4334 n = gtk_notebook_get_n_pages(notebook);
4335 dest = gtk_notebook_get_current_page(notebook);
4337 switch (args->i) {
4338 case XT_TAB_NEXT:
4339 if (args->precount < 0)
4340 dest = dest == n - 1 ? 0 : dest + 1;
4341 else
4342 dest = args->precount - 1;
4344 break;
4345 case XT_TAB_PREV:
4346 if (args->precount < 0)
4347 dest -= 1;
4348 else
4349 dest -= args->precount % n;
4351 if (dest < 0)
4352 dest += n;
4354 break;
4355 case XT_TAB_FIRST:
4356 dest = 0;
4357 break;
4358 case XT_TAB_LAST:
4359 dest = n - 1;
4360 break;
4361 default:
4362 return (XT_CB_PASSTHROUGH);
4365 if (dest < 0 || dest >= n)
4366 return (XT_CB_PASSTHROUGH);
4367 if (t->tab_id == dest) {
4368 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4369 return (XT_CB_HANDLED);
4372 set_current_tab(dest);
4374 return (XT_CB_HANDLED);
4377 int cmd_prefix = 0;
4380 command(struct tab *t, struct karg *args)
4382 char *s = NULL, *ss = NULL;
4383 GdkColor color;
4384 const gchar *uri;
4386 if (t == NULL || args == NULL) {
4387 show_oops(NULL, "command invalid parameters");
4388 return (XT_CB_PASSTHROUGH);
4391 switch (args->i) {
4392 case '/':
4393 s = "/";
4394 break;
4395 case '?':
4396 s = "?";
4397 break;
4398 case ':':
4399 if (cmd_prefix == 0)
4400 s = ":";
4401 else {
4402 ss = g_strdup_printf(":%d", cmd_prefix);
4403 s = ss;
4404 cmd_prefix = 0;
4406 break;
4407 case XT_CMD_OPEN:
4408 s = ":open ";
4409 break;
4410 case XT_CMD_TABNEW:
4411 s = ":tabnew ";
4412 break;
4413 case XT_CMD_OPEN_CURRENT:
4414 s = ":open ";
4415 /* FALL THROUGH */
4416 case XT_CMD_TABNEW_CURRENT:
4417 if (!s) /* FALL THROUGH? */
4418 s = ":tabnew ";
4419 if ((uri = get_uri(t)) != NULL) {
4420 ss = g_strdup_printf("%s%s", s, uri);
4421 s = ss;
4423 break;
4424 default:
4425 show_oops(t, "command: invalid opcode %d", args->i);
4426 return (XT_CB_PASSTHROUGH);
4429 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4431 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4432 gdk_color_parse(XT_COLOR_WHITE, &color);
4433 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4434 show_cmd(t);
4435 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4436 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4438 if (ss)
4439 g_free(ss);
4441 return (XT_CB_HANDLED);
4445 * Return a new string with a download row (in html)
4446 * appended. Old string is freed.
4448 char *
4449 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4452 WebKitDownloadStatus stat;
4453 char *status_html = NULL, *cmd_html = NULL, *new_html;
4454 gdouble progress;
4455 char cur_sz[FMT_SCALED_STRSIZE];
4456 char tot_sz[FMT_SCALED_STRSIZE];
4457 char *xtp_prefix;
4459 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4461 /* All actions wil take this form:
4462 * xxxt://class/seskey
4464 xtp_prefix = g_strdup_printf("%s%d/%s/",
4465 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4467 stat = webkit_download_get_status(dl->download);
4469 switch (stat) {
4470 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4471 status_html = g_strdup_printf("Finished");
4472 cmd_html = g_strdup_printf(
4473 "<a href='%s%d/%d'>Remove</a>",
4474 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4475 break;
4476 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4477 /* gather size info */
4478 progress = 100 * webkit_download_get_progress(dl->download);
4480 fmt_scaled(
4481 webkit_download_get_current_size(dl->download), cur_sz);
4482 fmt_scaled(
4483 webkit_download_get_total_size(dl->download), tot_sz);
4485 status_html = g_strdup_printf(
4486 "<div style='width: 100%%' align='center'>"
4487 "<div class='progress-outer'>"
4488 "<div class='progress-inner' style='width: %.2f%%'>"
4489 "</div></div></div>"
4490 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4491 progress, cur_sz, tot_sz, progress);
4493 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4494 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4496 break;
4497 /* LLL */
4498 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4499 status_html = g_strdup_printf("Cancelled");
4500 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4501 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4502 break;
4503 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4504 status_html = g_strdup_printf("Error!");
4505 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4506 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4507 break;
4508 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4509 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4510 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4511 status_html = g_strdup_printf("Starting");
4512 break;
4513 default:
4514 show_oops(t, "%s: unknown download status", __func__);
4517 new_html = g_strdup_printf(
4518 "%s\n<tr><td>%s</td><td>%s</td>"
4519 "<td style='text-align:center'>%s</td></tr>\n",
4520 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4521 status_html, cmd_html);
4522 g_free(html);
4524 if (status_html)
4525 g_free(status_html);
4527 if (cmd_html)
4528 g_free(cmd_html);
4530 g_free(xtp_prefix);
4532 return new_html;
4536 * update all download tabs apart from one. Pass NULL if
4537 * you want to update all.
4539 void
4540 update_download_tabs(struct tab *apart_from)
4542 struct tab *t;
4543 if (!updating_dl_tabs) {
4544 updating_dl_tabs = 1; /* stop infinite recursion */
4545 TAILQ_FOREACH(t, &tabs, entry)
4546 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4547 && (t != apart_from))
4548 xtp_page_dl(t, NULL);
4549 updating_dl_tabs = 0;
4554 * update all cookie tabs apart from one. Pass NULL if
4555 * you want to update all.
4557 void
4558 update_cookie_tabs(struct tab *apart_from)
4560 struct tab *t;
4561 if (!updating_cl_tabs) {
4562 updating_cl_tabs = 1; /* stop infinite recursion */
4563 TAILQ_FOREACH(t, &tabs, entry)
4564 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4565 && (t != apart_from))
4566 xtp_page_cl(t, NULL);
4567 updating_cl_tabs = 0;
4572 * update all history tabs apart from one. Pass NULL if
4573 * you want to update all.
4575 void
4576 update_history_tabs(struct tab *apart_from)
4578 struct tab *t;
4580 if (!updating_hl_tabs) {
4581 updating_hl_tabs = 1; /* stop infinite recursion */
4582 TAILQ_FOREACH(t, &tabs, entry)
4583 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4584 && (t != apart_from))
4585 xtp_page_hl(t, NULL);
4586 updating_hl_tabs = 0;
4590 /* cookie management XTP page */
4592 xtp_page_cl(struct tab *t, struct karg *args)
4594 char *body, *page, *tmp;
4595 int i = 1; /* all ids start 1 */
4596 GSList *sc, *pc, *pc_start;
4597 SoupCookie *c;
4598 char *type, *table_headers, *last_domain;
4600 DNPRINTF(XT_D_CMD, "%s", __func__);
4602 if (t == NULL) {
4603 show_oops(NULL, "%s invalid parameters", __func__);
4604 return (1);
4607 /* Generate a new session key */
4608 if (!updating_cl_tabs)
4609 generate_xtp_session_key(&cl_session_key);
4611 /* table headers */
4612 table_headers = g_strdup_printf("<table><tr>"
4613 "<th>Type</th>"
4614 "<th>Name</th>"
4615 "<th style='width:200px'>Value</th>"
4616 "<th>Path</th>"
4617 "<th>Expires</th>"
4618 "<th>Secure</th>"
4619 "<th>HTTP<br />only</th>"
4620 "<th style='width:40px'>Rm</th></tr>\n");
4622 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4623 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4624 pc_start = pc;
4626 body = NULL;
4627 last_domain = strdup("");
4628 for (; sc; sc = sc->next) {
4629 c = sc->data;
4631 if (strcmp(last_domain, c->domain) != 0) {
4632 /* new domain */
4633 free(last_domain);
4634 last_domain = strdup(c->domain);
4636 if (body != NULL) {
4637 tmp = body;
4638 body = g_strdup_printf("%s</table>"
4639 "<h2>%s</h2>%s\n",
4640 body, c->domain, table_headers);
4641 g_free(tmp);
4642 } else {
4643 /* first domain */
4644 body = g_strdup_printf("<h2>%s</h2>%s\n",
4645 c->domain, table_headers);
4649 type = "Session";
4650 for (pc = pc_start; pc; pc = pc->next)
4651 if (soup_cookie_equal(pc->data, c)) {
4652 type = "Session + Persistent";
4653 break;
4656 tmp = body;
4657 body = g_strdup_printf(
4658 "%s\n<tr>"
4659 "<td>%s</td>"
4660 "<td style='word-wrap:normal'>%s</td>"
4661 "<td>"
4662 " <textarea rows='4'>%s</textarea>"
4663 "</td>"
4664 "<td>%s</td>"
4665 "<td>%s</td>"
4666 "<td>%d</td>"
4667 "<td>%d</td>"
4668 "<td style='text-align:center'>"
4669 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4670 body,
4671 type,
4672 c->name,
4673 c->value,
4674 c->path,
4675 c->expires ?
4676 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4677 c->secure,
4678 c->http_only,
4680 XT_XTP_STR,
4681 XT_XTP_CL,
4682 cl_session_key,
4683 XT_XTP_CL_REMOVE,
4687 g_free(tmp);
4688 i++;
4691 soup_cookies_free(sc);
4692 soup_cookies_free(pc);
4694 /* small message if there are none */
4695 if (i == 1) {
4696 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4697 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4699 tmp = body;
4700 body = g_strdup_printf("%s</table>", body);
4701 g_free(tmp);
4703 page = get_html_page("Cookie Jar", body, "", TRUE);
4704 g_free(body);
4705 g_free(table_headers);
4706 g_free(last_domain);
4708 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4709 update_cookie_tabs(t);
4711 g_free(page);
4713 return (0);
4717 xtp_page_hl(struct tab *t, struct karg *args)
4719 char *body, *page, *tmp;
4720 struct history *h;
4721 int i = 1; /* all ids start 1 */
4723 DNPRINTF(XT_D_CMD, "%s", __func__);
4725 if (t == NULL) {
4726 show_oops(NULL, "%s invalid parameters", __func__);
4727 return (1);
4730 /* Generate a new session key */
4731 if (!updating_hl_tabs)
4732 generate_xtp_session_key(&hl_session_key);
4734 /* body */
4735 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4736 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4738 RB_FOREACH_REVERSE(h, history_list, &hl) {
4739 tmp = body;
4740 body = g_strdup_printf(
4741 "%s\n<tr>"
4742 "<td><a href='%s'>%s</a></td>"
4743 "<td>%s</td>"
4744 "<td style='text-align: center'>"
4745 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4746 body, h->uri, h->uri, h->title,
4747 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4748 XT_XTP_HL_REMOVE, i);
4750 g_free(tmp);
4751 i++;
4754 /* small message if there are none */
4755 if (i == 1) {
4756 tmp = body;
4757 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4758 "colspan='3'>No History</td></tr>\n", body);
4759 g_free(tmp);
4762 tmp = body;
4763 body = g_strdup_printf("%s</table>", body);
4764 g_free(tmp);
4766 page = get_html_page("History", body, "", TRUE);
4767 g_free(body);
4770 * update all history manager tabs as the xtp session
4771 * key has now changed. No need to update the current tab.
4772 * Already did that above.
4774 update_history_tabs(t);
4776 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4777 g_free(page);
4779 return (0);
4783 * Generate a web page detailing the status of any downloads
4786 xtp_page_dl(struct tab *t, struct karg *args)
4788 struct download *dl;
4789 char *body, *page, *tmp;
4790 char *ref;
4791 int n_dl = 1;
4793 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4795 if (t == NULL) {
4796 show_oops(NULL, "%s invalid parameters", __func__);
4797 return (1);
4801 * Generate a new session key for next page instance.
4802 * This only happens for the top level call to xtp_page_dl()
4803 * in which case updating_dl_tabs is 0.
4805 if (!updating_dl_tabs)
4806 generate_xtp_session_key(&dl_session_key);
4808 /* header - with refresh so as to update */
4809 if (refresh_interval >= 1)
4810 ref = g_strdup_printf(
4811 "<meta http-equiv='refresh' content='%u"
4812 ";url=%s%d/%s/%d' />\n",
4813 refresh_interval,
4814 XT_XTP_STR,
4815 XT_XTP_DL,
4816 dl_session_key,
4817 XT_XTP_DL_LIST);
4818 else
4819 ref = g_strdup("");
4821 body = g_strdup_printf("<div align='center'>"
4822 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4823 "</p><table><tr><th style='width: 60%%'>"
4824 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4825 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4827 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4828 body = xtp_page_dl_row(t, body, dl);
4829 n_dl++;
4832 /* message if no downloads in list */
4833 if (n_dl == 1) {
4834 tmp = body;
4835 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4836 " style='text-align: center'>"
4837 "No downloads</td></tr>\n", body);
4838 g_free(tmp);
4841 tmp = body;
4842 body = g_strdup_printf("%s</table></div>", body);
4843 g_free(tmp);
4845 page = get_html_page("Downloads", body, ref, 1);
4846 g_free(ref);
4847 g_free(body);
4850 * update all download manager tabs as the xtp session
4851 * key has now changed. No need to update the current tab.
4852 * Already did that above.
4854 update_download_tabs(t);
4856 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4857 g_free(page);
4859 return (0);
4863 search(struct tab *t, struct karg *args)
4865 gboolean d;
4867 if (t == NULL || args == NULL) {
4868 show_oops(NULL, "search invalid parameters");
4869 return (1);
4871 if (t->search_text == NULL) {
4872 if (global_search == NULL)
4873 return (XT_CB_PASSTHROUGH);
4874 else {
4875 t->search_text = g_strdup(global_search);
4876 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4877 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4881 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4882 t->tab_id, args->i, t->search_forward, t->search_text);
4884 switch (args->i) {
4885 case XT_SEARCH_NEXT:
4886 d = t->search_forward;
4887 break;
4888 case XT_SEARCH_PREV:
4889 d = !t->search_forward;
4890 break;
4891 default:
4892 return (XT_CB_PASSTHROUGH);
4895 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4897 return (XT_CB_HANDLED);
4900 struct settings_args {
4901 char **body;
4902 int i;
4905 void
4906 print_setting(struct settings *s, char *val, void *cb_args)
4908 char *tmp, *color;
4909 struct settings_args *sa = cb_args;
4911 if (sa == NULL)
4912 return;
4914 if (s->flags & XT_SF_RUNTIME)
4915 color = "#22cc22";
4916 else
4917 color = "#cccccc";
4919 tmp = *sa->body;
4920 *sa->body = g_strdup_printf(
4921 "%s\n<tr>"
4922 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4923 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4924 *sa->body,
4925 color,
4926 s->name,
4927 color,
4930 g_free(tmp);
4931 sa->i++;
4935 set_show(struct tab *t, struct karg *args)
4937 char *body, *page, *tmp;
4938 int i = 1;
4939 struct settings_args sa;
4941 bzero(&sa, sizeof sa);
4942 sa.body = &body;
4944 /* body */
4945 body = g_strdup_printf("<div align='center'><table><tr>"
4946 "<th align='left'>Setting</th>"
4947 "<th align='left'>Value</th></tr>\n");
4949 settings_walk(print_setting, &sa);
4950 i = sa.i;
4952 /* small message if there are none */
4953 if (i == 1) {
4954 tmp = body;
4955 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4956 "colspan='2'>No settings</td></tr>\n", body);
4957 g_free(tmp);
4960 tmp = body;
4961 body = g_strdup_printf("%s</table></div>", body);
4962 g_free(tmp);
4964 page = get_html_page("Settings", body, "", 0);
4966 g_free(body);
4968 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4970 g_free(page);
4972 return (XT_CB_PASSTHROUGH);
4976 set(struct tab *t, struct karg *args)
4978 char *p, *val;
4979 int i;
4981 if (args == NULL || args->s == NULL)
4982 return (set_show(t, args));
4984 /* strip spaces */
4985 p = g_strstrip(args->s);
4987 if (strlen(p) == 0)
4988 return (set_show(t, args));
4990 /* we got some sort of string */
4991 val = g_strrstr(p, "=");
4992 if (val) {
4993 *val++ = '\0';
4994 val = g_strchomp(val);
4995 p = g_strchomp(p);
4997 for (i = 0; i < LENGTH(rs); i++) {
4998 if (strcmp(rs[i].name, p))
4999 continue;
5001 if (rs[i].activate) {
5002 if (rs[i].activate(val))
5003 show_oops(t, "%s invalid value %s",
5004 p, val);
5005 else
5006 show_oops(t, ":set %s = %s", p, val);
5007 goto done;
5008 } else {
5009 show_oops(t, "not a runtime option: %s", p);
5010 goto done;
5013 show_oops(t, "unknown option: %s", p);
5014 } else {
5015 p = g_strchomp(p);
5017 for (i = 0; i < LENGTH(rs); i++) {
5018 if (strcmp(rs[i].name, p))
5019 continue;
5021 /* XXX this could use some cleanup */
5022 switch (rs[i].type) {
5023 case XT_S_INT:
5024 if (rs[i].ival)
5025 show_oops(t, "%s = %d",
5026 rs[i].name, *rs[i].ival);
5027 else if (rs[i].s && rs[i].s->get)
5028 show_oops(t, "%s = %s",
5029 rs[i].name,
5030 rs[i].s->get(&rs[i]));
5031 else if (rs[i].s && rs[i].s->get == NULL)
5032 show_oops(t, "%s = ...", rs[i].name);
5033 else
5034 show_oops(t, "%s = ", rs[i].name);
5035 break;
5036 case XT_S_FLOAT:
5037 if (rs[i].fval)
5038 show_oops(t, "%s = %f",
5039 rs[i].name, *rs[i].fval);
5040 else if (rs[i].s && rs[i].s->get)
5041 show_oops(t, "%s = %s",
5042 rs[i].name,
5043 rs[i].s->get(&rs[i]));
5044 else if (rs[i].s && rs[i].s->get == NULL)
5045 show_oops(t, "%s = ...", rs[i].name);
5046 else
5047 show_oops(t, "%s = ", rs[i].name);
5048 break;
5049 case XT_S_STR:
5050 if (rs[i].sval && *rs[i].sval)
5051 show_oops(t, "%s = %s",
5052 rs[i].name, *rs[i].sval);
5053 else if (rs[i].s && rs[i].s->get)
5054 show_oops(t, "%s = %s",
5055 rs[i].name,
5056 rs[i].s->get(&rs[i]));
5057 else if (rs[i].s && rs[i].s->get == NULL)
5058 show_oops(t, "%s = ...", rs[i].name);
5059 else
5060 show_oops(t, "%s = ", rs[i].name);
5061 break;
5062 default:
5063 show_oops(t, "unknown type for %s", rs[i].name);
5064 goto done;
5067 goto done;
5069 show_oops(t, "unknown option: %s", p);
5071 done:
5072 return (XT_CB_PASSTHROUGH);
5076 session_save(struct tab *t, char *filename)
5078 struct karg a;
5079 int rv = 1;
5081 if (strlen(filename) == 0)
5082 goto done;
5084 if (filename[0] == '.' || filename[0] == '/')
5085 goto done;
5087 a.s = filename;
5088 if (save_tabs(t, &a))
5089 goto done;
5090 strlcpy(named_session, filename, sizeof named_session);
5092 rv = 0;
5093 done:
5094 return (rv);
5098 session_open(struct tab *t, char *filename)
5100 struct karg a;
5101 int rv = 1;
5103 if (strlen(filename) == 0)
5104 goto done;
5106 if (filename[0] == '.' || filename[0] == '/')
5107 goto done;
5109 a.s = filename;
5110 a.i = XT_SES_CLOSETABS;
5111 if (open_tabs(t, &a))
5112 goto done;
5114 strlcpy(named_session, filename, sizeof named_session);
5116 rv = 0;
5117 done:
5118 return (rv);
5122 session_delete(struct tab *t, char *filename)
5124 char file[PATH_MAX];
5125 int rv = 1;
5127 if (strlen(filename) == 0)
5128 goto done;
5130 if (filename[0] == '.' || filename[0] == '/')
5131 goto done;
5133 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5134 if (unlink(file))
5135 goto done;
5137 if (!strcmp(filename, named_session))
5138 strlcpy(named_session, XT_SAVED_TABS_FILE,
5139 sizeof named_session);
5141 rv = 0;
5142 done:
5143 return (rv);
5147 session_cmd(struct tab *t, struct karg *args)
5149 char *filename = args->s;
5151 if (t == NULL)
5152 return (1);
5154 if (args->i & XT_SHOW)
5155 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5156 XT_SAVED_TABS_FILE : named_session);
5157 else if (args->i & XT_SAVE) {
5158 if (session_save(t, filename)) {
5159 show_oops(t, "Can't save session: %s",
5160 filename ? filename : "INVALID");
5161 goto done;
5163 } else if (args->i & XT_OPEN) {
5164 if (session_open(t, filename)) {
5165 show_oops(t, "Can't open session: %s",
5166 filename ? filename : "INVALID");
5167 goto done;
5169 } else if (args->i & XT_DELETE) {
5170 if (session_delete(t, filename)) {
5171 show_oops(t, "Can't delete session: %s",
5172 filename ? filename : "INVALID");
5173 goto done;
5176 done:
5177 return (XT_CB_PASSTHROUGH);
5181 * Make a hardcopy of the page
5184 print_page(struct tab *t, struct karg *args)
5186 WebKitWebFrame *frame;
5187 GtkPageSetup *ps;
5188 GtkPrintOperation *op;
5189 GtkPrintOperationAction action;
5190 GtkPrintOperationResult print_res;
5191 GError *g_err = NULL;
5192 int marg_l, marg_r, marg_t, marg_b;
5194 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5196 ps = gtk_page_setup_new();
5197 op = gtk_print_operation_new();
5198 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5199 frame = webkit_web_view_get_main_frame(t->wv);
5201 /* the default margins are too small, so we will bump them */
5202 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5203 XT_PRINT_EXTRA_MARGIN;
5204 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5205 XT_PRINT_EXTRA_MARGIN;
5206 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5207 XT_PRINT_EXTRA_MARGIN;
5208 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5209 XT_PRINT_EXTRA_MARGIN;
5211 /* set margins */
5212 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5213 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5214 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5215 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5217 gtk_print_operation_set_default_page_setup(op, ps);
5219 /* this appears to free 'op' and 'ps' */
5220 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5222 /* check it worked */
5223 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5224 show_oops(NULL, "can't print: %s", g_err->message);
5225 g_error_free (g_err);
5226 return (1);
5229 return (0);
5233 go_home(struct tab *t, struct karg *args)
5235 load_uri(t, home);
5236 return (0);
5240 restart(struct tab *t, struct karg *args)
5242 struct karg a;
5244 a.s = XT_RESTART_TABS_FILE;
5245 save_tabs(t, &a);
5246 execvp(start_argv[0], start_argv);
5247 /* NOTREACHED */
5249 return (0);
5252 #define CTRL GDK_CONTROL_MASK
5253 #define MOD1 GDK_MOD1_MASK
5254 #define SHFT GDK_SHIFT_MASK
5256 /* inherent to GTK not all keys will be caught at all times */
5257 /* XXX sort key bindings */
5258 struct key_binding {
5259 char *cmd;
5260 guint mask;
5261 guint use_in_entry;
5262 guint key;
5263 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5264 } keys[] = {
5265 { "cookiejar", MOD1, 0, GDK_j },
5266 { "downloadmgr", MOD1, 0, GDK_d },
5267 { "history", MOD1, 0, GDK_h },
5268 { "print", CTRL, 0, GDK_p },
5269 { "search", 0, 0, GDK_slash },
5270 { "searchb", 0, 0, GDK_question },
5271 { "statustoggle", CTRL, 0, GDK_n },
5272 { "command", 0, 0, GDK_colon },
5273 { "qa", CTRL, 0, GDK_q },
5274 { "restart", MOD1, 0, GDK_q },
5275 { "js toggle", CTRL, 0, GDK_j },
5276 { "cookie toggle", MOD1, 0, GDK_c },
5277 { "togglesrc", CTRL, 0, GDK_s },
5278 { "yankuri", 0, 0, GDK_y },
5279 { "pasteuricur", 0, 0, GDK_p },
5280 { "pasteurinew", 0, 0, GDK_P },
5281 { "toplevel toggle", 0, 0, GDK_F4 },
5282 { "help", 0, 0, GDK_F1 },
5283 { "run_script", MOD1, 0, GDK_r },
5285 /* search */
5286 { "searchnext", 0, 0, GDK_n },
5287 { "searchprevious", 0, 0, GDK_N },
5289 /* focus */
5290 { "focusaddress", 0, 0, GDK_F6 },
5291 { "focussearch", 0, 0, GDK_F7 },
5293 /* hinting */
5294 { "hinting", 0, 0, GDK_f },
5296 /* custom stylesheet */
5297 { "userstyle", 0, 0, GDK_i },
5299 /* navigation */
5300 { "goback", 0, 0, GDK_BackSpace },
5301 { "goback", MOD1, 0, GDK_Left },
5302 { "goforward", SHFT, 0, GDK_BackSpace },
5303 { "goforward", MOD1, 0, GDK_Right },
5304 { "reload", 0, 0, GDK_F5 },
5305 { "reload", CTRL, 0, GDK_r },
5306 { "reload", CTRL, 0, GDK_l },
5307 { "favorites", MOD1, 1, GDK_f },
5309 /* vertical movement */
5310 { "scrolldown", 0, 0, GDK_j },
5311 { "scrolldown", 0, 0, GDK_Down },
5312 { "scrollup", 0, 0, GDK_Up },
5313 { "scrollup", 0, 0, GDK_k },
5314 { "scrollbottom", 0, 0, GDK_G },
5315 { "scrollbottom", 0, 0, GDK_End },
5316 { "scrolltop", 0, 0, GDK_Home },
5317 { "scrollpagedown", 0, 0, GDK_space },
5318 { "scrollpagedown", CTRL, 0, GDK_f },
5319 { "scrollhalfdown", CTRL, 0, GDK_d },
5320 { "scrollpagedown", 0, 0, GDK_Page_Down },
5321 { "scrollpageup", 0, 0, GDK_Page_Up },
5322 { "scrollpageup", CTRL, 0, GDK_b },
5323 { "scrollhalfup", CTRL, 0, GDK_u },
5324 /* horizontal movement */
5325 { "scrollright", 0, 0, GDK_l },
5326 { "scrollright", 0, 0, GDK_Right },
5327 { "scrollleft", 0, 0, GDK_Left },
5328 { "scrollleft", 0, 0, GDK_h },
5329 { "scrollfarright", 0, 0, GDK_dollar },
5330 { "scrollfarleft", 0, 0, GDK_0 },
5332 /* tabs */
5333 { "tabnew", CTRL, 0, GDK_t },
5334 { "999tabnew", CTRL, 0, GDK_T },
5335 { "tabclose", CTRL, 1, GDK_w },
5336 { "tabundoclose", 0, 0, GDK_U },
5337 { "tabnext 1", CTRL, 0, GDK_1 },
5338 { "tabnext 2", CTRL, 0, GDK_2 },
5339 { "tabnext 3", CTRL, 0, GDK_3 },
5340 { "tabnext 4", CTRL, 0, GDK_4 },
5341 { "tabnext 5", CTRL, 0, GDK_5 },
5342 { "tabnext 6", CTRL, 0, GDK_6 },
5343 { "tabnext 7", CTRL, 0, GDK_7 },
5344 { "tabnext 8", CTRL, 0, GDK_8 },
5345 { "tabnext 9", CTRL, 0, GDK_9 },
5346 { "tabfirst", CTRL, 0, GDK_less },
5347 { "tablast", CTRL, 0, GDK_greater },
5348 { "tabprevious", CTRL, 0, GDK_Left },
5349 { "tabnext", CTRL, 0, GDK_Right },
5350 { "focusout", CTRL, 0, GDK_minus },
5351 { "focusin", CTRL, 0, GDK_plus },
5352 { "focusin", CTRL, 0, GDK_equal },
5353 { "focusreset", CTRL, 0, GDK_0 },
5355 /* command aliases (handy when -S flag is used) */
5356 { "promptopen", 0, 0, GDK_F9 },
5357 { "promptopencurrent", 0, 0, GDK_F10 },
5358 { "prompttabnew", 0, 0, GDK_F11 },
5359 { "prompttabnewcurrent",0, 0, GDK_F12 },
5361 TAILQ_HEAD(keybinding_list, key_binding);
5363 void
5364 walk_kb(struct settings *s,
5365 void (*cb)(struct settings *, char *, void *), void *cb_args)
5367 struct key_binding *k;
5368 char str[1024];
5370 if (s == NULL || cb == NULL) {
5371 show_oops(NULL, "walk_kb invalid parameters");
5372 return;
5375 TAILQ_FOREACH(k, &kbl, entry) {
5376 if (k->cmd == NULL)
5377 continue;
5378 str[0] = '\0';
5380 /* sanity */
5381 if (gdk_keyval_name(k->key) == NULL)
5382 continue;
5384 strlcat(str, k->cmd, sizeof str);
5385 strlcat(str, ",", sizeof str);
5387 if (k->mask & GDK_SHIFT_MASK)
5388 strlcat(str, "S-", sizeof str);
5389 if (k->mask & GDK_CONTROL_MASK)
5390 strlcat(str, "C-", sizeof str);
5391 if (k->mask & GDK_MOD1_MASK)
5392 strlcat(str, "M1-", sizeof str);
5393 if (k->mask & GDK_MOD2_MASK)
5394 strlcat(str, "M2-", sizeof str);
5395 if (k->mask & GDK_MOD3_MASK)
5396 strlcat(str, "M3-", sizeof str);
5397 if (k->mask & GDK_MOD4_MASK)
5398 strlcat(str, "M4-", sizeof str);
5399 if (k->mask & GDK_MOD5_MASK)
5400 strlcat(str, "M5-", sizeof str);
5402 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5403 cb(s, str, cb_args);
5407 void
5408 init_keybindings(void)
5410 int i;
5411 struct key_binding *k;
5413 for (i = 0; i < LENGTH(keys); i++) {
5414 k = g_malloc0(sizeof *k);
5415 k->cmd = keys[i].cmd;
5416 k->mask = keys[i].mask;
5417 k->use_in_entry = keys[i].use_in_entry;
5418 k->key = keys[i].key;
5419 TAILQ_INSERT_HEAD(&kbl, k, entry);
5421 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5422 k->cmd ? k->cmd : "unnamed key");
5426 void
5427 keybinding_clearall(void)
5429 struct key_binding *k, *next;
5431 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5432 next = TAILQ_NEXT(k, entry);
5433 if (k->cmd == NULL)
5434 continue;
5436 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5437 k->cmd ? k->cmd : "unnamed key");
5438 TAILQ_REMOVE(&kbl, k, entry);
5439 g_free(k);
5444 keybinding_add(char *cmd, char *key, int use_in_entry)
5446 struct key_binding *k;
5447 guint keyval, mask = 0;
5448 int i;
5450 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5452 /* Keys which are to be used in entry have been prefixed with an
5453 * exclamation mark. */
5454 if (use_in_entry)
5455 key++;
5457 /* find modifier keys */
5458 if (strstr(key, "S-"))
5459 mask |= GDK_SHIFT_MASK;
5460 if (strstr(key, "C-"))
5461 mask |= GDK_CONTROL_MASK;
5462 if (strstr(key, "M1-"))
5463 mask |= GDK_MOD1_MASK;
5464 if (strstr(key, "M2-"))
5465 mask |= GDK_MOD2_MASK;
5466 if (strstr(key, "M3-"))
5467 mask |= GDK_MOD3_MASK;
5468 if (strstr(key, "M4-"))
5469 mask |= GDK_MOD4_MASK;
5470 if (strstr(key, "M5-"))
5471 mask |= GDK_MOD5_MASK;
5473 /* find keyname */
5474 for (i = strlen(key) - 1; i > 0; i--)
5475 if (key[i] == '-')
5476 key = &key[i + 1];
5478 /* validate keyname */
5479 keyval = gdk_keyval_from_name(key);
5480 if (keyval == GDK_VoidSymbol) {
5481 warnx("invalid keybinding name %s", key);
5482 return (1);
5484 /* must run this test too, gtk+ doesn't handle 10 for example */
5485 if (gdk_keyval_name(keyval) == NULL) {
5486 warnx("invalid keybinding name %s", key);
5487 return (1);
5490 /* Remove eventual dupes. */
5491 TAILQ_FOREACH(k, &kbl, entry)
5492 if (k->key == keyval && k->mask == mask) {
5493 TAILQ_REMOVE(&kbl, k, entry);
5494 g_free(k);
5495 break;
5498 /* add keyname */
5499 k = g_malloc0(sizeof *k);
5500 k->cmd = g_strdup(cmd);
5501 k->mask = mask;
5502 k->use_in_entry = use_in_entry;
5503 k->key = keyval;
5505 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5506 k->cmd,
5507 k->mask,
5508 k->use_in_entry,
5509 k->key);
5510 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5511 k->cmd, gdk_keyval_name(keyval));
5513 TAILQ_INSERT_HEAD(&kbl, k, entry);
5515 return (0);
5519 add_kb(struct settings *s, char *entry)
5521 char *kb, *key;
5523 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5525 /* clearall is special */
5526 if (!strcmp(entry, "clearall")) {
5527 keybinding_clearall();
5528 return (0);
5531 kb = strstr(entry, ",");
5532 if (kb == NULL)
5533 return (1);
5534 *kb = '\0';
5535 key = kb + 1;
5537 return (keybinding_add(entry, key, key[0] == '!'));
5540 struct cmd {
5541 char *cmd;
5542 int level;
5543 int (*func)(struct tab *, struct karg *);
5544 int arg;
5545 int type;
5546 } cmds[] = {
5547 { "command", 0, command, ':', 0 },
5548 { "search", 0, command, '/', 0 },
5549 { "searchb", 0, command, '?', 0 },
5550 { "togglesrc", 0, toggle_src, 0, 0 },
5552 /* yanking and pasting */
5553 { "yankuri", 0, yank_uri, 0, 0 },
5554 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5555 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5556 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5558 /* search */
5559 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5560 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5562 /* focus */
5563 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5564 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5566 /* hinting */
5567 { "hinting", 0, hint, 0, 0 },
5569 /* custom stylesheet */
5570 { "userstyle", 0, userstyle, 0, 0 },
5572 /* navigation */
5573 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5574 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5575 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5577 /* vertical movement */
5578 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5579 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5580 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5581 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5582 { "1", 0, move, XT_MOVE_TOP, 0 },
5583 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5584 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5585 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5586 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5587 /* horizontal movement */
5588 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5589 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5590 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5591 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5593 { "favorites", 0, xtp_page_fl, 0, 0 },
5594 { "fav", 0, xtp_page_fl, 0, 0 },
5595 { "favadd", 0, add_favorite, 0, 0 },
5597 { "qall", 0, quit, 0, 0 },
5598 { "quitall", 0, quit, 0, 0 },
5599 { "w", 0, save_tabs, 0, 0 },
5600 { "wq", 0, save_tabs_and_quit, 0, 0 },
5601 { "help", 0, help, 0, 0 },
5602 { "about", 0, about, 0, 0 },
5603 { "stats", 0, stats, 0, 0 },
5604 { "version", 0, about, 0, 0 },
5606 /* js command */
5607 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5608 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5609 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5610 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5611 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5612 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5613 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5614 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5615 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5616 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5617 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5619 /* cookie command */
5620 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5621 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5622 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5623 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5624 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5625 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5626 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5627 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5628 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5629 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5630 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5632 /* toplevel (domain) command */
5633 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5634 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5636 /* cookie jar */
5637 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5639 /* cert command */
5640 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5641 { "save", 1, cert_cmd, XT_SAVE, 0 },
5642 { "show", 1, cert_cmd, XT_SHOW, 0 },
5644 { "ca", 0, ca_cmd, 0, 0 },
5645 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5646 { "dl", 0, xtp_page_dl, 0, 0 },
5647 { "h", 0, xtp_page_hl, 0, 0 },
5648 { "history", 0, xtp_page_hl, 0, 0 },
5649 { "home", 0, go_home, 0, 0 },
5650 { "restart", 0, restart, 0, 0 },
5651 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5652 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5653 { "statustoggle", 0, statustoggle, 0, 0 },
5654 { "run_script", 0, run_page_script, 0, XT_USERARG },
5656 { "print", 0, print_page, 0, 0 },
5658 /* tabs */
5659 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5660 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5661 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5662 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5663 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5664 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5665 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5666 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5667 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5668 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5669 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5670 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5671 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5672 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5673 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5674 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5675 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5676 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5677 { "buffers", 0, buffers, 0, 0 },
5678 { "ls", 0, buffers, 0, 0 },
5679 { "tabs", 0, buffers, 0, 0 },
5681 /* command aliases (handy when -S flag is used) */
5682 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5683 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5684 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5685 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5687 /* settings */
5688 { "set", 0, set, 0, XT_USERARG },
5690 { "fullscreen", 0, fullscreen, 0, 0 },
5691 { "f", 0, fullscreen, 0, 0 },
5693 /* sessions */
5694 { "session", 0, session_cmd, XT_SHOW, 0 },
5695 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5696 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5697 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5698 { "show", 1, session_cmd, XT_SHOW, 0 },
5701 struct {
5702 int index;
5703 int len;
5704 gchar *list[256];
5705 } cmd_status = {-1, 0};
5707 gboolean
5708 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5711 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5712 btn_down = 0;
5714 return (FALSE);
5717 gboolean
5718 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5720 struct karg a;
5722 hide_oops(t);
5723 hide_buffers(t);
5725 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5726 btn_down = 1;
5727 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5728 /* go backward */
5729 a.i = XT_NAV_BACK;
5730 navaction(t, &a);
5732 return (TRUE);
5733 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5734 /* go forward */
5735 a.i = XT_NAV_FORWARD;
5736 navaction(t, &a);
5738 return (TRUE);
5741 return (FALSE);
5744 gboolean
5745 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5747 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5749 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5750 delete_tab(t);
5752 return (FALSE);
5756 * cancel, remove, etc. downloads
5758 void
5759 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5761 struct download find, *d = NULL;
5763 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5765 /* some commands require a valid download id */
5766 if (cmd != XT_XTP_DL_LIST) {
5767 /* lookup download in question */
5768 find.id = id;
5769 d = RB_FIND(download_list, &downloads, &find);
5771 if (d == NULL) {
5772 show_oops(t, "%s: no such download", __func__);
5773 return;
5777 /* decide what to do */
5778 switch (cmd) {
5779 case XT_XTP_DL_CANCEL:
5780 webkit_download_cancel(d->download);
5781 break;
5782 case XT_XTP_DL_REMOVE:
5783 webkit_download_cancel(d->download); /* just incase */
5784 g_object_unref(d->download);
5785 RB_REMOVE(download_list, &downloads, d);
5786 break;
5787 case XT_XTP_DL_LIST:
5788 /* Nothing */
5789 break;
5790 default:
5791 show_oops(t, "%s: unknown command", __func__);
5792 break;
5794 xtp_page_dl(t, NULL);
5798 * Actions on history, only does one thing for now, but
5799 * we provide the function for future actions
5801 void
5802 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5804 struct history *h, *next;
5805 int i = 1;
5807 switch (cmd) {
5808 case XT_XTP_HL_REMOVE:
5809 /* walk backwards, as listed in reverse */
5810 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5811 next = RB_PREV(history_list, &hl, h);
5812 if (id == i) {
5813 RB_REMOVE(history_list, &hl, h);
5814 g_free((gpointer) h->title);
5815 g_free((gpointer) h->uri);
5816 g_free(h);
5817 break;
5819 i++;
5821 break;
5822 case XT_XTP_HL_LIST:
5823 /* Nothing - just xtp_page_hl() below */
5824 break;
5825 default:
5826 show_oops(t, "%s: unknown command", __func__);
5827 break;
5830 xtp_page_hl(t, NULL);
5833 /* remove a favorite */
5834 void
5835 remove_favorite(struct tab *t, int index)
5837 char file[PATH_MAX], *title, *uri = NULL;
5838 char *new_favs, *tmp;
5839 FILE *f;
5840 int i;
5841 size_t len, lineno;
5843 /* open favorites */
5844 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5846 if ((f = fopen(file, "r")) == NULL) {
5847 show_oops(t, "%s: can't open favorites: %s",
5848 __func__, strerror(errno));
5849 return;
5852 /* build a string which will become the new favroites file */
5853 new_favs = g_strdup("");
5855 for (i = 1;;) {
5856 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5857 if (feof(f) || ferror(f))
5858 break;
5859 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5860 if (len == 0) {
5861 free(title);
5862 title = NULL;
5863 continue;
5866 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5867 if (feof(f) || ferror(f)) {
5868 show_oops(t, "%s: can't parse favorites %s",
5869 __func__, strerror(errno));
5870 goto clean;
5874 /* as long as this isn't the one we are deleting add to file */
5875 if (i != index) {
5876 tmp = new_favs;
5877 new_favs = g_strdup_printf("%s%s\n%s\n",
5878 new_favs, title, uri);
5879 g_free(tmp);
5882 free(uri);
5883 uri = NULL;
5884 free(title);
5885 title = NULL;
5886 i++;
5888 fclose(f);
5890 /* write back new favorites file */
5891 if ((f = fopen(file, "w")) == NULL) {
5892 show_oops(t, "%s: can't open favorites: %s",
5893 __func__, strerror(errno));
5894 goto clean;
5897 fwrite(new_favs, strlen(new_favs), 1, f);
5898 fclose(f);
5900 clean:
5901 if (uri)
5902 free(uri);
5903 if (title)
5904 free(title);
5906 g_free(new_favs);
5909 void
5910 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5912 switch (cmd) {
5913 case XT_XTP_FL_LIST:
5914 /* nothing, just the below call to xtp_page_fl() */
5915 break;
5916 case XT_XTP_FL_REMOVE:
5917 remove_favorite(t, arg);
5918 break;
5919 default:
5920 show_oops(t, "%s: invalid favorites command", __func__);
5921 break;
5924 xtp_page_fl(t, NULL);
5927 void
5928 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5930 switch (cmd) {
5931 case XT_XTP_CL_LIST:
5932 /* nothing, just xtp_page_cl() */
5933 break;
5934 case XT_XTP_CL_REMOVE:
5935 remove_cookie(arg);
5936 break;
5937 default:
5938 show_oops(t, "%s: unknown cookie xtp command", __func__);
5939 break;
5942 xtp_page_cl(t, NULL);
5945 /* link an XTP class to it's session key and handler function */
5946 struct xtp_despatch {
5947 uint8_t xtp_class;
5948 char **session_key;
5949 void (*handle_func)(struct tab *, uint8_t, int);
5952 struct xtp_despatch xtp_despatches[] = {
5953 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5954 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5955 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5956 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5957 { XT_XTP_INVALID, NULL, NULL }
5961 * is the url xtp protocol? (xxxt://)
5962 * if so, parse and despatch correct bahvior
5965 parse_xtp_url(struct tab *t, const char *url)
5967 char *dup = NULL, *p, *last;
5968 uint8_t n_tokens = 0;
5969 char *tokens[4] = {NULL, NULL, NULL, ""};
5970 struct xtp_despatch *dsp, *dsp_match = NULL;
5971 uint8_t req_class;
5972 int ret = FALSE;
5975 * tokens array meaning:
5976 * tokens[0] = class
5977 * tokens[1] = session key
5978 * tokens[2] = action
5979 * tokens[3] = optional argument
5982 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5984 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5985 goto clean;
5987 dup = g_strdup(url + strlen(XT_XTP_STR));
5989 /* split out the url */
5990 for ((p = strtok_r(dup, "/", &last)); p;
5991 (p = strtok_r(NULL, "/", &last))) {
5992 if (n_tokens < 4)
5993 tokens[n_tokens++] = p;
5996 /* should be atleast three fields 'class/seskey/command/arg' */
5997 if (n_tokens < 3)
5998 goto clean;
6000 dsp = xtp_despatches;
6001 req_class = atoi(tokens[0]);
6002 while (dsp->xtp_class) {
6003 if (dsp->xtp_class == req_class) {
6004 dsp_match = dsp;
6005 break;
6007 dsp++;
6010 /* did we find one atall? */
6011 if (dsp_match == NULL) {
6012 show_oops(t, "%s: no matching xtp despatch found", __func__);
6013 goto clean;
6016 /* check session key and call despatch function */
6017 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6018 ret = TRUE; /* all is well, this was a valid xtp request */
6019 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6022 clean:
6023 if (dup)
6024 g_free(dup);
6026 return (ret);
6031 void
6032 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6034 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6036 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6038 if (t == NULL) {
6039 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6040 return;
6043 if (uri == NULL) {
6044 show_oops(t, "activate_uri_entry_cb no uri");
6045 return;
6048 uri += strspn(uri, "\t ");
6050 /* if xxxt:// treat specially */
6051 if (parse_xtp_url(t, uri))
6052 return;
6054 /* otherwise continue to load page normally */
6055 load_uri(t, (gchar *)uri);
6056 focus_webview(t);
6059 void
6060 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6062 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6063 char *newuri = NULL;
6064 gchar *enc_search;
6066 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6068 if (t == NULL) {
6069 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6070 return;
6073 if (search_string == NULL) {
6074 show_oops(t, "no search_string");
6075 return;
6078 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6080 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6081 newuri = g_strdup_printf(search_string, enc_search);
6082 g_free(enc_search);
6084 marks_clear(t);
6085 webkit_web_view_load_uri(t->wv, newuri);
6086 focus_webview(t);
6088 if (newuri)
6089 g_free(newuri);
6092 void
6093 check_and_set_cookie(const gchar *uri, struct tab *t)
6095 struct domain *d = NULL;
6096 int es = 0;
6098 if (uri == NULL || t == NULL)
6099 return;
6101 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6102 es = 0;
6103 else
6104 es = 1;
6106 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6107 es ? "enable" : "disable", uri);
6109 g_object_set(G_OBJECT(t->settings),
6110 "enable-html5-local-storage", es, (char *)NULL);
6111 webkit_web_view_set_settings(t->wv, t->settings);
6114 void
6115 check_and_set_js(const gchar *uri, struct tab *t)
6117 struct domain *d = NULL;
6118 int es = 0;
6120 if (uri == NULL || t == NULL)
6121 return;
6123 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6124 es = 0;
6125 else
6126 es = 1;
6128 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6129 es ? "enable" : "disable", uri);
6131 g_object_set(G_OBJECT(t->settings),
6132 "enable-scripts", es, (char *)NULL);
6133 g_object_set(G_OBJECT(t->settings),
6134 "javascript-can-open-windows-automatically", es, (char *)NULL);
6135 webkit_web_view_set_settings(t->wv, t->settings);
6137 button_set_stockid(t->js_toggle,
6138 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6141 gboolean
6142 color_address_bar(gpointer p)
6144 GdkColor color;
6145 struct tab *tt, *t = p;
6146 gchar *col_str = XT_COLOR_YELLOW;
6148 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6150 /* make sure t still exists */
6151 if (t == NULL)
6152 goto done;
6153 TAILQ_FOREACH(tt, &tabs, entry)
6154 if (t == tt)
6155 break;
6156 if (t != tt)
6157 goto done;
6159 switch (load_compare_cert(t, NULL)) {
6160 case CERT_LOCAL:
6161 col_str = XT_COLOR_BLUE;
6162 break;
6163 case CERT_TRUSTED:
6164 col_str = XT_COLOR_GREEN;
6165 break;
6166 case CERT_UNTRUSTED:
6167 col_str = XT_COLOR_YELLOW;
6168 break;
6169 case CERT_BAD:
6170 col_str = XT_COLOR_RED;
6171 break;
6174 gdk_color_parse(col_str, &color);
6175 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6177 if (!strcmp(col_str, XT_COLOR_WHITE))
6178 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6179 else
6180 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6182 col_str = NULL;
6183 done:
6184 return (FALSE /* kill thread */);
6187 void
6188 show_ca_status(struct tab *t, const char *uri)
6190 GdkColor color;
6191 gchar *col_str = XT_COLOR_WHITE;
6193 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6194 ssl_strict_certs, ssl_ca_file, uri);
6196 if (t == NULL)
6197 return;
6199 if (uri == NULL)
6200 goto done;
6201 if (ssl_ca_file == NULL) {
6202 if (g_str_has_prefix(uri, "http://"))
6203 goto done;
6204 if (g_str_has_prefix(uri, "https://")) {
6205 col_str = XT_COLOR_RED;
6206 goto done;
6208 return;
6210 if (g_str_has_prefix(uri, "http://") ||
6211 !g_str_has_prefix(uri, "https://"))
6212 goto done;
6214 /* thread the coloring of the address bar */
6215 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6216 color_address_bar, t, NULL);
6217 return;
6219 done:
6220 if (col_str) {
6221 gdk_color_parse(col_str, &color);
6222 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6224 if (!strcmp(col_str, XT_COLOR_WHITE))
6225 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6226 else
6227 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6231 void
6232 free_favicon(struct tab *t)
6234 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6235 __func__, t->icon_download, t->icon_request);
6237 if (t->icon_request)
6238 g_object_unref(t->icon_request);
6239 if (t->icon_dest_uri)
6240 g_free(t->icon_dest_uri);
6242 t->icon_request = NULL;
6243 t->icon_dest_uri = NULL;
6246 void
6247 xt_icon_from_name(struct tab *t, gchar *name)
6249 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6250 GTK_ENTRY_ICON_PRIMARY, "text-html");
6251 if (show_url == 0)
6252 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6253 GTK_ENTRY_ICON_PRIMARY, "text-html");
6254 else
6255 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6256 GTK_ENTRY_ICON_PRIMARY, NULL);
6259 void
6260 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6262 GdkPixbuf *pb_scaled;
6264 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6265 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6266 GDK_INTERP_BILINEAR);
6267 else
6268 pb_scaled = pb;
6270 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6271 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6272 if (show_url == 0)
6273 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6274 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6275 else
6276 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6277 GTK_ENTRY_ICON_PRIMARY, NULL);
6279 if (pb_scaled != pb)
6280 g_object_unref(pb_scaled);
6283 void
6284 xt_icon_from_file(struct tab *t, char *file)
6286 GdkPixbuf *pb;
6288 if (g_str_has_prefix(file, "file://"))
6289 file += strlen("file://");
6291 pb = gdk_pixbuf_new_from_file(file, NULL);
6292 if (pb) {
6293 xt_icon_from_pixbuf(t, pb);
6294 g_object_unref(pb);
6295 } else
6296 xt_icon_from_name(t, "text-html");
6299 gboolean
6300 is_valid_icon(char *file)
6302 gboolean valid = 0;
6303 const char *mime_type;
6304 GFileInfo *fi;
6305 GFile *gf;
6307 gf = g_file_new_for_path(file);
6308 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6309 NULL, NULL);
6310 mime_type = g_file_info_get_content_type(fi);
6311 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6312 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6313 g_strcmp0(mime_type, "image/png") == 0 ||
6314 g_strcmp0(mime_type, "image/gif") == 0 ||
6315 g_strcmp0(mime_type, "application/octet-stream") == 0;
6316 g_object_unref(fi);
6317 g_object_unref(gf);
6319 return (valid);
6322 void
6323 set_favicon_from_file(struct tab *t, char *file)
6325 struct stat sb;
6327 if (t == NULL || file == NULL)
6328 return;
6330 if (g_str_has_prefix(file, "file://"))
6331 file += strlen("file://");
6332 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6334 if (!stat(file, &sb)) {
6335 if (sb.st_size == 0 || !is_valid_icon(file)) {
6336 /* corrupt icon so trash it */
6337 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6338 __func__, file);
6339 unlink(file);
6340 /* no need to set icon to default here */
6341 return;
6344 xt_icon_from_file(t, file);
6347 void
6348 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6349 WebKitWebView *wv)
6351 WebKitDownloadStatus status = webkit_download_get_status(download);
6352 struct tab *tt = NULL, *t = NULL;
6355 * find the webview instead of passing in the tab as it could have been
6356 * deleted from underneath us.
6358 TAILQ_FOREACH(tt, &tabs, entry) {
6359 if (tt->wv == wv) {
6360 t = tt;
6361 break;
6364 if (t == NULL)
6365 return;
6367 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6368 __func__, t->tab_id, status);
6370 switch (status) {
6371 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6372 /* -1 */
6373 t->icon_download = NULL;
6374 free_favicon(t);
6375 break;
6376 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6377 /* 0 */
6378 break;
6379 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6380 /* 1 */
6381 break;
6382 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6383 /* 2 */
6384 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6385 __func__, t->tab_id);
6386 t->icon_download = NULL;
6387 free_favicon(t);
6388 break;
6389 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6390 /* 3 */
6392 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6393 __func__, t->icon_dest_uri);
6394 set_favicon_from_file(t, t->icon_dest_uri);
6395 /* these will be freed post callback */
6396 t->icon_request = NULL;
6397 t->icon_download = NULL;
6398 break;
6399 default:
6400 break;
6404 void
6405 abort_favicon_download(struct tab *t)
6407 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6409 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6410 if (t->icon_download) {
6411 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6412 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6413 webkit_download_cancel(t->icon_download);
6414 t->icon_download = NULL;
6415 } else
6416 free_favicon(t);
6417 #endif
6419 xt_icon_from_name(t, "text-html");
6422 void
6423 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6425 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6427 if (uri == NULL || t == NULL)
6428 return;
6430 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6431 /* take icon from WebKitIconDatabase */
6432 GdkPixbuf *pb;
6434 pb = webkit_web_view_get_icon_pixbuf(wv);
6435 if (pb) {
6436 xt_icon_from_pixbuf(t, pb);
6437 g_object_unref(pb);
6438 } else
6439 xt_icon_from_name(t, "text-html");
6440 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6441 /* download icon to cache dir */
6442 gchar *name_hash, file[PATH_MAX];
6443 struct stat sb;
6445 if (t->icon_request) {
6446 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6447 return;
6450 /* check to see if we got the icon in cache */
6451 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6452 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6453 g_free(name_hash);
6455 if (!stat(file, &sb)) {
6456 if (sb.st_size > 0) {
6457 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6458 __func__, file);
6459 set_favicon_from_file(t, file);
6460 return;
6463 /* corrupt icon so trash it */
6464 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6465 __func__, file);
6466 unlink(file);
6469 /* create download for icon */
6470 t->icon_request = webkit_network_request_new(uri);
6471 if (t->icon_request == NULL) {
6472 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6473 __func__, uri);
6474 return;
6477 t->icon_download = webkit_download_new(t->icon_request);
6478 if (t->icon_download == NULL)
6479 return;
6481 /* we have to free icon_dest_uri later */
6482 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6483 webkit_download_set_destination_uri(t->icon_download,
6484 t->icon_dest_uri);
6486 if (webkit_download_get_status(t->icon_download) ==
6487 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6488 g_object_unref(t->icon_request);
6489 g_free(t->icon_dest_uri);
6490 t->icon_request = NULL;
6491 t->icon_dest_uri = NULL;
6492 return;
6495 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6496 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6498 webkit_download_start(t->icon_download);
6499 #endif
6502 void
6503 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6505 const gchar *uri = NULL, *title = NULL;
6506 struct history *h, find;
6507 struct karg a;
6508 GdkColor color;
6510 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6511 webkit_web_view_get_load_status(wview),
6512 get_uri(t) ? get_uri(t) : "NOTHING");
6514 if (t == NULL) {
6515 show_oops(NULL, "notify_load_status_cb invalid parameters");
6516 return;
6519 switch (webkit_web_view_get_load_status(wview)) {
6520 case WEBKIT_LOAD_PROVISIONAL:
6521 /* 0 */
6522 abort_favicon_download(t);
6523 #if GTK_CHECK_VERSION(2, 20, 0)
6524 gtk_widget_show(t->spinner);
6525 gtk_spinner_start(GTK_SPINNER(t->spinner));
6526 #endif
6527 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6529 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6531 /* assume we are a new address */
6532 gdk_color_parse("white", &color);
6533 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6534 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6536 /* take focus if we are visible */
6537 focus_webview(t);
6538 t->focus_wv = 1;
6540 break;
6542 case WEBKIT_LOAD_COMMITTED:
6543 /* 1 */
6544 uri = get_uri(t);
6545 if (uri == NULL)
6546 return;
6547 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6549 if (t->status) {
6550 g_free(t->status);
6551 t->status = NULL;
6553 set_status(t, (char *)uri, XT_STATUS_LOADING);
6555 /* check if js white listing is enabled */
6556 if (enable_cookie_whitelist)
6557 check_and_set_cookie(uri, t);
6558 if (enable_js_whitelist)
6559 check_and_set_js(uri, t);
6561 if (t->styled)
6562 apply_style(t);
6565 /* we know enough to autosave the session */
6566 if (session_autosave) {
6567 a.s = NULL;
6568 save_tabs(t, &a);
6571 show_ca_status(t, uri);
6572 break;
6574 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6575 /* 3 */
6576 break;
6578 case WEBKIT_LOAD_FINISHED:
6579 /* 2 */
6580 uri = get_uri(t);
6581 if (uri == NULL)
6582 return;
6584 if (!strncmp(uri, "http://", strlen("http://")) ||
6585 !strncmp(uri, "https://", strlen("https://")) ||
6586 !strncmp(uri, "file://", strlen("file://"))) {
6587 find.uri = uri;
6588 h = RB_FIND(history_list, &hl, &find);
6589 if (!h) {
6590 title = get_title(t, FALSE);
6591 h = g_malloc(sizeof *h);
6592 h->uri = g_strdup(uri);
6593 h->title = g_strdup(title);
6594 RB_INSERT(history_list, &hl, h);
6595 completion_add_uri(h->uri);
6596 update_history_tabs(NULL);
6600 set_status(t, (char *)uri, XT_STATUS_URI);
6601 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6602 case WEBKIT_LOAD_FAILED:
6603 /* 4 */
6604 #endif
6605 #if GTK_CHECK_VERSION(2, 20, 0)
6606 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6607 gtk_widget_hide(t->spinner);
6608 #endif
6609 default:
6610 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6611 break;
6614 if (t->item)
6615 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6616 else
6617 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6618 webkit_web_view_can_go_back(wview));
6620 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6621 webkit_web_view_can_go_forward(wview));
6624 #if 0
6625 gboolean
6626 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6627 gchar *uri, gpointer web_error,struct tab *t)
6630 * XXX this function is wrong
6631 * it overwrites perfectly good urls with garbage on load errors
6632 * those happen often when popups fail to resolve dns
6634 if (t->tmp_uri)
6635 g_free(t->tmp_uri);
6636 t->tmp_uri = g_strdup(uri);
6637 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6638 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6639 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6640 set_status(t, uri, XT_STATUS_NOTHING);
6642 return (FALSE);
6644 #endif
6646 void
6647 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6649 const gchar *title = NULL, *win_title = NULL;
6651 title = get_title(t, FALSE);
6652 win_title = get_title(t, TRUE);
6653 gtk_label_set_text(GTK_LABEL(t->label), title);
6654 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6655 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6656 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6659 void
6660 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6662 run_script(t, JS_HINTING);
6665 void
6666 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6668 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6669 progress == 100 ? 0 : (double)progress / 100);
6670 if (show_url == 0) {
6671 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6672 progress == 100 ? 0 : (double)progress / 100);
6675 update_statusbar_position(NULL, NULL);
6679 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6680 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6681 WebKitWebPolicyDecision *pd, struct tab *t)
6683 char *uri;
6684 WebKitWebNavigationReason reason;
6685 struct domain *d = NULL;
6687 if (t == NULL) {
6688 show_oops(NULL, "webview_npd_cb invalid parameters");
6689 return (FALSE);
6692 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6693 t->ctrl_click,
6694 webkit_network_request_get_uri(request));
6696 uri = (char *)webkit_network_request_get_uri(request);
6698 /* if this is an xtp url, we don't load anything else */
6699 if (parse_xtp_url(t, uri))
6700 return (TRUE);
6702 if (t->ctrl_click) {
6703 t->ctrl_click = 0;
6704 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6705 webkit_web_policy_decision_ignore(pd);
6706 return (TRUE); /* we made the decission */
6710 * This is a little hairy but it comes down to this:
6711 * when we run in whitelist mode we have to assist the browser in
6712 * opening the URL that it would have opened in a new tab.
6714 reason = webkit_web_navigation_action_get_reason(na);
6715 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6716 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6717 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6718 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6719 load_uri(t, uri);
6720 webkit_web_policy_decision_use(pd);
6721 return (TRUE); /* we made the decision */
6724 return (FALSE);
6727 WebKitWebView *
6728 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6730 struct tab *tt;
6731 struct domain *d = NULL;
6732 const gchar *uri;
6733 WebKitWebView *webview = NULL;
6735 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6736 webkit_web_view_get_uri(wv));
6738 if (tabless) {
6739 /* open in current tab */
6740 webview = t->wv;
6741 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6742 uri = webkit_web_view_get_uri(wv);
6743 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6744 return (NULL);
6746 tt = create_new_tab(NULL, NULL, 1, -1);
6747 webview = tt->wv;
6748 } else if (enable_scripts == 1) {
6749 tt = create_new_tab(NULL, NULL, 1, -1);
6750 webview = tt->wv;
6753 return (webview);
6756 gboolean
6757 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6759 const gchar *uri;
6760 struct domain *d = NULL;
6762 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6764 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6765 uri = webkit_web_view_get_uri(wv);
6766 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6767 return (FALSE);
6769 delete_tab(t);
6770 } else if (enable_scripts == 1)
6771 delete_tab(t);
6773 return (TRUE);
6777 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6779 /* we can not eat the event without throwing gtk off so defer it */
6781 /* catch middle click */
6782 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6783 t->ctrl_click = 1;
6784 goto done;
6787 /* catch ctrl click */
6788 if (e->type == GDK_BUTTON_RELEASE &&
6789 CLEAN(e->state) == GDK_CONTROL_MASK)
6790 t->ctrl_click = 1;
6791 else
6792 t->ctrl_click = 0;
6793 done:
6794 return (XT_CB_PASSTHROUGH);
6798 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6800 struct mime_type *m;
6802 m = find_mime_type(mime_type);
6803 if (m == NULL)
6804 return (1);
6805 if (m->mt_download)
6806 return (1);
6808 switch (fork()) {
6809 case -1:
6810 show_oops(t, "can't fork mime handler");
6811 return (1);
6812 /* NOTREACHED */
6813 case 0:
6814 break;
6815 default:
6816 return (0);
6819 /* child */
6820 execlp(m->mt_action, m->mt_action,
6821 webkit_network_request_get_uri(request), (void *)NULL);
6823 _exit(0);
6825 /* NOTREACHED */
6826 return (0);
6829 const gchar *
6830 get_mime_type(char *file)
6832 const char *mime_type;
6833 GFileInfo *fi;
6834 GFile *gf;
6836 if (g_str_has_prefix(file, "file://"))
6837 file += strlen("file://");
6839 gf = g_file_new_for_path(file);
6840 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6841 NULL, NULL);
6842 mime_type = g_file_info_get_content_type(fi);
6843 g_object_unref(fi);
6844 g_object_unref(gf);
6846 return (mime_type);
6850 run_download_mimehandler(char *mime_type, char *file)
6852 struct mime_type *m;
6854 m = find_mime_type(mime_type);
6855 if (m == NULL)
6856 return (1);
6858 switch (fork()) {
6859 case -1:
6860 show_oops(NULL, "can't fork download mime handler");
6861 return (1);
6862 /* NOTREACHED */
6863 case 0:
6864 break;
6865 default:
6866 return (0);
6869 /* child */
6870 if (g_str_has_prefix(file, "file://"))
6871 file += strlen("file://");
6872 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6874 _exit(0);
6876 /* NOTREACHED */
6877 return (0);
6880 void
6881 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6882 WebKitWebView *wv)
6884 WebKitDownloadStatus status;
6885 const gchar *file = NULL, *mime = NULL;
6887 if (download == NULL)
6888 return;
6889 status = webkit_download_get_status(download);
6890 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6891 return;
6893 file = webkit_download_get_destination_uri(download);
6894 if (file == NULL)
6895 return;
6896 mime = get_mime_type((char *)file);
6897 if (mime == NULL)
6898 return;
6900 run_download_mimehandler((char *)mime, (char *)file);
6904 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6905 WebKitNetworkRequest *request, char *mime_type,
6906 WebKitWebPolicyDecision *decision, struct tab *t)
6908 if (t == NULL) {
6909 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6910 return (FALSE);
6913 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6914 t->tab_id, mime_type);
6916 if (run_mimehandler(t, mime_type, request) == 0) {
6917 webkit_web_policy_decision_ignore(decision);
6918 focus_webview(t);
6919 return (TRUE);
6922 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6923 webkit_web_policy_decision_download(decision);
6924 return (TRUE);
6927 return (FALSE);
6931 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6932 struct tab *t)
6934 struct stat sb;
6935 const gchar *suggested_name;
6936 gchar *filename = NULL;
6937 char *uri = NULL;
6938 struct download *download_entry;
6939 int i, ret = TRUE;
6941 if (wk_download == NULL || t == NULL) {
6942 show_oops(NULL, "%s invalid parameters", __func__);
6943 return (FALSE);
6946 suggested_name = webkit_download_get_suggested_filename(wk_download);
6947 if (suggested_name == NULL)
6948 return (FALSE); /* abort download */
6950 i = 0;
6951 do {
6952 if (filename) {
6953 g_free(filename);
6954 filename = NULL;
6956 if (i) {
6957 g_free(uri);
6958 uri = NULL;
6959 filename = g_strdup_printf("%d%s", i, suggested_name);
6961 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6962 filename : suggested_name);
6963 i++;
6964 } while (!stat(uri + strlen("file://"), &sb));
6966 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6967 "local %s\n", __func__, t->tab_id, filename, uri);
6969 webkit_download_set_destination_uri(wk_download, uri);
6971 if (webkit_download_get_status(wk_download) ==
6972 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6973 show_oops(t, "%s: download failed to start", __func__);
6974 ret = FALSE;
6975 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6976 } else {
6977 /* connect "download first" mime handler */
6978 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6979 G_CALLBACK(download_status_changed_cb), NULL);
6981 download_entry = g_malloc(sizeof(struct download));
6982 download_entry->download = wk_download;
6983 download_entry->tab = t;
6984 download_entry->id = next_download_id++;
6985 RB_INSERT(download_list, &downloads, download_entry);
6986 /* get from history */
6987 g_object_ref(wk_download);
6988 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6989 show_oops(t, "Download of '%s' started...",
6990 basename((char *)webkit_download_get_destination_uri(wk_download)));
6993 if (uri)
6994 g_free(uri);
6996 if (filename)
6997 g_free(filename);
6999 /* sync other download manager tabs */
7000 update_download_tabs(NULL);
7003 * NOTE: never redirect/render the current tab before this
7004 * function returns. This will cause the download to never start.
7006 return (ret); /* start download */
7009 void
7010 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7012 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7014 if (t == NULL) {
7015 show_oops(NULL, "webview_hover_cb");
7016 return;
7019 if (uri)
7020 set_status(t, uri, XT_STATUS_LINK);
7021 else {
7022 if (t->status)
7023 set_status(t, t->status, XT_STATUS_NOTHING);
7028 mark(struct tab *t, struct karg *arg)
7030 char mark;
7031 int index;
7033 mark = arg->s[1];
7034 if ((index = marktoindex(mark)) == -1)
7035 return -1;
7037 if (arg->i == XT_MARK_SET)
7038 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7039 else if (arg->i == XT_MARK_GOTO) {
7040 if (t->mark[index] == XT_INVALID_MARK) {
7041 show_oops(t, "mark '%c' does not exist", mark);
7042 return -1;
7044 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7045 something changes the document size */
7046 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7049 return 0;
7052 void
7053 marks_clear(struct tab *t)
7055 int i;
7057 for (i = 0; i < LENGTH(t->mark); i++)
7058 t->mark[i] = XT_INVALID_MARK;
7062 qmarks_load(void)
7064 char file[PATH_MAX];
7065 char *line = NULL, *p, mark;
7066 int index, i;
7067 FILE *f;
7068 size_t linelen;
7070 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7071 if ((f = fopen(file, "r+")) == NULL) {
7072 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7073 return (1);
7076 for (i = 1; ; i++) {
7077 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7078 if (feof(f) || ferror(f))
7079 break;
7080 if (strlen(line) == 0 || line[0] == '#') {
7081 free(line);
7082 line = NULL;
7083 continue;
7086 p = strtok(line, " \t");
7088 if (p == NULL || strlen(p) != 1 ||
7089 (index = marktoindex(*p)) == -1) {
7090 warnx("corrupt quickmarks file, line %d", i);
7091 break;
7094 mark = *p;
7095 p = strtok(NULL, " \t");
7096 if (qmarks[index] != NULL)
7097 g_free(qmarks[index]);
7098 qmarks[index] = g_strdup(p);
7101 fclose(f);
7103 return (0);
7107 qmarks_save(void)
7109 char file[PATH_MAX];
7110 int i;
7111 FILE *f;
7113 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7114 if ((f = fopen(file, "r+")) == NULL) {
7115 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7116 return (1);
7119 for (i = 0; i < XT_NOMARKS; i++)
7120 if (qmarks[i] != NULL)
7121 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7123 fclose(f);
7125 return (0);
7129 qmark(struct tab *t, struct karg *arg)
7131 char mark;
7132 int index;
7134 mark = arg->s[strlen(arg->s)-1];
7135 index = marktoindex(mark);
7136 if (index == -1)
7137 return (-1);
7139 switch (arg->i) {
7140 case XT_QMARK_SET:
7141 if (qmarks[index] != NULL)
7142 g_free(qmarks[index]);
7144 qmarks_load(); /* sync if multiple instances */
7145 qmarks[index] = g_strdup(get_uri(t));
7146 qmarks_save();
7147 break;
7148 case XT_QMARK_OPEN:
7149 if (qmarks[index] != NULL)
7150 load_uri(t, qmarks[index]);
7151 else {
7152 show_oops(t, "quickmark \"%c\" does not exist",
7153 mark);
7154 return (-1);
7156 break;
7157 case XT_QMARK_TAB:
7158 if (qmarks[index] != NULL)
7159 create_new_tab(qmarks[index], NULL, 1, -1);
7160 else {
7161 show_oops(t, "quickmark \"%c\" does not exist",
7162 mark);
7163 return (-1);
7165 break;
7168 return (0);
7172 go_up(struct tab *t, struct karg *args)
7174 int levels;
7175 char *uri;
7176 char *tmp;
7178 levels = atoi(args->s);
7179 if (levels == 0)
7180 levels = 1;
7182 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7183 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7184 return 1;
7185 tmp += strlen(XT_PROTO_DELIM);
7187 /* if an uri starts with a slash, leave it alone (for file:///) */
7188 if (tmp[0] == '/')
7189 tmp++;
7191 while (levels--) {
7192 char *p;
7194 p = strrchr(tmp, '/');
7195 if (p != NULL)
7196 *p = '\0';
7197 else
7198 break;
7201 load_uri(t, uri);
7202 g_free(uri);
7204 return (0);
7208 gototab(struct tab *t, struct karg *args)
7210 int tab;
7211 struct karg arg = {0, NULL, -1};
7213 tab = atoi(args->s);
7215 arg.i = XT_TAB_NEXT;
7216 arg.precount = tab;
7218 movetab(t, &arg);
7220 return (0);
7224 zoom_amount(struct tab *t, struct karg *arg)
7226 struct karg narg = {0, NULL, -1};
7228 narg.i = atoi(arg->s);
7229 resizetab(t, &narg);
7231 return 0;
7235 flip_colon(struct tab *t, struct karg *arg)
7237 struct karg narg = {0, NULL, -1};
7238 char *p;
7240 if (t == NULL || arg == NULL)
7241 return (1);
7243 p = strstr(arg->s, ":");
7244 if (p == NULL)
7245 return (1);
7246 *p = '\0';
7248 narg.i = ':';
7249 narg.s = arg->s;
7250 command(t, &narg);
7252 return (0);
7255 /* buffer commands receive the regex that triggered them in arg.s */
7256 char bcmd[XT_BUFCMD_SZ];
7257 struct buffercmd {
7258 char *regex;
7259 int precount;
7260 #define XT_PRE_NO (0)
7261 #define XT_PRE_YES (1)
7262 #define XT_PRE_MAYBE (2)
7263 char *cmd;
7264 int (*func)(struct tab *, struct karg *);
7265 int arg;
7266 regex_t cregex;
7267 } buffercmds[] = {
7268 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7269 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7270 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7271 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7272 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7273 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7274 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7275 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7276 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7277 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7278 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7279 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7280 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7281 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7282 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7283 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7284 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7285 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7288 void
7289 buffercmd_init(void)
7291 int i;
7293 for (i = 0; i < LENGTH(buffercmds); i++)
7294 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7295 REG_EXTENDED | REG_NOSUB))
7296 startpage_add("invalid buffercmd regex %s",
7297 buffercmds[i].regex);
7300 void
7301 buffercmd_abort(struct tab *t)
7303 int i;
7305 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7306 for (i = 0; i < LENGTH(bcmd); i++)
7307 bcmd[i] = '\0';
7309 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7310 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7313 void
7314 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7316 struct karg arg = {0, NULL, -1};
7318 arg.i = cmd->arg;
7319 arg.s = g_strdup(bcmd);
7321 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7322 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7323 cmd->func(t, &arg);
7325 if (arg.s)
7326 g_free(arg.s);
7328 buffercmd_abort(t);
7331 gboolean
7332 buffercmd_addkey(struct tab *t, guint keyval)
7334 int i, c, match ;
7335 char s[XT_BUFCMD_SZ];
7337 if (keyval == GDK_Escape) {
7338 buffercmd_abort(t);
7339 return (XT_CB_HANDLED);
7342 /* key with modifier or non-ascii character */
7343 if (!isascii(keyval))
7344 return (XT_CB_PASSTHROUGH);
7346 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7347 "to buffer \"%s\"\n", keyval, bcmd);
7349 for (i = 0; i < LENGTH(bcmd); i++)
7350 if (bcmd[i] == '\0') {
7351 bcmd[i] = keyval;
7352 break;
7355 /* buffer full, ignore input */
7356 if (i >= LENGTH(bcmd) -1) {
7357 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7358 buffercmd_abort(t);
7359 return (XT_CB_HANDLED);
7362 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7364 /* find exact match */
7365 for (i = 0; i < LENGTH(buffercmds); i++)
7366 if (regexec(&buffercmds[i].cregex, bcmd,
7367 (size_t) 0, NULL, 0) == 0) {
7368 buffercmd_execute(t, &buffercmds[i]);
7369 goto done;
7372 /* find non exact matches to see if we need to abort ot not */
7373 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7374 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7375 c = -1;
7376 s[0] = '\0';
7377 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7378 if (isdigit(bcmd[0])) {
7379 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7380 continue;
7381 } else {
7382 c = 0;
7383 if (sscanf(bcmd, "%s", s) == 0)
7384 continue;
7386 } else if (buffercmds[i].precount == XT_PRE_YES) {
7387 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7388 continue;
7389 } else {
7390 if (sscanf(bcmd, "%s", s) == 0)
7391 continue;
7393 if (c == -1 && buffercmds[i].precount)
7394 continue;
7395 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7396 match++;
7398 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7399 i, match, buffercmds[i].cmd, c, s);
7401 if (match == 0) {
7402 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7403 buffercmd_abort(t);
7406 done:
7407 return (XT_CB_HANDLED);
7410 gboolean
7411 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7413 struct key_binding *k;
7415 /* handle keybindings if buffercmd is empty.
7416 if not empty, allow commands like C-n */
7417 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7418 TAILQ_FOREACH(k, &kbl, entry)
7419 if (e->keyval == k->key
7420 && (entry ? k->use_in_entry : 1)) {
7421 if (k->mask == 0) {
7422 if ((e->state & (CTRL | MOD1)) == 0)
7423 return (cmd_execute(t, k->cmd));
7424 } else if ((e->state & k->mask) == k->mask) {
7425 return (cmd_execute(t, k->cmd));
7429 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7430 return buffercmd_addkey(t, e->keyval);
7432 return (XT_CB_PASSTHROUGH);
7436 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7438 char s[2], buf[128];
7439 const char *errstr = NULL;
7441 /* don't use w directly; use t->whatever instead */
7443 if (t == NULL) {
7444 show_oops(NULL, "wv_keypress_after_cb");
7445 return (XT_CB_PASSTHROUGH);
7448 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7449 e->keyval, e->state, t);
7451 if (t->hints_on) {
7452 /* ESC */
7453 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7454 disable_hints(t);
7455 return (XT_CB_HANDLED);
7458 /* RETURN */
7459 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7460 if (errstr) {
7461 /* we have a string */
7462 } else {
7463 /* we have a number */
7464 snprintf(buf, sizeof buf,
7465 "vimprobable_fire(%s)", t->hint_num);
7466 run_script(t, buf);
7468 disable_hints(t);
7471 /* BACKSPACE */
7472 /* XXX unfuck this */
7473 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7474 if (t->hint_mode == XT_HINT_NUMERICAL) {
7475 /* last input was numerical */
7476 int l;
7477 l = strlen(t->hint_num);
7478 if (l > 0) {
7479 l--;
7480 if (l == 0) {
7481 disable_hints(t);
7482 enable_hints(t);
7483 } else {
7484 t->hint_num[l] = '\0';
7485 goto num;
7488 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7489 /* last input was alphanumerical */
7490 int l;
7491 l = strlen(t->hint_buf);
7492 if (l > 0) {
7493 l--;
7494 if (l == 0) {
7495 disable_hints(t);
7496 enable_hints(t);
7497 } else {
7498 t->hint_buf[l] = '\0';
7499 goto anum;
7502 } else {
7503 /* bogus */
7504 disable_hints(t);
7508 /* numerical input */
7509 if (CLEAN(e->state) == 0 &&
7510 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7511 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7512 snprintf(s, sizeof s, "%c", e->keyval);
7513 strlcat(t->hint_num, s, sizeof t->hint_num);
7514 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7515 t->hint_num);
7516 num:
7517 if (errstr) {
7518 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7519 "invalid link number\n");
7520 disable_hints(t);
7521 } else {
7522 snprintf(buf, sizeof buf,
7523 "vimprobable_update_hints(%s)",
7524 t->hint_num);
7525 t->hint_mode = XT_HINT_NUMERICAL;
7526 run_script(t, buf);
7529 /* empty the counter buffer */
7530 bzero(t->hint_buf, sizeof t->hint_buf);
7531 return (XT_CB_HANDLED);
7534 /* alphanumerical input */
7535 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7536 e->keyval <= GDK_z) ||
7537 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7538 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7539 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7540 e->keyval <= GDK_9) ||
7541 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7542 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7543 snprintf(s, sizeof s, "%c", e->keyval);
7544 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7545 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7546 " %s\n", t->hint_buf);
7547 anum:
7548 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7549 run_script(t, buf);
7551 snprintf(buf, sizeof buf,
7552 "vimprobable_show_hints('%s')", t->hint_buf);
7553 t->hint_mode = XT_HINT_ALPHANUM;
7554 run_script(t, buf);
7556 /* empty the counter buffer */
7557 bzero(t->hint_num, sizeof t->hint_num);
7558 return (XT_CB_HANDLED);
7561 return (XT_CB_HANDLED);
7562 } else {
7563 /* prefix input*/
7564 snprintf(s, sizeof s, "%c", e->keyval);
7565 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7566 cmd_prefix = 10 * cmd_prefix + atoi(s);
7569 return (handle_keypress(t, e, 0));
7573 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7575 hide_oops(t);
7577 /* Hide buffers, if they are visible, with escape. */
7578 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7579 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7580 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7581 hide_buffers(t);
7582 return (XT_CB_HANDLED);
7585 return (XT_CB_PASSTHROUGH);
7588 gboolean
7589 search_continue(struct tab *t)
7591 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7592 gboolean rv = FALSE;
7594 if (c[0] == ':')
7595 goto done;
7596 if (strlen(c) == 1) {
7597 webkit_web_view_unmark_text_matches(t->wv);
7598 goto done;
7601 if (c[0] == '/')
7602 t->search_forward = TRUE;
7603 else if (c[0] == '?')
7604 t->search_forward = FALSE;
7605 else
7606 goto done;
7608 rv = TRUE;
7609 done:
7610 return (rv);
7613 gboolean
7614 search_cb(struct tab *t)
7616 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7617 GdkColor color;
7619 if (search_continue(t) == FALSE)
7620 goto done;
7622 /* search */
7623 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7624 TRUE) == FALSE) {
7625 /* not found, mark red */
7626 gdk_color_parse(XT_COLOR_RED, &color);
7627 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7628 /* unmark and remove selection */
7629 webkit_web_view_unmark_text_matches(t->wv);
7630 /* my kingdom for a way to unselect text in webview */
7631 } else {
7632 /* found, highlight all */
7633 webkit_web_view_unmark_text_matches(t->wv);
7634 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7635 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7636 gdk_color_parse(XT_COLOR_WHITE, &color);
7637 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7639 done:
7640 t->search_id = 0;
7641 return (FALSE);
7645 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7647 const gchar *c = gtk_entry_get_text(w);
7649 if (t == NULL) {
7650 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7651 return (XT_CB_PASSTHROUGH);
7654 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7655 e->keyval, e->state, t);
7657 if (search_continue(t) == FALSE)
7658 goto done;
7660 /* if search length is > 4 then no longer play timeout games */
7661 if (strlen(c) > 4) {
7662 if (t->search_id) {
7663 g_source_remove(t->search_id);
7664 t->search_id = 0;
7666 search_cb(t);
7667 goto done;
7670 /* reestablish a new timer if the user types fast */
7671 if (t->search_id)
7672 g_source_remove(t->search_id);
7673 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7675 done:
7676 return (XT_CB_PASSTHROUGH);
7679 gboolean
7680 match_uri(const gchar *uri, const gchar *key) {
7681 gchar *voffset;
7682 size_t len;
7683 gboolean match = FALSE;
7685 len = strlen(key);
7687 if (!strncmp(key, uri, len))
7688 match = TRUE;
7689 else {
7690 voffset = strstr(uri, "/") + 2;
7691 if (!strncmp(key, voffset, len))
7692 match = TRUE;
7693 else if (g_str_has_prefix(voffset, "www.")) {
7694 voffset = voffset + strlen("www.");
7695 if (!strncmp(key, voffset, len))
7696 match = TRUE;
7700 return (match);
7703 void
7704 cmd_getlist(int id, char *key)
7706 int i, dep, c = 0;
7707 struct history *h;
7709 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7710 RB_FOREACH_REVERSE(h, history_list, &hl)
7711 if (match_uri(h->uri, key)) {
7712 cmd_status.list[c] = (char *)h->uri;
7713 if (++c > 255)
7714 break;
7717 cmd_status.len = c;
7718 return;
7721 dep = (id == -1) ? 0 : cmds[id].level + 1;
7723 for (i = id + 1; i < LENGTH(cmds); i++) {
7724 if (cmds[i].level < dep)
7725 break;
7726 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7727 strlen(key)))
7728 cmd_status.list[c++] = cmds[i].cmd;
7732 cmd_status.len = c;
7735 char *
7736 cmd_getnext(int dir)
7738 cmd_status.index += dir;
7740 if (cmd_status.index < 0)
7741 cmd_status.index = cmd_status.len - 1;
7742 else if (cmd_status.index >= cmd_status.len)
7743 cmd_status.index = 0;
7745 return cmd_status.list[cmd_status.index];
7749 cmd_tokenize(char *s, char *tokens[])
7751 int i = 0;
7752 char *tok, *last;
7753 size_t len = strlen(s);
7754 bool blank;
7756 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7757 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7758 tok = strtok_r(NULL, " ", &last), i++)
7759 tokens[i] = tok;
7761 if (blank && i < 3)
7762 tokens[i++] = "";
7764 return (i);
7767 void
7768 cmd_complete(struct tab *t, char *str, int dir)
7770 GtkEntry *w = GTK_ENTRY(t->cmd);
7771 int i, j, levels, c = 0, dep = 0, parent = -1;
7772 int matchcount = 0;
7773 char *tok, *match, *s = g_strdup(str);
7774 char *tokens[3];
7775 char res[XT_MAX_URL_LENGTH + 32] = ":";
7776 char *sc = s;
7778 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7780 /* copy prefix*/
7781 for (i = 0; isdigit(s[i]); i++)
7782 res[i + 1] = s[i];
7784 for (; isspace(s[i]); i++)
7785 res[i + 1] = s[i];
7787 s += i;
7789 levels = cmd_tokenize(s, tokens);
7791 for (i = 0; i < levels - 1; i++) {
7792 tok = tokens[i];
7793 matchcount = 0;
7794 for (j = c; j < LENGTH(cmds); j++) {
7795 if (cmds[j].level < dep)
7796 break;
7797 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7798 strlen(tok))) {
7799 matchcount++;
7800 c = j + 1;
7801 if (strlen(tok) == strlen(cmds[j].cmd)) {
7802 matchcount = 1;
7803 break;
7808 if (matchcount == 1) {
7809 strlcat(res, tok, sizeof res);
7810 strlcat(res, " ", sizeof res);
7811 dep++;
7812 } else {
7813 g_free(sc);
7814 return;
7817 parent = c - 1;
7820 if (cmd_status.index == -1)
7821 cmd_getlist(parent, tokens[i]);
7823 if (cmd_status.len > 0) {
7824 match = cmd_getnext(dir);
7825 strlcat(res, match, sizeof res);
7826 gtk_entry_set_text(w, res);
7827 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7830 g_free(sc);
7833 gboolean
7834 cmd_execute(struct tab *t, char *str)
7836 struct cmd *cmd = NULL;
7837 char *tok, *last, *s = g_strdup(str), *sc;
7838 char prefixstr[4];
7839 int j, len, c = 0, dep = 0, matchcount = 0;
7840 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7841 struct karg arg = {0, NULL, -1};
7843 sc = s;
7845 /* copy prefix*/
7846 for (j = 0; j<3 && isdigit(s[j]); j++)
7847 prefixstr[j]=s[j];
7849 prefixstr[j]='\0';
7851 s += j;
7852 while (isspace(s[0]))
7853 s++;
7855 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7856 prefix = atoi(prefixstr);
7857 else
7858 s = sc;
7860 for (tok = strtok_r(s, " ", &last); tok;
7861 tok = strtok_r(NULL, " ", &last)) {
7862 matchcount = 0;
7863 for (j = c; j < LENGTH(cmds); j++) {
7864 if (cmds[j].level < dep)
7865 break;
7866 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7867 strlen(tok);
7868 if (cmds[j].level == dep &&
7869 !strncmp(tok, cmds[j].cmd, len)) {
7870 matchcount++;
7871 c = j + 1;
7872 cmd = &cmds[j];
7873 if (len == strlen(cmds[j].cmd)) {
7874 matchcount = 1;
7875 break;
7879 if (matchcount == 1) {
7880 if (cmd->type > 0)
7881 goto execute_cmd;
7882 dep++;
7883 } else {
7884 show_oops(t, "Invalid command: %s", str);
7885 goto done;
7888 execute_cmd:
7889 arg.i = cmd->arg;
7891 if (prefix != -1)
7892 arg.precount = prefix;
7893 else if (cmd_prefix > 0)
7894 arg.precount = cmd_prefix;
7896 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7897 show_oops(t, "No prefix allowed: %s", str);
7898 goto done;
7900 if (cmd->type > 1)
7901 arg.s = last ? g_strdup(last) : g_strdup("");
7902 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7903 arg.precount = atoi(arg.s);
7904 if (arg.precount <= 0) {
7905 if (arg.s[0] == '0')
7906 show_oops(t, "Zero count");
7907 else
7908 show_oops(t, "Trailing characters");
7909 goto done;
7913 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7914 __func__, arg.precount, arg.s);
7916 cmd->func(t, &arg);
7918 rv = XT_CB_HANDLED;
7919 done:
7920 if (j > 0)
7921 cmd_prefix = 0;
7922 g_free(sc);
7923 if (arg.s)
7924 g_free(arg.s);
7926 return (rv);
7930 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7932 if (t == NULL) {
7933 show_oops(NULL, "entry_key_cb invalid parameters");
7934 return (XT_CB_PASSTHROUGH);
7937 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7938 e->keyval, e->state, t);
7940 hide_oops(t);
7942 if (e->keyval == GDK_Escape) {
7943 /* don't use focus_webview(t) because we want to type :cmds */
7944 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7947 return (handle_keypress(t, e, 1));
7951 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7953 int rv = XT_CB_HANDLED;
7954 const gchar *c = gtk_entry_get_text(w);
7956 if (t == NULL) {
7957 show_oops(NULL, "cmd_keypress_cb parameters");
7958 return (XT_CB_PASSTHROUGH);
7961 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7962 e->keyval, e->state, t);
7964 /* sanity */
7965 if (c == NULL)
7966 e->keyval = GDK_Escape;
7967 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7968 e->keyval = GDK_Escape;
7970 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7971 e->keyval != GDK_ISO_Left_Tab)
7972 cmd_status.index = -1;
7974 switch (e->keyval) {
7975 case GDK_Tab:
7976 if (c[0] == ':')
7977 cmd_complete(t, (char *)&c[1], 1);
7978 goto done;
7979 case GDK_ISO_Left_Tab:
7980 if (c[0] == ':')
7981 cmd_complete(t, (char *)&c[1], -1);
7983 goto done;
7984 case GDK_Down:
7985 if (c[0] != ':')
7986 goto done;
7988 if (history_at == NULL)
7989 history_at = TAILQ_LAST(&chl, command_list);
7990 else {
7991 history_at = TAILQ_PREV(history_at, command_list,
7992 entry);
7993 if (history_at == NULL)
7994 history_at = TAILQ_LAST(&chl, command_list);
7997 if (history_at) {
7998 gtk_entry_set_text(w, history_at->line);
7999 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8001 goto done;
8002 case GDK_Up:
8003 if (c[0] != ':')
8004 goto done;
8006 if (history_at == NULL)
8007 history_at = TAILQ_FIRST(&chl);
8008 else {
8009 history_at = TAILQ_NEXT(history_at, entry);
8010 if (history_at == NULL)
8011 history_at = TAILQ_FIRST(&chl);
8014 if (history_at) {
8015 gtk_entry_set_text(w, history_at->line);
8016 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8018 goto done;
8019 case GDK_BackSpace:
8020 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8021 break;
8022 /* FALLTHROUGH */
8023 case GDK_Escape:
8024 hide_cmd(t);
8025 focus_webview(t);
8027 /* cancel search */
8028 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8029 webkit_web_view_unmark_text_matches(t->wv);
8030 goto done;
8033 rv = XT_CB_PASSTHROUGH;
8034 done:
8035 return (rv);
8038 void
8039 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8041 /* popup menu enabled */
8042 t->popup = 1;
8046 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8048 if (t == NULL) {
8049 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8050 return (XT_CB_PASSTHROUGH);
8053 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8054 t->tab_id, t->popup);
8056 /* if popup is enabled don't lose focus */
8057 if (t->popup) {
8058 t->popup = 0;
8059 return (XT_CB_PASSTHROUGH);
8062 hide_cmd(t);
8063 hide_oops(t);
8065 if (show_url == 0 || t->focus_wv)
8066 focus_webview(t);
8067 else
8068 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8070 return (XT_CB_PASSTHROUGH);
8073 void
8074 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8076 char *s;
8077 const gchar *c = gtk_entry_get_text(entry);
8079 if (t == NULL) {
8080 show_oops(NULL, "cmd_activate_cb invalid parameters");
8081 return;
8084 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8086 hide_cmd(t);
8088 /* sanity */
8089 if (c == NULL)
8090 goto done;
8091 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8092 goto done;
8093 if (strlen(c) < 2)
8094 goto done;
8095 s = (char *)&c[1];
8097 if (c[0] == '/' || c[0] == '?') {
8098 /* see if there is a timer pending */
8099 if (t->search_id) {
8100 g_source_remove(t->search_id);
8101 t->search_id = 0;
8102 search_cb(t);
8105 if (t->search_text) {
8106 g_free(t->search_text);
8107 t->search_text = NULL;
8110 t->search_text = g_strdup(s);
8111 if (global_search)
8112 g_free(global_search);
8113 global_search = g_strdup(s);
8114 t->search_forward = c[0] == '/';
8116 goto done;
8119 cmd_execute(t, s);
8121 cmd_history_add(s);
8122 done:
8123 return;
8126 void
8127 backward_cb(GtkWidget *w, struct tab *t)
8129 struct karg a;
8131 if (t == NULL) {
8132 show_oops(NULL, "backward_cb invalid parameters");
8133 return;
8136 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8138 a.i = XT_NAV_BACK;
8139 navaction(t, &a);
8142 void
8143 forward_cb(GtkWidget *w, struct tab *t)
8145 struct karg a;
8147 if (t == NULL) {
8148 show_oops(NULL, "forward_cb invalid parameters");
8149 return;
8152 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8154 a.i = XT_NAV_FORWARD;
8155 navaction(t, &a);
8158 void
8159 home_cb(GtkWidget *w, struct tab *t)
8161 if (t == NULL) {
8162 show_oops(NULL, "home_cb invalid parameters");
8163 return;
8166 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8168 load_uri(t, home);
8171 void
8172 stop_cb(GtkWidget *w, struct tab *t)
8174 WebKitWebFrame *frame;
8176 if (t == NULL) {
8177 show_oops(NULL, "stop_cb invalid parameters");
8178 return;
8181 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8183 frame = webkit_web_view_get_main_frame(t->wv);
8184 if (frame == NULL) {
8185 show_oops(t, "stop_cb: no frame");
8186 return;
8189 webkit_web_frame_stop_loading(frame);
8190 abort_favicon_download(t);
8193 void
8194 setup_webkit(struct tab *t)
8196 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8197 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8198 FALSE, (char *)NULL);
8199 else
8200 warnx("webkit does not have \"enable-dns-prefetching\" property");
8201 g_object_set(G_OBJECT(t->settings),
8202 "user-agent", t->user_agent, (char *)NULL);
8203 g_object_set(G_OBJECT(t->settings),
8204 "enable-scripts", enable_scripts, (char *)NULL);
8205 g_object_set(G_OBJECT(t->settings),
8206 "enable-plugins", enable_plugins, (char *)NULL);
8207 g_object_set(G_OBJECT(t->settings),
8208 "javascript-can-open-windows-automatically", enable_scripts,
8209 (char *)NULL);
8210 g_object_set(G_OBJECT(t->settings),
8211 "enable-html5-database", FALSE, (char *)NULL);
8212 g_object_set(G_OBJECT(t->settings),
8213 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8214 g_object_set(G_OBJECT(t->settings),
8215 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8216 g_object_set(G_OBJECT(t->settings),
8217 "spell_checking_languages", spell_check_languages, (char *)NULL);
8218 g_object_set(G_OBJECT(t->wv),
8219 "full-content-zoom", TRUE, (char *)NULL);
8221 webkit_web_view_set_settings(t->wv, t->settings);
8224 gboolean
8225 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8227 struct tab *ti, *t = NULL;
8228 gdouble view_size, value, max;
8229 gchar *position;
8231 TAILQ_FOREACH(ti, &tabs, entry)
8232 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8233 t = ti;
8234 break;
8237 if (t == NULL)
8238 return FALSE;
8240 if (adjustment == NULL)
8241 adjustment = gtk_scrolled_window_get_vadjustment(
8242 GTK_SCROLLED_WINDOW(t->browser_win));
8244 view_size = gtk_adjustment_get_page_size(adjustment);
8245 value = gtk_adjustment_get_value(adjustment);
8246 max = gtk_adjustment_get_upper(adjustment) - view_size;
8248 if (max == 0)
8249 position = g_strdup("All");
8250 else if (value == max)
8251 position = g_strdup("Bot");
8252 else if (value == 0)
8253 position = g_strdup("Top");
8254 else
8255 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8257 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8258 g_free(position);
8260 return (TRUE);
8263 GtkWidget *
8264 create_browser(struct tab *t)
8266 GtkWidget *w;
8267 gchar *strval;
8268 GtkAdjustment *adjustment;
8270 if (t == NULL) {
8271 show_oops(NULL, "create_browser invalid parameters");
8272 return (NULL);
8275 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8276 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8277 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8278 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8280 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8281 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8282 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8284 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8285 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8287 /* set defaults */
8288 t->settings = webkit_web_settings_new();
8290 if (user_agent == NULL) {
8291 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8292 (char *)NULL);
8293 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8294 g_free(strval);
8295 } else
8296 t->user_agent = g_strdup(user_agent);
8298 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8300 adjustment =
8301 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8302 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8303 G_CALLBACK(update_statusbar_position), NULL);
8305 setup_webkit(t);
8307 return (w);
8310 GtkWidget *
8311 create_window(void)
8313 GtkWidget *w;
8315 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8316 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8317 gtk_widget_set_name(w, "xxxterm");
8318 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8319 g_signal_connect(G_OBJECT(w), "delete_event",
8320 G_CALLBACK (gtk_main_quit), NULL);
8322 return (w);
8325 GtkWidget *
8326 create_kiosk_toolbar(struct tab *t)
8328 GtkWidget *toolbar = NULL, *b;
8330 b = gtk_hbox_new(FALSE, 0);
8331 toolbar = b;
8332 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8334 /* backward button */
8335 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8336 gtk_widget_set_sensitive(t->backward, FALSE);
8337 g_signal_connect(G_OBJECT(t->backward), "clicked",
8338 G_CALLBACK(backward_cb), t);
8339 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8341 /* forward button */
8342 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8343 gtk_widget_set_sensitive(t->forward, FALSE);
8344 g_signal_connect(G_OBJECT(t->forward), "clicked",
8345 G_CALLBACK(forward_cb), t);
8346 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8348 /* home button */
8349 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8350 gtk_widget_set_sensitive(t->gohome, true);
8351 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8352 G_CALLBACK(home_cb), t);
8353 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8355 /* create widgets but don't use them */
8356 t->uri_entry = gtk_entry_new();
8357 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8358 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8359 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8361 return (toolbar);
8364 GtkWidget *
8365 create_toolbar(struct tab *t)
8367 GtkWidget *toolbar = NULL, *b, *eb1;
8369 b = gtk_hbox_new(FALSE, 0);
8370 toolbar = b;
8371 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8373 /* backward button */
8374 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8375 gtk_widget_set_sensitive(t->backward, FALSE);
8376 g_signal_connect(G_OBJECT(t->backward), "clicked",
8377 G_CALLBACK(backward_cb), t);
8378 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8380 /* forward button */
8381 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8382 gtk_widget_set_sensitive(t->forward, FALSE);
8383 g_signal_connect(G_OBJECT(t->forward), "clicked",
8384 G_CALLBACK(forward_cb), t);
8385 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8386 FALSE, 0);
8388 /* stop button */
8389 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8390 gtk_widget_set_sensitive(t->stop, FALSE);
8391 g_signal_connect(G_OBJECT(t->stop), "clicked",
8392 G_CALLBACK(stop_cb), t);
8393 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8394 FALSE, 0);
8396 /* JS button */
8397 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8398 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8399 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8400 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8401 G_CALLBACK(js_toggle_cb), t);
8402 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8404 t->uri_entry = gtk_entry_new();
8405 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8406 G_CALLBACK(activate_uri_entry_cb), t);
8407 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8408 G_CALLBACK(entry_key_cb), t);
8409 completion_add(t);
8410 eb1 = gtk_hbox_new(FALSE, 0);
8411 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8412 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8413 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8415 /* search entry */
8416 if (search_string) {
8417 GtkWidget *eb2;
8418 t->search_entry = gtk_entry_new();
8419 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8420 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8421 G_CALLBACK(activate_search_entry_cb), t);
8422 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8423 G_CALLBACK(entry_key_cb), t);
8424 gtk_widget_set_size_request(t->search_entry, -1, -1);
8425 eb2 = gtk_hbox_new(FALSE, 0);
8426 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8427 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8429 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8432 return (toolbar);
8435 GtkWidget *
8436 create_buffers(struct tab *t)
8438 GtkCellRenderer *renderer;
8439 GtkWidget *view;
8441 view = gtk_tree_view_new();
8443 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8445 renderer = gtk_cell_renderer_text_new();
8446 gtk_tree_view_insert_column_with_attributes
8447 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8449 renderer = gtk_cell_renderer_text_new();
8450 gtk_tree_view_insert_column_with_attributes
8451 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8452 NULL);
8454 gtk_tree_view_set_model
8455 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8457 return view;
8460 void
8461 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8462 GtkTreeViewColumn *col, struct tab *t)
8464 GtkTreeIter iter;
8465 guint id;
8467 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8469 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8470 path)) {
8471 gtk_tree_model_get
8472 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8473 set_current_tab(id - 1);
8476 hide_buffers(t);
8479 /* after tab reordering/creation/removal */
8480 void
8481 recalc_tabs(void)
8483 struct tab *t;
8484 int maxid = 0;
8486 TAILQ_FOREACH(t, &tabs, entry) {
8487 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8488 if (t->tab_id > maxid)
8489 maxid = t->tab_id;
8491 gtk_widget_show(t->tab_elems.sep);
8494 TAILQ_FOREACH(t, &tabs, entry) {
8495 if (t->tab_id == maxid) {
8496 gtk_widget_hide(t->tab_elems.sep);
8497 break;
8502 /* after active tab change */
8503 void
8504 recolor_compact_tabs(void)
8506 struct tab *t;
8507 int curid = 0;
8508 GdkColor color;
8510 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8511 TAILQ_FOREACH(t, &tabs, entry)
8512 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8513 &color);
8515 curid = gtk_notebook_get_current_page(notebook);
8516 TAILQ_FOREACH(t, &tabs, entry)
8517 if (t->tab_id == curid) {
8518 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8519 gtk_widget_modify_fg(t->tab_elems.label,
8520 GTK_STATE_NORMAL, &color);
8521 break;
8525 void
8526 set_current_tab(int page_num)
8528 buffercmd_abort(get_current_tab());
8529 gtk_notebook_set_current_page(notebook, page_num);
8530 recolor_compact_tabs();
8534 undo_close_tab_save(struct tab *t)
8536 int m, n;
8537 const gchar *uri;
8538 struct undo *u1, *u2;
8539 GList *items;
8540 WebKitWebHistoryItem *item;
8542 if ((uri = get_uri(t)) == NULL)
8543 return (1);
8545 u1 = g_malloc0(sizeof(struct undo));
8546 u1->uri = g_strdup(uri);
8548 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8550 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8551 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8552 u1->back = n;
8554 /* forward history */
8555 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8557 while (items) {
8558 item = items->data;
8559 u1->history = g_list_prepend(u1->history,
8560 webkit_web_history_item_copy(item));
8561 items = g_list_next(items);
8564 /* current item */
8565 if (m) {
8566 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8567 u1->history = g_list_prepend(u1->history,
8568 webkit_web_history_item_copy(item));
8571 /* back history */
8572 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8574 while (items) {
8575 item = items->data;
8576 u1->history = g_list_prepend(u1->history,
8577 webkit_web_history_item_copy(item));
8578 items = g_list_next(items);
8581 TAILQ_INSERT_HEAD(&undos, u1, entry);
8583 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8584 u2 = TAILQ_LAST(&undos, undo_tailq);
8585 TAILQ_REMOVE(&undos, u2, entry);
8586 g_free(u2->uri);
8587 g_list_free(u2->history);
8588 g_free(u2);
8589 } else
8590 undo_count++;
8592 return (0);
8595 void
8596 delete_tab(struct tab *t)
8598 struct karg a;
8600 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8602 if (t == NULL)
8603 return;
8605 buffercmd_abort(t);
8606 TAILQ_REMOVE(&tabs, t, entry);
8608 /* Halt all webkit activity. */
8609 abort_favicon_download(t);
8610 webkit_web_view_stop_loading(t->wv);
8612 /* Save the tab, so we can undo the close. */
8613 undo_close_tab_save(t);
8615 if (browser_mode == XT_BM_KIOSK) {
8616 gtk_widget_destroy(t->uri_entry);
8617 gtk_widget_destroy(t->stop);
8618 gtk_widget_destroy(t->js_toggle);
8621 gtk_widget_destroy(t->tab_elems.eventbox);
8622 gtk_widget_destroy(t->vbox);
8624 /* just in case */
8625 if (t->search_id)
8626 g_source_remove(t->search_id);
8628 g_free(t->user_agent);
8629 g_free(t->stylesheet);
8630 g_free(t->tmp_uri);
8631 g_free(t);
8633 if (TAILQ_EMPTY(&tabs)) {
8634 if (browser_mode == XT_BM_KIOSK)
8635 create_new_tab(home, NULL, 1, -1);
8636 else
8637 create_new_tab(NULL, NULL, 1, -1);
8640 /* recreate session */
8641 if (session_autosave) {
8642 a.s = NULL;
8643 save_tabs(t, &a);
8646 recalc_tabs();
8647 recolor_compact_tabs();
8650 void
8651 update_statusbar_zoom(struct tab *t)
8653 gfloat zoom;
8654 char s[16] = { '\0' };
8656 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8657 if ((zoom <= 0.99 || zoom >= 1.01))
8658 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8659 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8662 void
8663 setzoom_webkit(struct tab *t, int adjust)
8665 #define XT_ZOOMPERCENT 0.04
8667 gfloat zoom;
8669 if (t == NULL) {
8670 show_oops(NULL, "setzoom_webkit invalid parameters");
8671 return;
8674 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8675 if (adjust == XT_ZOOM_IN)
8676 zoom += XT_ZOOMPERCENT;
8677 else if (adjust == XT_ZOOM_OUT)
8678 zoom -= XT_ZOOMPERCENT;
8679 else if (adjust > 0)
8680 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8681 else {
8682 show_oops(t, "setzoom_webkit invalid zoom value");
8683 return;
8686 if (zoom < XT_ZOOMPERCENT)
8687 zoom = XT_ZOOMPERCENT;
8688 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8689 update_statusbar_zoom(t);
8692 gboolean
8693 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8695 struct tab *t = (struct tab *) data;
8697 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8699 switch (event->button) {
8700 case 1:
8701 set_current_tab(t->tab_id);
8702 break;
8703 case 2:
8704 delete_tab(t);
8705 break;
8708 return TRUE;
8711 void
8712 append_tab(struct tab *t)
8714 if (t == NULL)
8715 return;
8717 TAILQ_INSERT_TAIL(&tabs, t, entry);
8718 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8721 GtkWidget *
8722 create_sbe(int width)
8724 GtkWidget *sbe;
8726 sbe = gtk_entry_new();
8727 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8728 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8729 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8730 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8731 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8732 gtk_widget_set_size_request(sbe, width, -1);
8734 return sbe;
8737 struct tab *
8738 create_new_tab(char *title, struct undo *u, int focus, int position)
8740 struct tab *t;
8741 int load = 1, id;
8742 GtkWidget *b, *bb;
8743 WebKitWebHistoryItem *item;
8744 GList *items;
8745 GdkColor color;
8746 char *p;
8747 int sbe_p = 0, sbe_b = 0,
8748 sbe_z = 0;
8750 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8752 if (tabless && !TAILQ_EMPTY(&tabs)) {
8753 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8754 return (NULL);
8757 t = g_malloc0(sizeof *t);
8759 if (title == NULL) {
8760 title = "(untitled)";
8761 load = 0;
8764 t->vbox = gtk_vbox_new(FALSE, 0);
8766 /* label + button for tab */
8767 b = gtk_hbox_new(FALSE, 0);
8768 t->tab_content = b;
8770 #if GTK_CHECK_VERSION(2, 20, 0)
8771 t->spinner = gtk_spinner_new();
8772 #endif
8773 t->label = gtk_label_new(title);
8774 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8775 gtk_widget_set_size_request(t->label, 100, 0);
8776 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8777 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8778 gtk_widget_set_size_request(b, 130, 0);
8780 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8781 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8782 #if GTK_CHECK_VERSION(2, 20, 0)
8783 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8784 #endif
8786 /* toolbar */
8787 if (browser_mode == XT_BM_KIOSK) {
8788 t->toolbar = create_kiosk_toolbar(t);
8789 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8791 } else {
8792 t->toolbar = create_toolbar(t);
8793 if (fancy_bar)
8794 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8795 FALSE, 0);
8798 /* marks */
8799 marks_clear(t);
8801 /* browser */
8802 t->browser_win = create_browser(t);
8803 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8805 /* oops message for user feedback */
8806 t->oops = gtk_entry_new();
8807 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8808 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8809 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8810 gdk_color_parse(XT_COLOR_RED, &color);
8811 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8812 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8813 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8815 /* command entry */
8816 t->cmd = gtk_entry_new();
8817 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8818 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8819 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8820 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8822 /* status bar */
8823 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8825 t->sbe.statusbar = gtk_entry_new();
8826 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8827 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8828 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8829 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8831 /* create these widgets only if specified in statusbar_elems */
8833 t->sbe.position = create_sbe(40);
8834 t->sbe.zoom = create_sbe(40);
8835 t->sbe.buffercmd = create_sbe(60);
8837 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8839 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8840 TRUE, FALSE);
8842 /* gtk widgets cannot be added to a box twice. sbe_* variables
8843 make sure of this */
8844 for (p = statusbar_elems; *p != '\0'; p++) {
8845 switch (*p) {
8846 case '|':
8848 GtkWidget *sep = gtk_vseparator_new();
8850 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8851 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8852 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8853 FALSE, FALSE, FALSE);
8854 break;
8856 case 'P':
8857 if (sbe_p) {
8858 warnx("flag \"%c\" specified more than "
8859 "once in statusbar_elems\n", *p);
8860 break;
8862 sbe_p = 1;
8863 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8864 t->sbe.position, FALSE, FALSE, FALSE);
8865 break;
8866 case 'B':
8867 if (sbe_b) {
8868 warnx("flag \"%c\" specified more than "
8869 "once in statusbar_elems\n", *p);
8870 break;
8872 sbe_b = 1;
8873 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8874 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8875 break;
8876 case 'Z':
8877 if (sbe_z) {
8878 warnx("flag \"%c\" specified more than "
8879 "once in statusbar_elems\n", *p);
8880 break;
8882 sbe_z = 1;
8883 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8884 t->sbe.zoom, FALSE, FALSE, FALSE);
8885 break;
8886 default:
8887 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8888 break;
8892 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8894 /* buffer list */
8895 t->buffers = create_buffers(t);
8896 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8898 /* xtp meaning is normal by default */
8899 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8901 /* set empty favicon */
8902 xt_icon_from_name(t, "text-html");
8904 /* and show it all */
8905 gtk_widget_show_all(b);
8906 gtk_widget_show_all(t->vbox);
8908 /* compact tab bar */
8909 t->tab_elems.label = gtk_label_new(title);
8910 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8911 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8912 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8913 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8915 t->tab_elems.eventbox = gtk_event_box_new();
8916 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8917 t->tab_elems.sep = gtk_vseparator_new();
8919 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8920 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8921 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8922 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8923 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8924 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8926 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8927 TRUE, 0);
8928 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8929 FALSE, 0);
8930 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8931 t->tab_elems.box);
8933 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8934 TRUE, 0);
8935 gtk_widget_show_all(t->tab_elems.eventbox);
8937 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8938 append_tab(t);
8939 else {
8940 id = position >= 0 ? position :
8941 gtk_notebook_get_current_page(notebook) + 1;
8942 if (id > gtk_notebook_get_n_pages(notebook))
8943 append_tab(t);
8944 else {
8945 TAILQ_INSERT_TAIL(&tabs, t, entry);
8946 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8947 gtk_box_reorder_child(GTK_BOX(tab_bar),
8948 t->tab_elems.eventbox, id);
8949 recalc_tabs();
8953 #if GTK_CHECK_VERSION(2, 20, 0)
8954 /* turn spinner off if we are a new tab without uri */
8955 if (!load) {
8956 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8957 gtk_widget_hide(t->spinner);
8959 #endif
8960 /* make notebook tabs reorderable */
8961 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8963 /* compact tabs clickable */
8964 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8965 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8967 g_object_connect(G_OBJECT(t->cmd),
8968 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8969 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8970 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8971 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8972 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
8973 (char *)NULL);
8975 /* reuse wv_button_cb to hide oops */
8976 g_object_connect(G_OBJECT(t->oops),
8977 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8978 (char *)NULL);
8980 g_signal_connect(t->buffers,
8981 "row-activated", G_CALLBACK(row_activated_cb), t);
8982 g_object_connect(G_OBJECT(t->buffers),
8983 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8985 g_object_connect(G_OBJECT(t->wv),
8986 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8987 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8988 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8989 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8990 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8991 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8992 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8993 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8994 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8995 "signal::event", G_CALLBACK(webview_event_cb), t,
8996 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8997 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8998 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8999 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9000 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9001 (char *)NULL);
9002 g_signal_connect(t->wv,
9003 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9005 * XXX this puts invalid url in uri_entry and that is undesirable
9007 #if 0
9008 g_signal_connect(t->wv,
9009 "load-error", G_CALLBACK(notify_load_error_cb), t);
9010 #endif
9011 g_signal_connect(t->wv,
9012 "notify::title", G_CALLBACK(notify_title_cb), t);
9014 /* hijack the unused keys as if we were the browser */
9015 g_object_connect(G_OBJECT(t->toolbar),
9016 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9017 (char *)NULL);
9019 g_signal_connect(G_OBJECT(bb), "button_press_event",
9020 G_CALLBACK(tab_close_cb), t);
9022 /* hide stuff */
9023 hide_cmd(t);
9024 hide_oops(t);
9025 hide_buffers(t);
9026 url_set_visibility();
9027 statusbar_set_visibility();
9029 if (focus) {
9030 set_current_tab(t->tab_id);
9031 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9032 t->tab_id);
9035 if (load) {
9036 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9037 load_uri(t, title);
9038 } else {
9039 if (show_url == 1)
9040 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9041 else
9042 focus_webview(t);
9045 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9046 /* restore the tab's history */
9047 if (u && u->history) {
9048 items = u->history;
9049 while (items) {
9050 item = items->data;
9051 webkit_web_back_forward_list_add_item(t->bfl, item);
9052 items = g_list_next(items);
9055 item = g_list_nth_data(u->history, u->back);
9056 if (item)
9057 webkit_web_view_go_to_back_forward_item(t->wv, item);
9059 g_list_free(items);
9060 g_list_free(u->history);
9061 } else
9062 webkit_web_back_forward_list_clear(t->bfl);
9064 recolor_compact_tabs();
9065 setzoom_webkit(t, XT_ZOOM_NORMAL);
9066 return (t);
9069 void
9070 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9071 gpointer *udata)
9073 struct tab *t;
9074 const gchar *uri;
9076 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9078 if (gtk_notebook_get_current_page(notebook) == -1)
9079 recalc_tabs();
9081 TAILQ_FOREACH(t, &tabs, entry) {
9082 if (t->tab_id == pn) {
9083 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9084 "%d\n", pn);
9086 uri = get_title(t, TRUE);
9087 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9089 hide_cmd(t);
9090 hide_oops(t);
9092 if (t->focus_wv) {
9093 /* can't use focus_webview here */
9094 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9100 void
9101 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9102 gpointer *udata)
9104 struct tab *t = NULL, *tt;
9106 recalc_tabs();
9108 TAILQ_FOREACH(tt, &tabs, entry)
9109 if (tt->tab_id == pn) {
9110 t = tt;
9111 break;
9114 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9116 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9117 t->tab_id);
9120 void
9121 menuitem_response(struct tab *t)
9123 gtk_notebook_set_current_page(notebook, t->tab_id);
9126 gboolean
9127 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9129 GtkWidget *menu, *menu_items;
9130 GdkEventButton *bevent;
9131 const gchar *uri;
9132 struct tab *ti;
9134 if (event->type == GDK_BUTTON_PRESS) {
9135 bevent = (GdkEventButton *) event;
9136 menu = gtk_menu_new();
9138 TAILQ_FOREACH(ti, &tabs, entry) {
9139 if ((uri = get_uri(ti)) == NULL)
9140 /* XXX make sure there is something to print */
9141 /* XXX add gui pages in here to look purdy */
9142 uri = "(untitled)";
9143 menu_items = gtk_menu_item_new_with_label(uri);
9144 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9145 gtk_widget_show(menu_items);
9147 g_signal_connect_swapped((menu_items),
9148 "activate", G_CALLBACK(menuitem_response),
9149 (gpointer)ti);
9152 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9153 bevent->button, bevent->time);
9155 /* unref object so it'll free itself when popped down */
9156 #if !GTK_CHECK_VERSION(3, 0, 0)
9157 /* XXX does not need unref with gtk+3? */
9158 g_object_ref_sink(menu);
9159 g_object_unref(menu);
9160 #endif
9162 return (TRUE /* eat event */);
9165 return (FALSE /* propagate */);
9169 icon_size_map(int icon_size)
9171 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9172 icon_size > GTK_ICON_SIZE_DIALOG)
9173 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9175 return (icon_size);
9178 GtkWidget *
9179 create_button(char *name, char *stockid, int size)
9181 GtkWidget *button, *image;
9182 gchar *rcstring;
9183 int gtk_icon_size;
9185 rcstring = g_strdup_printf(
9186 "style \"%s-style\"\n"
9187 "{\n"
9188 " GtkWidget::focus-padding = 0\n"
9189 " GtkWidget::focus-line-width = 0\n"
9190 " xthickness = 0\n"
9191 " ythickness = 0\n"
9192 "}\n"
9193 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9194 gtk_rc_parse_string(rcstring);
9195 g_free(rcstring);
9196 button = gtk_button_new();
9197 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9198 gtk_icon_size = icon_size_map(size ? size : icon_size);
9200 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9201 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9202 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9203 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9204 gtk_widget_set_name(button, name);
9205 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9207 return (button);
9210 void
9211 button_set_stockid(GtkWidget *button, char *stockid)
9213 GtkWidget *image;
9215 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9216 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9217 gtk_button_set_image(GTK_BUTTON(button), image);
9220 void
9221 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9223 gchar *p = NULL;
9224 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9225 gint len;
9227 if (xterm_workaround == 0)
9228 return;
9231 * xterm doesn't play nice with clipboards because it clears the
9232 * primary when clicked. We rely on primary being set to properly
9233 * handle middle mouse button clicks (paste). So when someone clears
9234 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9235 * other application behavior (as in DON'T clear primary).
9238 p = gtk_clipboard_wait_for_text(primary);
9239 if (p == NULL) {
9240 if (gdk_property_get(gdk_get_default_root_window(),
9241 atom,
9242 gdk_atom_intern("STRING", FALSE),
9244 1024 * 1024 /* picked out of my butt */,
9245 FALSE,
9246 NULL,
9247 NULL,
9248 &len,
9249 (guchar **)&p)) {
9250 /* yes sir, we need to NUL the string */
9251 p[len] = '\0';
9252 gtk_clipboard_set_text(primary, p, -1);
9256 if (p)
9257 g_free(p);
9260 void
9261 create_canvas(void)
9263 GtkWidget *vbox;
9264 GList *l = NULL;
9265 GdkPixbuf *pb;
9266 char file[PATH_MAX];
9267 int i;
9269 vbox = gtk_vbox_new(FALSE, 0);
9270 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9271 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9272 #if !GTK_CHECK_VERSION(3, 0, 0)
9273 /* XXX seems to be needed with gtk+2 */
9274 gtk_notebook_set_tab_hborder(notebook, 0);
9275 gtk_notebook_set_tab_vborder(notebook, 0);
9276 #endif
9277 gtk_notebook_set_scrollable(notebook, TRUE);
9278 gtk_notebook_set_show_border(notebook, FALSE);
9279 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9281 abtn = gtk_button_new();
9282 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9283 gtk_widget_set_size_request(arrow, -1, -1);
9284 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9285 gtk_widget_set_size_request(abtn, -1, 20);
9287 #if GTK_CHECK_VERSION(2, 20, 0)
9288 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9289 #endif
9290 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9292 /* compact tab bar */
9293 tab_bar = gtk_hbox_new(TRUE, 0);
9295 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9296 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9297 gtk_widget_set_size_request(vbox, -1, -1);
9299 g_object_connect(G_OBJECT(notebook),
9300 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9301 (char *)NULL);
9302 g_object_connect(G_OBJECT(notebook),
9303 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9304 NULL, (char *)NULL);
9305 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9306 G_CALLBACK(arrow_cb), NULL);
9308 main_window = create_window();
9309 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9311 /* icons */
9312 for (i = 0; i < LENGTH(icons); i++) {
9313 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9314 pb = gdk_pixbuf_new_from_file(file, NULL);
9315 l = g_list_append(l, pb);
9317 gtk_window_set_default_icon_list(l);
9319 /* clipboard work around */
9320 if (xterm_workaround)
9321 g_signal_connect(
9322 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9323 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9325 gtk_widget_show_all(abtn);
9326 gtk_widget_show_all(main_window);
9327 notebook_tab_set_visibility();
9330 void
9331 set_hook(void **hook, char *name)
9333 if (hook == NULL)
9334 errx(1, "set_hook");
9336 if (*hook == NULL) {
9337 *hook = dlsym(RTLD_NEXT, name);
9338 if (*hook == NULL)
9339 errx(1, "can't hook %s", name);
9343 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9344 gboolean
9345 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9347 g_return_val_if_fail(cookie1, FALSE);
9348 g_return_val_if_fail(cookie2, FALSE);
9350 return (!strcmp (cookie1->name, cookie2->name) &&
9351 !strcmp (cookie1->value, cookie2->value) &&
9352 !strcmp (cookie1->path, cookie2->path) &&
9353 !strcmp (cookie1->domain, cookie2->domain));
9356 void
9357 transfer_cookies(void)
9359 GSList *cf;
9360 SoupCookie *sc, *pc;
9362 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9364 for (;cf; cf = cf->next) {
9365 pc = cf->data;
9366 sc = soup_cookie_copy(pc);
9367 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9370 soup_cookies_free(cf);
9373 void
9374 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9376 GSList *cf;
9377 SoupCookie *ci;
9379 print_cookie("soup_cookie_jar_delete_cookie", c);
9381 if (cookies_enabled == 0)
9382 return;
9384 if (jar == NULL || c == NULL)
9385 return;
9387 /* find and remove from persistent jar */
9388 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9390 for (;cf; cf = cf->next) {
9391 ci = cf->data;
9392 if (soup_cookie_equal(ci, c)) {
9393 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9394 break;
9398 soup_cookies_free(cf);
9400 /* delete from session jar */
9401 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9404 void
9405 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9407 struct domain *d = NULL;
9408 SoupCookie *c;
9409 FILE *r_cookie_f;
9411 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9412 jar, p_cookiejar, s_cookiejar);
9414 if (cookies_enabled == 0)
9415 return;
9417 /* see if we are up and running */
9418 if (p_cookiejar == NULL) {
9419 _soup_cookie_jar_add_cookie(jar, cookie);
9420 return;
9422 /* disallow p_cookiejar adds, shouldn't happen */
9423 if (jar == p_cookiejar)
9424 return;
9426 /* sanity */
9427 if (jar == NULL || cookie == NULL)
9428 return;
9430 if (enable_cookie_whitelist &&
9431 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9432 blocked_cookies++;
9433 DNPRINTF(XT_D_COOKIE,
9434 "soup_cookie_jar_add_cookie: reject %s\n",
9435 cookie->domain);
9436 if (save_rejected_cookies) {
9437 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9438 show_oops(NULL, "can't open reject cookie file");
9439 return;
9441 fseek(r_cookie_f, 0, SEEK_END);
9442 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9443 cookie->http_only ? "#HttpOnly_" : "",
9444 cookie->domain,
9445 *cookie->domain == '.' ? "TRUE" : "FALSE",
9446 cookie->path,
9447 cookie->secure ? "TRUE" : "FALSE",
9448 cookie->expires ?
9449 (gulong)soup_date_to_time_t(cookie->expires) :
9451 cookie->name,
9452 cookie->value);
9453 fflush(r_cookie_f);
9454 fclose(r_cookie_f);
9456 if (!allow_volatile_cookies)
9457 return;
9460 if (cookie->expires == NULL && session_timeout) {
9461 soup_cookie_set_expires(cookie,
9462 soup_date_new_from_now(session_timeout));
9463 print_cookie("modified add cookie", cookie);
9466 /* see if we are white listed for persistence */
9467 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9468 /* add to persistent jar */
9469 c = soup_cookie_copy(cookie);
9470 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9471 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9474 /* add to session jar */
9475 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9476 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9479 void
9480 setup_cookies(void)
9482 char file[PATH_MAX];
9484 set_hook((void *)&_soup_cookie_jar_add_cookie,
9485 "soup_cookie_jar_add_cookie");
9486 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9487 "soup_cookie_jar_delete_cookie");
9489 if (cookies_enabled == 0)
9490 return;
9493 * the following code is intricate due to overriding several libsoup
9494 * functions.
9495 * do not alter order of these operations.
9498 /* rejected cookies */
9499 if (save_rejected_cookies)
9500 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9501 XT_REJECT_FILE);
9503 /* persistent cookies */
9504 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9505 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9507 /* session cookies */
9508 s_cookiejar = soup_cookie_jar_new();
9509 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9510 cookie_policy, (void *)NULL);
9511 transfer_cookies();
9513 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9516 void
9517 setup_proxy(char *uri)
9519 SoupURI *suri;
9521 if (proxy_uri) {
9522 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9523 soup_uri_free(proxy_uri);
9524 proxy_uri = NULL;
9526 if (http_proxy) {
9527 if (http_proxy != uri) {
9528 g_free(http_proxy);
9529 http_proxy = NULL;
9533 if (uri) {
9534 http_proxy = g_strdup(uri);
9535 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9536 suri = soup_uri_new(http_proxy);
9537 if (!(suri == NULL || !SOUP_URI_VALID_FOR_HTTP(suri)))
9538 g_object_set(session, "proxy-uri", proxy_uri,
9539 (char *)NULL);
9540 if (suri)
9541 soup_uri_free(suri);
9546 set_http_proxy(char *proxy)
9548 SoupURI *uri;
9550 if (proxy == NULL)
9551 return (1);
9553 /* see if we need to clear it instead */
9554 if (strlen(proxy) == 0) {
9555 setup_proxy(NULL);
9556 return (0);
9559 uri = soup_uri_new(proxy);
9560 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9561 return (1);
9563 setup_proxy(proxy);
9565 soup_uri_free(uri);
9567 return (0);
9571 send_cmd_to_socket(char *cmd)
9573 int s, len, rv = 1;
9574 struct sockaddr_un sa;
9576 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9577 warnx("%s: socket", __func__);
9578 return (rv);
9581 sa.sun_family = AF_UNIX;
9582 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9583 work_dir, XT_SOCKET_FILE);
9584 len = SUN_LEN(&sa);
9586 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9587 warnx("%s: connect", __func__);
9588 goto done;
9591 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9592 warnx("%s: send", __func__);
9593 goto done;
9596 rv = 0;
9597 done:
9598 close(s);
9599 return (rv);
9602 gboolean
9603 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9605 int s, n;
9606 char str[XT_MAX_URL_LENGTH];
9607 socklen_t t = sizeof(struct sockaddr_un);
9608 struct sockaddr_un sa;
9609 struct passwd *p;
9610 uid_t uid;
9611 gid_t gid;
9612 struct tab *tt;
9613 gint fd = g_io_channel_unix_get_fd(source);
9615 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9616 warn("accept");
9617 return (FALSE);
9620 if (getpeereid(s, &uid, &gid) == -1) {
9621 warn("getpeereid");
9622 return (FALSE);
9624 if (uid != getuid() || gid != getgid()) {
9625 warnx("unauthorized user");
9626 return (FALSE);
9629 p = getpwuid(uid);
9630 if (p == NULL) {
9631 warnx("not a valid user");
9632 return (FALSE);
9635 n = recv(s, str, sizeof(str), 0);
9636 if (n <= 0)
9637 return (TRUE);
9639 tt = TAILQ_LAST(&tabs, tab_list);
9640 cmd_execute(tt, str);
9641 return (TRUE);
9645 is_running(void)
9647 int s, len, rv = 1;
9648 struct sockaddr_un sa;
9650 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9651 warn("is_running: socket");
9652 return (-1);
9655 sa.sun_family = AF_UNIX;
9656 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9657 work_dir, XT_SOCKET_FILE);
9658 len = SUN_LEN(&sa);
9660 /* connect to see if there is a listener */
9661 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9662 rv = 0; /* not running */
9663 else
9664 rv = 1; /* already running */
9666 close(s);
9668 return (rv);
9672 build_socket(void)
9674 int s, len;
9675 struct sockaddr_un sa;
9677 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9678 warn("build_socket: socket");
9679 return (-1);
9682 sa.sun_family = AF_UNIX;
9683 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9684 work_dir, XT_SOCKET_FILE);
9685 len = SUN_LEN(&sa);
9687 /* connect to see if there is a listener */
9688 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9689 /* no listener so we will */
9690 unlink(sa.sun_path);
9692 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9693 warn("build_socket: bind");
9694 goto done;
9697 if (listen(s, 1) == -1) {
9698 warn("build_socket: listen");
9699 goto done;
9702 return (s);
9705 done:
9706 close(s);
9707 return (-1);
9710 gboolean
9711 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9712 GtkTreeIter *iter, struct tab *t)
9714 gchar *value;
9716 gtk_tree_model_get(model, iter, 0, &value, -1);
9717 load_uri(t, value);
9718 g_free(value);
9720 return (FALSE);
9723 gboolean
9724 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9725 GtkTreeIter *iter, struct tab *t)
9727 gchar *value;
9729 gtk_tree_model_get(model, iter, 0, &value, -1);
9730 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9731 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9732 g_free(value);
9734 return (TRUE);
9737 void
9738 completion_add_uri(const gchar *uri)
9740 GtkTreeIter iter;
9742 /* add uri to list_store */
9743 gtk_list_store_append(completion_model, &iter);
9744 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9747 gboolean
9748 completion_match(GtkEntryCompletion *completion, const gchar *key,
9749 GtkTreeIter *iter, gpointer user_data)
9751 gchar *value;
9752 gboolean match = FALSE;
9754 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9755 -1);
9757 if (value == NULL)
9758 return FALSE;
9760 match = match_uri(value, key);
9762 g_free(value);
9763 return (match);
9766 void
9767 completion_add(struct tab *t)
9769 /* enable completion for tab */
9770 t->completion = gtk_entry_completion_new();
9771 gtk_entry_completion_set_text_column(t->completion, 0);
9772 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9773 gtk_entry_completion_set_model(t->completion,
9774 GTK_TREE_MODEL(completion_model));
9775 gtk_entry_completion_set_match_func(t->completion, completion_match,
9776 NULL, NULL);
9777 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9778 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9779 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9780 G_CALLBACK(completion_select_cb), t);
9781 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9782 G_CALLBACK(completion_hover_cb), t);
9785 void
9786 xxx_dir(char *dir)
9788 struct stat sb;
9790 if (stat(dir, &sb)) {
9791 if (mkdir(dir, S_IRWXU) == -1)
9792 err(1, "mkdir %s", dir);
9793 if (stat(dir, &sb))
9794 err(1, "stat %s", dir);
9796 if (S_ISDIR(sb.st_mode) == 0)
9797 errx(1, "%s not a dir", dir);
9798 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9799 warnx("fixing invalid permissions on %s", dir);
9800 if (chmod(dir, S_IRWXU) == -1)
9801 err(1, "chmod %s", dir);
9805 void
9806 usage(void)
9808 fprintf(stderr,
9809 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9810 exit(0);
9815 main(int argc, char *argv[])
9817 struct stat sb;
9818 int c, s, optn = 0, opte = 0, focus = 1;
9819 char conf[PATH_MAX] = { '\0' };
9820 char file[PATH_MAX];
9821 char *env_proxy = NULL;
9822 char *cmd = NULL;
9823 FILE *f = NULL;
9824 struct karg a;
9825 struct sigaction sact;
9826 GIOChannel *channel;
9827 struct rlimit rlp;
9829 start_argv = argv;
9831 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9833 RB_INIT(&hl);
9834 RB_INIT(&js_wl);
9835 RB_INIT(&downloads);
9837 TAILQ_INIT(&tabs);
9838 TAILQ_INIT(&mtl);
9839 TAILQ_INIT(&aliases);
9840 TAILQ_INIT(&undos);
9841 TAILQ_INIT(&kbl);
9842 TAILQ_INIT(&spl);
9843 TAILQ_INIT(&chl);
9845 /* fiddle with ulimits */
9846 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9847 warn("getrlimit");
9848 else {
9849 /* just use them all */
9850 rlp.rlim_cur = rlp.rlim_max;
9851 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9852 warn("setrlimit");
9853 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9854 warn("getrlimit");
9855 else if (rlp.rlim_cur <= 256)
9856 startpage_add("%s requires at least 256 file "
9857 "descriptors, currently it has up to %d available",
9858 __progname, rlp.rlim_cur);
9861 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9862 switch (c) {
9863 case 'S':
9864 show_url = 0;
9865 break;
9866 case 'T':
9867 show_tabs = 0;
9868 break;
9869 case 'V':
9870 errx(0 , "Version: %s", version);
9871 break;
9872 case 'f':
9873 strlcpy(conf, optarg, sizeof(conf));
9874 break;
9875 case 's':
9876 strlcpy(named_session, optarg, sizeof(named_session));
9877 break;
9878 case 't':
9879 tabless = 1;
9880 break;
9881 case 'n':
9882 optn = 1;
9883 break;
9884 case 'e':
9885 opte = 1;
9886 break;
9887 default:
9888 usage();
9889 /* NOTREACHED */
9892 argc -= optind;
9893 argv += optind;
9895 init_keybindings();
9897 gnutls_global_init();
9899 /* generate session keys for xtp pages */
9900 generate_xtp_session_key(&dl_session_key);
9901 generate_xtp_session_key(&hl_session_key);
9902 generate_xtp_session_key(&cl_session_key);
9903 generate_xtp_session_key(&fl_session_key);
9905 /* prepare gtk */
9906 if (!g_thread_supported()) {
9907 g_thread_init(NULL);
9908 gdk_threads_init();
9909 gdk_threads_enter();
9911 gtk_init(&argc, &argv);
9913 /* signals */
9914 bzero(&sact, sizeof(sact));
9915 sigemptyset(&sact.sa_mask);
9916 sact.sa_handler = sigchild;
9917 sact.sa_flags = SA_NOCLDSTOP;
9918 sigaction(SIGCHLD, &sact, NULL);
9920 /* set download dir */
9921 pwd = getpwuid(getuid());
9922 if (pwd == NULL)
9923 errx(1, "invalid user %d", getuid());
9924 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9926 /* compile buffer command regexes */
9927 buffercmd_init();
9929 /* set default string settings */
9930 home = g_strdup("https://www.cyphertite.com");
9931 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9932 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9933 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9934 cmd_font_name = g_strdup("monospace normal 9");
9935 oops_font_name = g_strdup("monospace normal 9");
9936 statusbar_font_name = g_strdup("monospace normal 9");
9937 tabbar_font_name = g_strdup("monospace normal 9");
9938 statusbar_elems = g_strdup("BP");
9940 /* read config file */
9941 if (strlen(conf) == 0)
9942 snprintf(conf, sizeof conf, "%s/.%s",
9943 pwd->pw_dir, XT_CONF_FILE);
9944 config_parse(conf, 0);
9946 /* init fonts */
9947 cmd_font = pango_font_description_from_string(cmd_font_name);
9948 oops_font = pango_font_description_from_string(oops_font_name);
9949 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9950 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9952 /* working directory */
9953 if (strlen(work_dir) == 0)
9954 snprintf(work_dir, sizeof work_dir, "%s/%s",
9955 pwd->pw_dir, XT_DIR);
9956 xxx_dir(work_dir);
9958 /* icon cache dir */
9959 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9960 xxx_dir(cache_dir);
9962 /* certs dir */
9963 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9964 xxx_dir(certs_dir);
9966 /* sessions dir */
9967 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9968 work_dir, XT_SESSIONS_DIR);
9969 xxx_dir(sessions_dir);
9971 /* runtime settings that can override config file */
9972 if (runtime_settings[0] != '\0')
9973 config_parse(runtime_settings, 1);
9975 /* download dir */
9976 if (!strcmp(download_dir, pwd->pw_dir))
9977 strlcat(download_dir, "/downloads", sizeof download_dir);
9978 xxx_dir(download_dir);
9980 /* favorites file */
9981 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9982 if (stat(file, &sb)) {
9983 warnx("favorites file doesn't exist, creating it");
9984 if ((f = fopen(file, "w")) == NULL)
9985 err(1, "favorites");
9986 fclose(f);
9989 /* quickmarks file */
9990 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9991 if (stat(file, &sb)) {
9992 warnx("quickmarks file doesn't exist, creating it");
9993 if ((f = fopen(file, "w")) == NULL)
9994 err(1, "quickmarks");
9995 fclose(f);
9998 /* cookies */
9999 session = webkit_get_default_session();
10000 setup_cookies();
10002 /* certs */
10003 if (ssl_ca_file) {
10004 if (stat(ssl_ca_file, &sb)) {
10005 warnx("no CA file: %s", ssl_ca_file);
10006 g_free(ssl_ca_file);
10007 ssl_ca_file = NULL;
10008 } else
10009 g_object_set(session,
10010 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10011 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10012 (void *)NULL);
10015 /* guess_search regex */
10016 if (url_regex == NULL)
10017 url_regex = g_strdup(XT_URL_REGEX);
10018 if (url_regex)
10019 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10020 startpage_add("invalid url regex %s", url_regex);
10022 /* proxy */
10023 env_proxy = getenv("http_proxy");
10024 if (env_proxy)
10025 setup_proxy(env_proxy);
10026 else
10027 setup_proxy(http_proxy);
10029 if (opte) {
10030 send_cmd_to_socket(argv[0]);
10031 exit(0);
10034 /* set some connection parameters */
10035 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10036 g_object_set(session, "max-conns-per-host", max_host_connections,
10037 (char *)NULL);
10039 /* see if there is already an xxxterm running */
10040 if (single_instance && is_running()) {
10041 optn = 1;
10042 warnx("already running");
10045 if (optn) {
10046 while (argc) {
10047 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10048 send_cmd_to_socket(cmd);
10049 if (cmd)
10050 g_free(cmd);
10052 argc--;
10053 argv++;
10055 exit(0);
10058 /* uri completion */
10059 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10061 /* buffers */
10062 buffers_store = gtk_list_store_new
10063 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10065 qmarks_load();
10067 /* go graphical */
10068 create_canvas();
10069 notebook_tab_set_visibility();
10071 if (save_global_history)
10072 restore_global_history();
10074 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10075 restore_saved_tabs();
10076 else {
10077 a.s = named_session;
10078 a.i = XT_SES_DONOTHING;
10079 open_tabs(NULL, &a);
10082 /* see if we have an exception */
10083 if (!TAILQ_EMPTY(&spl)) {
10084 create_new_tab("about:startpage", NULL, focus, -1);
10085 focus = 0;
10088 while (argc) {
10089 create_new_tab(argv[0], NULL, focus, -1);
10090 focus = 0;
10092 argc--;
10093 argv++;
10096 if (TAILQ_EMPTY(&tabs))
10097 create_new_tab(home, NULL, 1, -1);
10099 if (enable_socket)
10100 if ((s = build_socket()) != -1) {
10101 channel = g_io_channel_unix_new(s);
10102 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10105 gtk_main();
10107 if (!g_thread_supported()) {
10108 gdk_threads_leave();
10111 gnutls_global_deinit();
10113 if (url_regex)
10114 regfree(&url_re);
10116 return (0);