Sites like flickr REQUIRE HTML5 local storage for the JS popup menus to
[xxxterm.git] / xxxterm.c
blob56dd3a8cbdab7fee8c6a466d49c3dc5cb42fcd52
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;
227 /* hints */
228 int hints_on;
229 int hint_mode;
230 #define XT_HINT_NONE (0)
231 #define XT_HINT_NUMERICAL (1)
232 #define XT_HINT_ALPHANUM (2)
233 char hint_buf[128];
234 char hint_num[128];
236 /* custom stylesheet */
237 int styled;
238 char *stylesheet;
240 /* search */
241 char *search_text;
242 int search_forward;
243 guint search_id;
245 /* settings */
246 WebKitWebSettings *settings;
247 gchar *user_agent;
249 /* marks */
250 double mark[XT_NOMARKS];
252 TAILQ_HEAD(tab_list, tab);
254 struct history {
255 RB_ENTRY(history) entry;
256 const gchar *uri;
257 const gchar *title;
259 RB_HEAD(history_list, history);
261 struct download {
262 RB_ENTRY(download) entry;
263 int id;
264 WebKitDownload *download;
265 struct tab *tab;
267 RB_HEAD(download_list, download);
269 struct domain {
270 RB_ENTRY(domain) entry;
271 gchar *d;
272 int handy; /* app use */
274 RB_HEAD(domain_list, domain);
276 struct undo {
277 TAILQ_ENTRY(undo) entry;
278 gchar *uri;
279 GList *history;
280 int back; /* Keeps track of how many back
281 * history items there are. */
283 TAILQ_HEAD(undo_tailq, undo);
285 struct sp {
286 char *line;
287 TAILQ_ENTRY(sp) entry;
289 TAILQ_HEAD(sp_list, sp);
291 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
292 int next_download_id = 1;
294 struct karg {
295 int i;
296 char *s;
297 int precount;
300 /* defines */
301 #define XT_NAME ("XXXTerm")
302 #define XT_DIR (".xxxterm")
303 #define XT_CACHE_DIR ("cache")
304 #define XT_CERT_DIR ("certs/")
305 #define XT_SESSIONS_DIR ("sessions/")
306 #define XT_CONF_FILE ("xxxterm.conf")
307 #define XT_FAVS_FILE ("favorites")
308 #define XT_QMARKS_FILE ("quickmarks")
309 #define XT_SAVED_TABS_FILE ("main_session")
310 #define XT_RESTART_TABS_FILE ("restart_tabs")
311 #define XT_SOCKET_FILE ("socket")
312 #define XT_HISTORY_FILE ("history")
313 #define XT_REJECT_FILE ("rejected.txt")
314 #define XT_COOKIE_FILE ("cookies.txt")
315 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
316 #define XT_CB_HANDLED (TRUE)
317 #define XT_CB_PASSTHROUGH (FALSE)
318 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
319 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
320 #define XT_DLMAN_REFRESH "10"
321 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
322 "td{overflow: hidden;" \
323 " padding: 2px 2px 2px 2px;" \
324 " border: 1px solid black;" \
325 " vertical-align:top;" \
326 " word-wrap: break-word}\n" \
327 "tr:hover{background: #ffff99}\n" \
328 "th{background-color: #cccccc;" \
329 " border: 1px solid black}\n" \
330 "table{width: 100%%;" \
331 " border: 1px black solid;" \
332 " border-collapse:collapse}\n" \
333 ".progress-outer{" \
334 "border: 1px solid black;" \
335 " height: 8px;" \
336 " width: 90%%}\n" \
337 ".progress-inner{float: left;" \
338 " height: 8px;" \
339 " background: green}\n" \
340 ".dlstatus{font-size: small;" \
341 " text-align: center}\n" \
342 "</style>\n"
343 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
344 #define XT_MAX_UNDO_CLOSE_TAB (32)
345 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
346 #define XT_PRINT_EXTRA_MARGIN 10
347 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
349 /* colors */
350 #define XT_COLOR_RED "#cc0000"
351 #define XT_COLOR_YELLOW "#ffff66"
352 #define XT_COLOR_BLUE "lightblue"
353 #define XT_COLOR_GREEN "#99ff66"
354 #define XT_COLOR_WHITE "white"
355 #define XT_COLOR_BLACK "black"
357 #define XT_COLOR_CT_BACKGROUND "#000000"
358 #define XT_COLOR_CT_INACTIVE "#dddddd"
359 #define XT_COLOR_CT_ACTIVE "#bbbb00"
360 #define XT_COLOR_CT_SEPARATOR "#555555"
362 #define XT_COLOR_SB_SEPARATOR "#555555"
364 #define XT_PROTO_DELIM "://"
367 * xxxterm "protocol" (xtp)
368 * We use this for managing stuff like downloads and favorites. They
369 * make magical HTML pages in memory which have xxxt:// links in order
370 * to communicate with xxxterm's internals. These links take the format:
371 * xxxt://class/session_key/action/arg
373 * Don't begin xtp class/actions as 0. atoi returns that on error.
375 * Typically we have not put addition of items in this framework, as
376 * adding items is either done via an ex-command or via a keybinding instead.
379 #define XT_XTP_STR "xxxt://"
381 /* XTP classes (xxxt://<class>) */
382 #define XT_XTP_INVALID 0 /* invalid */
383 #define XT_XTP_DL 1 /* downloads */
384 #define XT_XTP_HL 2 /* history */
385 #define XT_XTP_CL 3 /* cookies */
386 #define XT_XTP_FL 4 /* favorites */
388 /* XTP download actions */
389 #define XT_XTP_DL_LIST 1
390 #define XT_XTP_DL_CANCEL 2
391 #define XT_XTP_DL_REMOVE 3
393 /* XTP history actions */
394 #define XT_XTP_HL_LIST 1
395 #define XT_XTP_HL_REMOVE 2
397 /* XTP cookie actions */
398 #define XT_XTP_CL_LIST 1
399 #define XT_XTP_CL_REMOVE 2
401 /* XTP cookie actions */
402 #define XT_XTP_FL_LIST 1
403 #define XT_XTP_FL_REMOVE 2
405 /* actions */
406 #define XT_MOVE_INVALID (0)
407 #define XT_MOVE_DOWN (1)
408 #define XT_MOVE_UP (2)
409 #define XT_MOVE_BOTTOM (3)
410 #define XT_MOVE_TOP (4)
411 #define XT_MOVE_PAGEDOWN (5)
412 #define XT_MOVE_PAGEUP (6)
413 #define XT_MOVE_HALFDOWN (7)
414 #define XT_MOVE_HALFUP (8)
415 #define XT_MOVE_LEFT (9)
416 #define XT_MOVE_FARLEFT (10)
417 #define XT_MOVE_RIGHT (11)
418 #define XT_MOVE_FARRIGHT (12)
419 #define XT_MOVE_PERCENT (13)
421 #define XT_QMARK_SET (0)
422 #define XT_QMARK_OPEN (1)
423 #define XT_QMARK_TAB (2)
425 #define XT_MARK_SET (0)
426 #define XT_MARK_GOTO (1)
428 #define XT_TAB_LAST (-4)
429 #define XT_TAB_FIRST (-3)
430 #define XT_TAB_PREV (-2)
431 #define XT_TAB_NEXT (-1)
432 #define XT_TAB_INVALID (0)
433 #define XT_TAB_NEW (1)
434 #define XT_TAB_DELETE (2)
435 #define XT_TAB_DELQUIT (3)
436 #define XT_TAB_OPEN (4)
437 #define XT_TAB_UNDO_CLOSE (5)
438 #define XT_TAB_SHOW (6)
439 #define XT_TAB_HIDE (7)
440 #define XT_TAB_NEXTSTYLE (8)
442 #define XT_NAV_INVALID (0)
443 #define XT_NAV_BACK (1)
444 #define XT_NAV_FORWARD (2)
445 #define XT_NAV_RELOAD (3)
447 #define XT_FOCUS_INVALID (0)
448 #define XT_FOCUS_URI (1)
449 #define XT_FOCUS_SEARCH (2)
451 #define XT_SEARCH_INVALID (0)
452 #define XT_SEARCH_NEXT (1)
453 #define XT_SEARCH_PREV (2)
455 #define XT_PASTE_CURRENT_TAB (0)
456 #define XT_PASTE_NEW_TAB (1)
458 #define XT_ZOOM_IN (-1)
459 #define XT_ZOOM_OUT (-2)
460 #define XT_ZOOM_NORMAL (100)
462 #define XT_URL_SHOW (1)
463 #define XT_URL_HIDE (2)
465 #define XT_WL_TOGGLE (1<<0)
466 #define XT_WL_ENABLE (1<<1)
467 #define XT_WL_DISABLE (1<<2)
468 #define XT_WL_FQDN (1<<3) /* default */
469 #define XT_WL_TOPLEVEL (1<<4)
470 #define XT_WL_PERSISTENT (1<<5)
471 #define XT_WL_SESSION (1<<6)
472 #define XT_WL_RELOAD (1<<7)
474 #define XT_SHOW (1<<7)
475 #define XT_DELETE (1<<8)
476 #define XT_SAVE (1<<9)
477 #define XT_OPEN (1<<10)
479 #define XT_CMD_OPEN (0)
480 #define XT_CMD_OPEN_CURRENT (1)
481 #define XT_CMD_TABNEW (2)
482 #define XT_CMD_TABNEW_CURRENT (3)
484 #define XT_STATUS_NOTHING (0)
485 #define XT_STATUS_LINK (1)
486 #define XT_STATUS_URI (2)
487 #define XT_STATUS_LOADING (3)
489 #define XT_SES_DONOTHING (0)
490 #define XT_SES_CLOSETABS (1)
492 #define XT_BM_NORMAL (0)
493 #define XT_BM_WHITELIST (1)
494 #define XT_BM_KIOSK (2)
496 #define XT_PREFIX (1<<0)
497 #define XT_USERARG (1<<1)
498 #define XT_URLARG (1<<2)
499 #define XT_INTARG (1<<3)
501 #define XT_TABS_NORMAL 0
502 #define XT_TABS_COMPACT 1
504 #define XT_BUFCMD_SZ (8)
506 /* mime types */
507 struct mime_type {
508 char *mt_type;
509 char *mt_action;
510 int mt_default;
511 int mt_download;
512 TAILQ_ENTRY(mime_type) entry;
514 TAILQ_HEAD(mime_type_list, mime_type);
516 /* uri aliases */
517 struct alias {
518 char *a_name;
519 char *a_uri;
520 TAILQ_ENTRY(alias) entry;
522 TAILQ_HEAD(alias_list, alias);
524 /* settings that require restart */
525 int tabless = 0; /* allow only 1 tab */
526 int enable_socket = 0;
527 int single_instance = 0; /* only allow one xxxterm to run */
528 int fancy_bar = 1; /* fancy toolbar */
529 int browser_mode = XT_BM_NORMAL;
530 int enable_localstorage = 0;
531 char *statusbar_elems = NULL;
533 /* runtime settings */
534 int show_tabs = 1; /* show tabs on notebook */
535 int tab_style = XT_TABS_NORMAL; /* tab bar style */
536 int show_url = 1; /* show url toolbar on notebook */
537 int show_statusbar = 0; /* vimperator style status bar */
538 int ctrl_click_focus = 0; /* ctrl click gets focus */
539 int cookies_enabled = 1; /* enable cookies */
540 int read_only_cookies = 0; /* enable to not write cookies */
541 int enable_scripts = 1;
542 int enable_plugins = 0;
543 gfloat default_zoom_level = 1.0;
544 char default_script[PATH_MAX];
545 int window_height = 768;
546 int window_width = 1024;
547 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
548 int refresh_interval = 10; /* download refresh interval */
549 int enable_cookie_whitelist = 0;
550 int enable_js_whitelist = 0;
551 int session_timeout = 3600; /* cookie session timeout */
552 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
553 char *ssl_ca_file = NULL;
554 char *resource_dir = NULL;
555 gboolean ssl_strict_certs = FALSE;
556 int append_next = 1; /* append tab after current tab */
557 char *home = NULL;
558 char *search_string = NULL;
559 char *http_proxy = NULL;
560 char download_dir[PATH_MAX];
561 char runtime_settings[PATH_MAX]; /* override of settings */
562 int allow_volatile_cookies = 0;
563 int save_global_history = 0; /* save global history to disk */
564 char *user_agent = NULL;
565 int save_rejected_cookies = 0;
566 int session_autosave = 0;
567 int guess_search = 0;
568 int dns_prefetch = FALSE;
569 gint max_connections = 25;
570 gint max_host_connections = 5;
571 gint enable_spell_checking = 0;
572 char *spell_check_languages = NULL;
573 int xterm_workaround = 0;
575 char *cmd_font_name = NULL;
576 char *oops_font_name = NULL;
577 char *statusbar_font_name = NULL;
578 char *tabbar_font_name = NULL;
579 PangoFontDescription *cmd_font;
580 PangoFontDescription *oops_font;
581 PangoFontDescription *statusbar_font;
582 PangoFontDescription *tabbar_font;
583 char *qmarks[XT_NOMARKS];
585 int btn_down; /* M1 down in any wv */
587 struct settings;
588 struct key_binding;
589 int set_browser_mode(struct settings *, char *);
590 int set_cookie_policy(struct settings *, char *);
591 int set_download_dir(struct settings *, char *);
592 int set_default_script(struct settings *, char *);
593 int set_runtime_dir(struct settings *, char *);
594 int set_tab_style(struct settings *, char *);
595 int set_work_dir(struct settings *, char *);
596 int add_alias(struct settings *, char *);
597 int add_mime_type(struct settings *, char *);
598 int add_cookie_wl(struct settings *, char *);
599 int add_js_wl(struct settings *, char *);
600 int add_kb(struct settings *, char *);
601 void button_set_stockid(GtkWidget *, char *);
602 GtkWidget * create_button(char *, char *, int);
604 char *get_browser_mode(struct settings *);
605 char *get_cookie_policy(struct settings *);
606 char *get_download_dir(struct settings *);
607 char *get_default_script(struct settings *);
608 char *get_runtime_dir(struct settings *);
609 char *get_tab_style(struct settings *);
610 char *get_work_dir(struct settings *);
611 void startpage_add(const char *, ...);
613 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
614 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
615 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
616 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
617 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
619 void recalc_tabs(void);
620 void recolor_compact_tabs(void);
621 void set_current_tab(int page_num);
622 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
623 void marks_clear(struct tab *t);
625 struct special {
626 int (*set)(struct settings *, char *);
627 char *(*get)(struct settings *);
628 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
631 struct special s_browser_mode = {
632 set_browser_mode,
633 get_browser_mode,
634 NULL
637 struct special s_cookie = {
638 set_cookie_policy,
639 get_cookie_policy,
640 NULL
643 struct special s_alias = {
644 add_alias,
645 NULL,
646 walk_alias
649 struct special s_mime = {
650 add_mime_type,
651 NULL,
652 walk_mime_type
655 struct special s_js = {
656 add_js_wl,
657 NULL,
658 walk_js_wl
661 struct special s_kb = {
662 add_kb,
663 NULL,
664 walk_kb
667 struct special s_cookie_wl = {
668 add_cookie_wl,
669 NULL,
670 walk_cookie_wl
673 struct special s_default_script = {
674 set_default_script,
675 get_default_script,
676 NULL
679 struct special s_download_dir = {
680 set_download_dir,
681 get_download_dir,
682 NULL
685 struct special s_work_dir = {
686 set_work_dir,
687 get_work_dir,
688 NULL
691 struct special s_tab_style = {
692 set_tab_style,
693 get_tab_style,
694 NULL
697 struct settings {
698 char *name;
699 int type;
700 #define XT_S_INVALID (0)
701 #define XT_S_INT (1)
702 #define XT_S_STR (2)
703 #define XT_S_FLOAT (3)
704 uint32_t flags;
705 #define XT_SF_RESTART (1<<0)
706 #define XT_SF_RUNTIME (1<<1)
707 int *ival;
708 char **sval;
709 struct special *s;
710 gfloat *fval;
711 } rs[] = {
712 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
713 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
714 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
715 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
716 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
717 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
718 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
719 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
720 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
721 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
722 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
723 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
724 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
725 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
726 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
727 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
728 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
729 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
730 { "home", XT_S_STR, 0, NULL, &home, NULL },
731 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
732 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
733 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
734 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
735 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
736 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
737 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
738 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
739 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
740 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
741 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
742 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
743 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
744 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
745 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
746 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
747 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
748 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
749 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
750 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
751 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
752 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
753 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
754 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
755 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
756 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
758 /* font settings */
759 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
760 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
761 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
762 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
764 /* runtime settings */
765 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
766 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
767 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
768 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
769 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
772 int about(struct tab *, struct karg *);
773 int blank(struct tab *, struct karg *);
774 int ca_cmd(struct tab *, struct karg *);
775 int cookie_show_wl(struct tab *, struct karg *);
776 int js_show_wl(struct tab *, struct karg *);
777 int help(struct tab *, struct karg *);
778 int set(struct tab *, struct karg *);
779 int stats(struct tab *, struct karg *);
780 int marco(struct tab *, struct karg *);
781 int startpage(struct tab *, struct karg *);
782 const char * marco_message(int *);
783 int xtp_page_cl(struct tab *, struct karg *);
784 int xtp_page_dl(struct tab *, struct karg *);
785 int xtp_page_fl(struct tab *, struct karg *);
786 int xtp_page_hl(struct tab *, struct karg *);
787 void xt_icon_from_file(struct tab *, char *);
788 const gchar *get_uri(struct tab *);
789 const gchar *get_title(struct tab *, bool);
791 #define XT_URI_ABOUT ("about:")
792 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
793 #define XT_URI_ABOUT_ABOUT ("about")
794 #define XT_URI_ABOUT_BLANK ("blank")
795 #define XT_URI_ABOUT_CERTS ("certs")
796 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
797 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
798 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
799 #define XT_URI_ABOUT_FAVORITES ("favorites")
800 #define XT_URI_ABOUT_HELP ("help")
801 #define XT_URI_ABOUT_HISTORY ("history")
802 #define XT_URI_ABOUT_JSWL ("jswl")
803 #define XT_URI_ABOUT_SET ("set")
804 #define XT_URI_ABOUT_STATS ("stats")
805 #define XT_URI_ABOUT_MARCO ("marco")
806 #define XT_URI_ABOUT_STARTPAGE ("startpage")
808 struct about_type {
809 char *name;
810 int (*func)(struct tab *, struct karg *);
811 } about_list[] = {
812 { XT_URI_ABOUT_ABOUT, about },
813 { XT_URI_ABOUT_BLANK, blank },
814 { XT_URI_ABOUT_CERTS, ca_cmd },
815 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
816 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
817 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
818 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
819 { XT_URI_ABOUT_HELP, help },
820 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
821 { XT_URI_ABOUT_JSWL, js_show_wl },
822 { XT_URI_ABOUT_SET, set },
823 { XT_URI_ABOUT_STATS, stats },
824 { XT_URI_ABOUT_MARCO, marco },
825 { XT_URI_ABOUT_STARTPAGE, startpage },
828 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
829 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
830 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
831 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
832 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
833 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
834 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
836 /* globals */
837 extern char *__progname;
838 char **start_argv;
839 struct passwd *pwd;
840 GtkWidget *main_window;
841 GtkNotebook *notebook;
842 GtkWidget *tab_bar;
843 GtkWidget *arrow, *abtn;
844 struct tab_list tabs;
845 struct history_list hl;
846 struct download_list downloads;
847 struct domain_list c_wl;
848 struct domain_list js_wl;
849 struct undo_tailq undos;
850 struct keybinding_list kbl;
851 struct sp_list spl;
852 int undo_count;
853 int updating_dl_tabs = 0;
854 int updating_hl_tabs = 0;
855 int updating_cl_tabs = 0;
856 int updating_fl_tabs = 0;
857 char *global_search;
858 uint64_t blocked_cookies = 0;
859 char named_session[PATH_MAX];
860 int icon_size_map(int);
862 GtkListStore *completion_model;
863 void completion_add(struct tab *);
864 void completion_add_uri(const gchar *);
865 GtkListStore *buffers_store;
866 void xxx_dir(char *);
868 /* marks and quickmarks array storage.
869 * first a-z, then A-Z, then 0-9 */
870 char
871 indextomark(int i)
873 if (i < 0)
874 return 0;
876 if (i >= 0 && i <= 'z' - 'a')
877 return 'a' + i;
879 i -= 'z' - 'a' + 1;
880 if (i >= 0 && i <= 'Z' - 'A')
881 return 'A' + i;
883 i -= 'Z' - 'A' + 1;
884 if (i >= 10)
885 return 0;
887 return i + '0';
891 marktoindex(char m)
893 int ret = 0;
895 if (m >= 'a' && m <= 'z')
896 return ret + m - 'a';
898 ret += 'z' - 'a' + 1;
899 if (m >= 'A' && m <= 'Z')
900 return ret + m - 'A';
902 ret += 'Z' - 'A' + 1;
903 if (m >= '0' && m <= '9')
904 return ret + m - '0';
906 return -1;
910 void
911 sigchild(int sig)
913 int saved_errno, status;
914 pid_t pid;
916 saved_errno = errno;
918 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
919 if (pid == -1) {
920 if (errno == EINTR)
921 continue;
922 if (errno != ECHILD) {
924 clog_warn("sigchild: waitpid:");
927 break;
930 if (WIFEXITED(status)) {
931 if (WEXITSTATUS(status) != 0) {
933 clog_warnx("sigchild: child exit status: %d",
934 WEXITSTATUS(status));
937 } else {
939 clog_warnx("sigchild: child is terminated abnormally");
944 errno = saved_errno;
948 is_g_object_setting(GObject *o, char *str)
950 guint n_props = 0, i;
951 GParamSpec **proplist;
953 if (! G_IS_OBJECT(o))
954 return (0);
956 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
957 &n_props);
959 for (i=0; i < n_props; i++) {
960 if (! strcmp(proplist[i]->name, str))
961 return (1);
963 return (0);
966 gchar *
967 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
969 gchar *r;
971 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
972 "<head>\n"
973 "<title>%s</title>\n"
974 "%s"
975 "%s"
976 "</head>\n"
977 "<body>\n"
978 "<h1>%s</h1>\n"
979 "%s\n</body>\n"
980 "</html>",
981 title,
982 addstyles ? XT_PAGE_STYLE : "",
983 head,
984 title,
985 body);
987 return r;
991 * Display a web page from a HTML string in memory, rather than from a URL
993 void
994 load_webkit_string(struct tab *t, const char *str, gchar *title)
996 char file[PATH_MAX];
997 int i;
999 /* we set this to indicate we want to manually do navaction */
1000 if (t->bfl)
1001 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1003 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1004 if (title) {
1005 /* set t->xtp_meaning */
1006 for (i = 0; i < LENGTH(about_list); i++)
1007 if (!strcmp(title, about_list[i].name)) {
1008 t->xtp_meaning = i;
1009 break;
1012 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1013 #if GTK_CHECK_VERSION(2, 20, 0)
1014 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1015 gtk_widget_hide(t->spinner);
1016 #endif
1017 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1018 xt_icon_from_file(t, file);
1022 struct tab *
1023 get_current_tab(void)
1025 struct tab *t;
1027 TAILQ_FOREACH(t, &tabs, entry) {
1028 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1029 return (t);
1032 warnx("%s: no current tab", __func__);
1034 return (NULL);
1037 void
1038 set_status(struct tab *t, gchar *s, int status)
1040 gchar *type = NULL;
1042 if (s == NULL)
1043 return;
1045 switch (status) {
1046 case XT_STATUS_LOADING:
1047 type = g_strdup_printf("Loading: %s", s);
1048 s = type;
1049 break;
1050 case XT_STATUS_LINK:
1051 type = g_strdup_printf("Link: %s", s);
1052 if (!t->status)
1053 t->status = g_strdup(gtk_entry_get_text(
1054 GTK_ENTRY(t->sbe.statusbar)));
1055 s = type;
1056 break;
1057 case XT_STATUS_URI:
1058 type = g_strdup_printf("%s", s);
1059 if (!t->status) {
1060 t->status = g_strdup(type);
1062 s = type;
1063 if (!t->status)
1064 t->status = g_strdup(s);
1065 break;
1066 case XT_STATUS_NOTHING:
1067 /* FALL THROUGH */
1068 default:
1069 break;
1071 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1072 if (type)
1073 g_free(type);
1076 void
1077 hide_cmd(struct tab *t)
1079 gtk_widget_hide(t->cmd);
1082 void
1083 show_cmd(struct tab *t)
1085 gtk_widget_hide(t->oops);
1086 gtk_widget_show(t->cmd);
1089 void
1090 hide_buffers(struct tab *t)
1092 gtk_widget_hide(t->buffers);
1093 gtk_list_store_clear(buffers_store);
1096 enum {
1097 COL_ID = 0,
1098 COL_TITLE,
1099 NUM_COLS
1103 sort_tabs_by_page_num(struct tab ***stabs)
1105 int num_tabs = 0;
1106 struct tab *t;
1108 num_tabs = gtk_notebook_get_n_pages(notebook);
1110 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1112 TAILQ_FOREACH(t, &tabs, entry)
1113 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1115 return (num_tabs);
1118 void
1119 buffers_make_list(void)
1121 int i, num_tabs;
1122 const gchar *title = NULL;
1123 GtkTreeIter iter;
1124 struct tab **stabs = NULL;
1126 num_tabs = sort_tabs_by_page_num(&stabs);
1128 for (i = 0; i < num_tabs; i++)
1129 if (stabs[i]) {
1130 gtk_list_store_append(buffers_store, &iter);
1131 title = get_title(stabs[i], FALSE);
1132 gtk_list_store_set(buffers_store, &iter,
1133 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1134 * rather than 0. */
1135 COL_TITLE, title,
1136 -1);
1139 g_free(stabs);
1142 void
1143 show_buffers(struct tab *t)
1145 buffers_make_list();
1146 gtk_widget_show(t->buffers);
1147 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1150 void
1151 toggle_buffers(struct tab *t)
1153 if (gtk_widget_get_visible(t->buffers))
1154 hide_buffers(t);
1155 else
1156 show_buffers(t);
1160 buffers(struct tab *t, struct karg *args)
1162 show_buffers(t);
1164 return (0);
1167 void
1168 hide_oops(struct tab *t)
1170 gtk_widget_hide(t->oops);
1173 void
1174 show_oops(struct tab *at, const char *fmt, ...)
1176 va_list ap;
1177 char *msg = NULL;
1178 struct tab *t = NULL;
1180 if (fmt == NULL)
1181 return;
1183 if (at == NULL) {
1184 if ((t = get_current_tab()) == NULL)
1185 return;
1186 } else
1187 t = at;
1189 va_start(ap, fmt);
1190 if (vasprintf(&msg, fmt, ap) == -1)
1191 errx(1, "show_oops failed");
1192 va_end(ap);
1194 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1195 gtk_widget_hide(t->cmd);
1196 gtk_widget_show(t->oops);
1198 if (msg)
1199 free(msg);
1202 char *
1203 get_as_string(struct settings *s)
1205 char *r = NULL;
1207 if (s == NULL)
1208 return (NULL);
1210 if (s->s) {
1211 if (s->s->get)
1212 r = s->s->get(s);
1213 else
1214 warnx("get_as_string skip %s\n", s->name);
1215 } else if (s->type == XT_S_INT)
1216 r = g_strdup_printf("%d", *s->ival);
1217 else if (s->type == XT_S_STR)
1218 r = g_strdup(*s->sval);
1219 else if (s->type == XT_S_FLOAT)
1220 r = g_strdup_printf("%f", *s->fval);
1221 else
1222 r = g_strdup_printf("INVALID TYPE");
1224 return (r);
1227 void
1228 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1230 int i;
1231 char *s;
1233 for (i = 0; i < LENGTH(rs); i++) {
1234 if (rs[i].s && rs[i].s->walk)
1235 rs[i].s->walk(&rs[i], cb, cb_args);
1236 else {
1237 s = get_as_string(&rs[i]);
1238 cb(&rs[i], s, cb_args);
1239 g_free(s);
1245 set_browser_mode(struct settings *s, char *val)
1247 if (!strcmp(val, "whitelist")) {
1248 browser_mode = XT_BM_WHITELIST;
1249 allow_volatile_cookies = 0;
1250 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1251 cookies_enabled = 1;
1252 enable_cookie_whitelist = 1;
1253 read_only_cookies = 0;
1254 save_rejected_cookies = 0;
1255 session_timeout = 3600;
1256 enable_scripts = 0;
1257 enable_js_whitelist = 1;
1258 enable_localstorage = 0;
1259 } else if (!strcmp(val, "normal")) {
1260 browser_mode = XT_BM_NORMAL;
1261 allow_volatile_cookies = 0;
1262 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1263 cookies_enabled = 1;
1264 enable_cookie_whitelist = 0;
1265 read_only_cookies = 0;
1266 save_rejected_cookies = 0;
1267 session_timeout = 3600;
1268 enable_scripts = 1;
1269 enable_js_whitelist = 0;
1270 enable_localstorage = 1;
1271 } else if (!strcmp(val, "kiosk")) {
1272 browser_mode = XT_BM_KIOSK;
1273 allow_volatile_cookies = 0;
1274 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1275 cookies_enabled = 1;
1276 enable_cookie_whitelist = 0;
1277 read_only_cookies = 0;
1278 save_rejected_cookies = 0;
1279 session_timeout = 3600;
1280 enable_scripts = 1;
1281 enable_js_whitelist = 0;
1282 enable_localstorage = 1;
1283 show_tabs = 0;
1284 tabless = 1;
1285 } else
1286 return (1);
1288 return (0);
1291 char *
1292 get_browser_mode(struct settings *s)
1294 char *r = NULL;
1296 if (browser_mode == XT_BM_WHITELIST)
1297 r = g_strdup("whitelist");
1298 else if (browser_mode == XT_BM_NORMAL)
1299 r = g_strdup("normal");
1300 else if (browser_mode == XT_BM_KIOSK)
1301 r = g_strdup("kiosk");
1302 else
1303 return (NULL);
1305 return (r);
1309 set_cookie_policy(struct settings *s, char *val)
1311 if (!strcmp(val, "no3rdparty"))
1312 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1313 else if (!strcmp(val, "accept"))
1314 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1315 else if (!strcmp(val, "reject"))
1316 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1317 else
1318 return (1);
1320 return (0);
1323 char *
1324 get_cookie_policy(struct settings *s)
1326 char *r = NULL;
1328 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1329 r = g_strdup("no3rdparty");
1330 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1331 r = g_strdup("accept");
1332 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1333 r = g_strdup("reject");
1334 else
1335 return (NULL);
1337 return (r);
1340 char *
1341 get_default_script(struct settings *s)
1343 if (default_script[0] == '\0')
1344 return (0);
1345 return (g_strdup(default_script));
1349 set_default_script(struct settings *s, char *val)
1351 if (val[0] == '~')
1352 snprintf(default_script, sizeof default_script, "%s/%s",
1353 pwd->pw_dir, &val[1]);
1354 else
1355 strlcpy(default_script, val, sizeof default_script);
1357 return (0);
1360 char *
1361 get_download_dir(struct settings *s)
1363 if (download_dir[0] == '\0')
1364 return (0);
1365 return (g_strdup(download_dir));
1369 set_download_dir(struct settings *s, char *val)
1371 if (val[0] == '~')
1372 snprintf(download_dir, sizeof download_dir, "%s/%s",
1373 pwd->pw_dir, &val[1]);
1374 else
1375 strlcpy(download_dir, val, sizeof download_dir);
1377 return (0);
1381 * Session IDs.
1382 * We use these to prevent people putting xxxt:// URLs on
1383 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1385 #define XT_XTP_SES_KEY_SZ 8
1386 #define XT_XTP_SES_KEY_HEX_FMT \
1387 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1388 char *dl_session_key; /* downloads */
1389 char *hl_session_key; /* history list */
1390 char *cl_session_key; /* cookie list */
1391 char *fl_session_key; /* favorites list */
1393 char work_dir[PATH_MAX];
1394 char certs_dir[PATH_MAX];
1395 char cache_dir[PATH_MAX];
1396 char sessions_dir[PATH_MAX];
1397 char cookie_file[PATH_MAX];
1398 SoupURI *proxy_uri = NULL;
1399 SoupSession *session;
1400 SoupCookieJar *s_cookiejar;
1401 SoupCookieJar *p_cookiejar;
1402 char rc_fname[PATH_MAX];
1404 struct mime_type_list mtl;
1405 struct alias_list aliases;
1407 /* protos */
1408 struct tab *create_new_tab(char *, struct undo *, int, int);
1409 void delete_tab(struct tab *);
1410 void setzoom_webkit(struct tab *, int);
1411 int run_script(struct tab *, char *);
1412 int download_rb_cmp(struct download *, struct download *);
1413 gboolean cmd_execute(struct tab *t, char *str);
1416 history_rb_cmp(struct history *h1, struct history *h2)
1418 return (strcmp(h1->uri, h2->uri));
1420 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1423 domain_rb_cmp(struct domain *d1, struct domain *d2)
1425 return (strcmp(d1->d, d2->d));
1427 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1429 char *
1430 get_work_dir(struct settings *s)
1432 if (work_dir[0] == '\0')
1433 return (0);
1434 return (g_strdup(work_dir));
1438 set_work_dir(struct settings *s, char *val)
1440 if (val[0] == '~')
1441 snprintf(work_dir, sizeof work_dir, "%s/%s",
1442 pwd->pw_dir, &val[1]);
1443 else
1444 strlcpy(work_dir, val, sizeof work_dir);
1446 return (0);
1449 char *
1450 get_tab_style(struct settings *s)
1452 if (tab_style == XT_TABS_NORMAL)
1453 return (g_strdup("normal"));
1454 else
1455 return (g_strdup("compact"));
1459 set_tab_style(struct settings *s, char *val)
1461 if (!strcmp(val, "normal"))
1462 tab_style = XT_TABS_NORMAL;
1463 else if (!strcmp(val, "compact"))
1464 tab_style = XT_TABS_COMPACT;
1465 else
1466 return (1);
1468 return (0);
1472 * generate a session key to secure xtp commands.
1473 * pass in a ptr to the key in question and it will
1474 * be modified in place.
1476 void
1477 generate_xtp_session_key(char **key)
1479 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1481 /* free old key */
1482 if (*key)
1483 g_free(*key);
1485 /* make a new one */
1486 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1487 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1488 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1489 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1491 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1495 * validate a xtp session key.
1496 * return 1 if OK
1499 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1501 if (strcmp(trusted, untrusted) != 0) {
1502 show_oops(t, "%s: xtp session key mismatch possible spoof",
1503 __func__);
1504 return (0);
1507 return (1);
1511 download_rb_cmp(struct download *e1, struct download *e2)
1513 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1515 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1517 struct valid_url_types {
1518 char *type;
1519 } vut[] = {
1520 { "http://" },
1521 { "https://" },
1522 { "ftp://" },
1523 { "file://" },
1524 { XT_XTP_STR },
1528 valid_url_type(char *url)
1530 int i;
1532 for (i = 0; i < LENGTH(vut); i++)
1533 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1534 return (0);
1536 return (1);
1539 void
1540 print_cookie(char *msg, SoupCookie *c)
1542 if (c == NULL)
1543 return;
1545 if (msg)
1546 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1547 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1548 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1549 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1550 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1551 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1552 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1553 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1554 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1555 DNPRINTF(XT_D_COOKIE, "====================================\n");
1558 void
1559 walk_alias(struct settings *s,
1560 void (*cb)(struct settings *, char *, void *), void *cb_args)
1562 struct alias *a;
1563 char *str;
1565 if (s == NULL || cb == NULL) {
1566 show_oops(NULL, "walk_alias invalid parameters");
1567 return;
1570 TAILQ_FOREACH(a, &aliases, entry) {
1571 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1572 cb(s, str, cb_args);
1573 g_free(str);
1577 char *
1578 match_alias(char *url_in)
1580 struct alias *a;
1581 char *arg;
1582 char *url_out = NULL, *search, *enc_arg;
1584 search = g_strdup(url_in);
1585 arg = search;
1586 if (strsep(&arg, " \t") == NULL) {
1587 show_oops(NULL, "match_alias: NULL URL");
1588 goto done;
1591 TAILQ_FOREACH(a, &aliases, entry) {
1592 if (!strcmp(search, a->a_name))
1593 break;
1596 if (a != NULL) {
1597 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1598 a->a_name);
1599 if (arg != NULL) {
1600 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1601 url_out = g_strdup_printf(a->a_uri, enc_arg);
1602 g_free(enc_arg);
1603 } else
1604 url_out = g_strdup_printf(a->a_uri, "");
1606 done:
1607 g_free(search);
1608 return (url_out);
1611 char *
1612 guess_url_type(char *url_in)
1614 struct stat sb;
1615 char *url_out = NULL, *enc_search = NULL;
1617 url_out = match_alias(url_in);
1618 if (url_out != NULL)
1619 return (url_out);
1621 if (guess_search) {
1623 * If there is no dot nor slash in the string and it isn't a
1624 * path to a local file and doesn't resolves to an IP, assume
1625 * that the user wants to search for the string.
1628 if (strchr(url_in, '.') == NULL &&
1629 strchr(url_in, '/') == NULL &&
1630 stat(url_in, &sb) != 0 &&
1631 gethostbyname(url_in) == NULL) {
1633 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1634 url_out = g_strdup_printf(search_string, enc_search);
1635 g_free(enc_search);
1636 return (url_out);
1640 /* XXX not sure about this heuristic */
1641 if (stat(url_in, &sb) == 0)
1642 url_out = g_strdup_printf("file://%s", url_in);
1643 else
1644 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1646 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1648 return (url_out);
1651 void
1652 load_uri(struct tab *t, gchar *uri)
1654 struct karg args;
1655 gchar *newuri = NULL;
1656 int i;
1658 if (uri == NULL)
1659 return;
1661 /* Strip leading spaces. */
1662 while (*uri && isspace(*uri))
1663 uri++;
1665 if (strlen(uri) == 0) {
1666 blank(t, NULL);
1667 return;
1670 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1672 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1673 for (i = 0; i < LENGTH(about_list); i++)
1674 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1675 bzero(&args, sizeof args);
1676 about_list[i].func(t, &args);
1677 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1678 FALSE);
1679 return;
1681 show_oops(t, "invalid about page");
1682 return;
1685 if (valid_url_type(uri)) {
1686 newuri = guess_url_type(uri);
1687 uri = newuri;
1690 set_status(t, (char *)uri, XT_STATUS_LOADING);
1691 marks_clear(t);
1692 webkit_web_view_load_uri(t->wv, uri);
1694 if (newuri)
1695 g_free(newuri);
1698 const gchar *
1699 get_uri(struct tab *t)
1701 const gchar *uri = NULL;
1703 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1704 return t->tmp_uri;
1705 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1706 uri = webkit_web_view_get_uri(t->wv);
1707 } else {
1708 /* use tmp_uri to make sure it is g_freed */
1709 if (t->tmp_uri)
1710 g_free(t->tmp_uri);
1711 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1712 about_list[t->xtp_meaning].name);
1713 uri = t->tmp_uri;
1715 return uri;
1718 const gchar *
1719 get_title(struct tab *t, bool window)
1721 const gchar *set = NULL, *title = NULL;
1722 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1724 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1725 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1726 goto notitle;
1728 title = webkit_web_view_get_title(t->wv);
1729 if ((set = title ? title : get_uri(t)))
1730 return set;
1732 notitle:
1733 set = window ? XT_NAME : "(untitled)";
1735 return set;
1739 add_alias(struct settings *s, char *line)
1741 char *l, *alias;
1742 struct alias *a = NULL;
1744 if (s == NULL || line == NULL) {
1745 show_oops(NULL, "add_alias invalid parameters");
1746 return (1);
1749 l = line;
1750 a = g_malloc(sizeof(*a));
1752 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1753 show_oops(NULL, "add_alias: incomplete alias definition");
1754 goto bad;
1756 if (strlen(alias) == 0 || strlen(l) == 0) {
1757 show_oops(NULL, "add_alias: invalid alias definition");
1758 goto bad;
1761 a->a_name = g_strdup(alias);
1762 a->a_uri = g_strdup(l);
1764 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1766 TAILQ_INSERT_TAIL(&aliases, a, entry);
1768 return (0);
1769 bad:
1770 if (a)
1771 g_free(a);
1772 return (1);
1776 add_mime_type(struct settings *s, char *line)
1778 char *mime_type;
1779 char *l;
1780 struct mime_type *m = NULL;
1781 int downloadfirst = 0;
1783 /* XXX this could be smarter */
1785 if (line == NULL || strlen(line) == 0) {
1786 show_oops(NULL, "add_mime_type invalid parameters");
1787 return (1);
1790 l = line;
1791 if (*l == '@') {
1792 downloadfirst = 1;
1793 l++;
1795 m = g_malloc(sizeof(*m));
1797 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1798 show_oops(NULL, "add_mime_type: invalid mime_type");
1799 goto bad;
1801 if (mime_type[strlen(mime_type) - 1] == '*') {
1802 mime_type[strlen(mime_type) - 1] = '\0';
1803 m->mt_default = 1;
1804 } else
1805 m->mt_default = 0;
1807 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1808 show_oops(NULL, "add_mime_type: invalid mime_type");
1809 goto bad;
1812 m->mt_type = g_strdup(mime_type);
1813 m->mt_action = g_strdup(l);
1814 m->mt_download = downloadfirst;
1816 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1817 m->mt_type, m->mt_action, m->mt_default);
1819 TAILQ_INSERT_TAIL(&mtl, m, entry);
1821 return (0);
1822 bad:
1823 if (m)
1824 g_free(m);
1825 return (1);
1828 struct mime_type *
1829 find_mime_type(char *mime_type)
1831 struct mime_type *m, *def = NULL, *rv = NULL;
1833 TAILQ_FOREACH(m, &mtl, entry) {
1834 if (m->mt_default &&
1835 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1836 def = m;
1838 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1839 rv = m;
1840 break;
1844 if (rv == NULL)
1845 rv = def;
1847 return (rv);
1850 void
1851 walk_mime_type(struct settings *s,
1852 void (*cb)(struct settings *, char *, void *), void *cb_args)
1854 struct mime_type *m;
1855 char *str;
1857 if (s == NULL || cb == NULL) {
1858 show_oops(NULL, "walk_mime_type invalid parameters");
1859 return;
1862 TAILQ_FOREACH(m, &mtl, entry) {
1863 str = g_strdup_printf("%s%s --> %s",
1864 m->mt_type,
1865 m->mt_default ? "*" : "",
1866 m->mt_action);
1867 cb(s, str, cb_args);
1868 g_free(str);
1872 void
1873 wl_add(char *str, struct domain_list *wl, int handy)
1875 struct domain *d;
1876 int add_dot = 0;
1878 if (str == NULL || wl == NULL || strlen(str) < 2)
1879 return;
1881 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1883 /* treat *.moo.com the same as .moo.com */
1884 if (str[0] == '*' && str[1] == '.')
1885 str = &str[1];
1886 else if (str[0] == '.')
1887 str = &str[0];
1888 else
1889 add_dot = 1;
1891 d = g_malloc(sizeof *d);
1892 if (add_dot)
1893 d->d = g_strdup_printf(".%s", str);
1894 else
1895 d->d = g_strdup(str);
1896 d->handy = handy;
1898 if (RB_INSERT(domain_list, wl, d))
1899 goto unwind;
1901 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1902 return;
1903 unwind:
1904 if (d) {
1905 if (d->d)
1906 g_free(d->d);
1907 g_free(d);
1912 add_cookie_wl(struct settings *s, char *entry)
1914 wl_add(entry, &c_wl, 1);
1915 return (0);
1918 void
1919 walk_cookie_wl(struct settings *s,
1920 void (*cb)(struct settings *, char *, void *), void *cb_args)
1922 struct domain *d;
1924 if (s == NULL || cb == NULL) {
1925 show_oops(NULL, "walk_cookie_wl invalid parameters");
1926 return;
1929 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1930 cb(s, d->d, cb_args);
1933 void
1934 walk_js_wl(struct settings *s,
1935 void (*cb)(struct settings *, char *, void *), void *cb_args)
1937 struct domain *d;
1939 if (s == NULL || cb == NULL) {
1940 show_oops(NULL, "walk_js_wl invalid parameters");
1941 return;
1944 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1945 cb(s, d->d, cb_args);
1949 add_js_wl(struct settings *s, char *entry)
1951 wl_add(entry, &js_wl, 1 /* persistent */);
1952 return (0);
1955 struct domain *
1956 wl_find(const gchar *search, struct domain_list *wl)
1958 int i;
1959 struct domain *d = NULL, dfind;
1960 gchar *s = NULL;
1962 if (search == NULL || wl == NULL)
1963 return (NULL);
1964 if (strlen(search) < 2)
1965 return (NULL);
1967 if (search[0] != '.')
1968 s = g_strdup_printf(".%s", search);
1969 else
1970 s = g_strdup(search);
1972 for (i = strlen(s) - 1; i >= 0; i--) {
1973 if (s[i] == '.') {
1974 dfind.d = &s[i];
1975 d = RB_FIND(domain_list, wl, &dfind);
1976 if (d)
1977 goto done;
1981 done:
1982 if (s)
1983 g_free(s);
1985 return (d);
1988 struct domain *
1989 wl_find_uri(const gchar *s, struct domain_list *wl)
1991 int i;
1992 char *ss;
1993 struct domain *r;
1995 if (s == NULL || wl == NULL)
1996 return (NULL);
1998 if (!strncmp(s, "http://", strlen("http://")))
1999 s = &s[strlen("http://")];
2000 else if (!strncmp(s, "https://", strlen("https://")))
2001 s = &s[strlen("https://")];
2003 if (strlen(s) < 2)
2004 return (NULL);
2006 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2007 /* chop string at first slash */
2008 if (s[i] == '/' || s[i] == '\0') {
2009 ss = g_strdup(s);
2010 ss[i] = '\0';
2011 r = wl_find(ss, wl);
2012 g_free(ss);
2013 return (r);
2016 return (NULL);
2020 settings_add(char *var, char *val)
2022 int i, rv, *p;
2023 gfloat *f;
2024 char **s;
2026 /* get settings */
2027 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2028 if (strcmp(var, rs[i].name))
2029 continue;
2031 if (rs[i].s) {
2032 if (rs[i].s->set(&rs[i], val))
2033 errx(1, "invalid value for %s: %s", var, val);
2034 rv = 1;
2035 break;
2036 } else
2037 switch (rs[i].type) {
2038 case XT_S_INT:
2039 p = rs[i].ival;
2040 *p = atoi(val);
2041 rv = 1;
2042 break;
2043 case XT_S_STR:
2044 s = rs[i].sval;
2045 if (s == NULL)
2046 errx(1, "invalid sval for %s",
2047 rs[i].name);
2048 if (*s)
2049 g_free(*s);
2050 *s = g_strdup(val);
2051 rv = 1;
2052 break;
2053 case XT_S_FLOAT:
2054 f = rs[i].fval;
2055 *f = atof(val);
2056 rv = 1;
2057 break;
2058 case XT_S_INVALID:
2059 default:
2060 errx(1, "invalid type for %s", var);
2062 break;
2064 return (rv);
2067 #define WS "\n= \t"
2068 void
2069 config_parse(char *filename, int runtime)
2071 FILE *config, *f;
2072 char *line, *cp, *var, *val;
2073 size_t len, lineno = 0;
2074 int handled;
2075 char file[PATH_MAX];
2076 struct stat sb;
2078 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2080 if (filename == NULL)
2081 return;
2083 if (runtime && runtime_settings[0] != '\0') {
2084 snprintf(file, sizeof file, "%s/%s",
2085 work_dir, runtime_settings);
2086 if (stat(file, &sb)) {
2087 warnx("runtime file doesn't exist, creating it");
2088 if ((f = fopen(file, "w")) == NULL)
2089 err(1, "runtime");
2090 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2091 fclose(f);
2093 } else
2094 strlcpy(file, filename, sizeof file);
2096 if ((config = fopen(file, "r")) == NULL) {
2097 warn("config_parse: cannot open %s", filename);
2098 return;
2101 for (;;) {
2102 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2103 if (feof(config) || ferror(config))
2104 break;
2106 cp = line;
2107 cp += (long)strspn(cp, WS);
2108 if (cp[0] == '\0') {
2109 /* empty line */
2110 free(line);
2111 continue;
2114 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2115 startpage_add("invalid configuration file entry: %s",
2116 line);
2118 cp += (long)strspn(cp, WS);
2120 if ((val = strsep(&cp, "\0")) == NULL)
2121 break;
2123 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2124 handled = settings_add(var, val);
2125 if (handled == 0)
2126 startpage_add("invalid configuration file entry: %s=%s",
2127 var, val);
2129 free(line);
2132 fclose(config);
2135 char *
2136 js_ref_to_string(JSContextRef context, JSValueRef ref)
2138 char *s = NULL;
2139 size_t l;
2140 JSStringRef jsref;
2142 jsref = JSValueToStringCopy(context, ref, NULL);
2143 if (jsref == NULL)
2144 return (NULL);
2146 l = JSStringGetMaximumUTF8CStringSize(jsref);
2147 s = g_malloc(l);
2148 if (s)
2149 JSStringGetUTF8CString(jsref, s, l);
2150 JSStringRelease(jsref);
2152 return (s);
2155 void
2156 disable_hints(struct tab *t)
2158 bzero(t->hint_buf, sizeof t->hint_buf);
2159 bzero(t->hint_num, sizeof t->hint_num);
2160 run_script(t, "vimprobable_clear()");
2161 t->hints_on = 0;
2162 t->hint_mode = XT_HINT_NONE;
2165 void
2166 enable_hints(struct tab *t)
2168 bzero(t->hint_buf, sizeof t->hint_buf);
2169 run_script(t, "vimprobable_show_hints()");
2170 t->hints_on = 1;
2171 t->hint_mode = XT_HINT_NONE;
2174 #define XT_JS_OPEN ("open;")
2175 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2176 #define XT_JS_FIRE ("fire;")
2177 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2178 #define XT_JS_FOUND ("found;")
2179 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2182 run_script(struct tab *t, char *s)
2184 JSGlobalContextRef ctx;
2185 WebKitWebFrame *frame;
2186 JSStringRef str;
2187 JSValueRef val, exception;
2188 char *es, buf[128];
2190 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2191 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2193 frame = webkit_web_view_get_main_frame(t->wv);
2194 ctx = webkit_web_frame_get_global_context(frame);
2196 str = JSStringCreateWithUTF8CString(s);
2197 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2198 NULL, 0, &exception);
2199 JSStringRelease(str);
2201 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2202 if (val == NULL) {
2203 es = js_ref_to_string(ctx, exception);
2204 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2205 g_free(es);
2206 return (1);
2207 } else {
2208 es = js_ref_to_string(ctx, val);
2209 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2211 /* handle return value right here */
2212 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2213 disable_hints(t);
2214 marks_clear(t);
2215 load_uri(t, &es[XT_JS_OPEN_LEN]);
2218 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2219 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2220 &es[XT_JS_FIRE_LEN]);
2221 run_script(t, buf);
2222 disable_hints(t);
2225 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2226 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2227 disable_hints(t);
2230 g_free(es);
2233 return (0);
2237 hint(struct tab *t, struct karg *args)
2240 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2242 if (t->hints_on == 0)
2243 enable_hints(t);
2244 else
2245 disable_hints(t);
2247 return (0);
2250 void
2251 apply_style(struct tab *t)
2253 g_object_set(G_OBJECT(t->settings),
2254 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2258 userstyle(struct tab *t, struct karg *args)
2260 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2262 if (t->styled) {
2263 t->styled = 0;
2264 g_object_set(G_OBJECT(t->settings),
2265 "user-stylesheet-uri", NULL, (char *)NULL);
2266 } else {
2267 t->styled = 1;
2268 apply_style(t);
2270 return (0);
2274 * Doesn't work fully, due to the following bug:
2275 * https://bugs.webkit.org/show_bug.cgi?id=51747
2278 restore_global_history(void)
2280 char file[PATH_MAX];
2281 FILE *f;
2282 struct history *h;
2283 gchar *uri;
2284 gchar *title;
2285 const char delim[3] = {'\\', '\\', '\0'};
2287 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2289 if ((f = fopen(file, "r")) == NULL) {
2290 warnx("%s: fopen", __func__);
2291 return (1);
2294 for (;;) {
2295 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2296 if (feof(f) || ferror(f))
2297 break;
2299 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2300 if (feof(f) || ferror(f)) {
2301 free(uri);
2302 warnx("%s: broken history file\n", __func__);
2303 return (1);
2306 if (uri && strlen(uri) && title && strlen(title)) {
2307 webkit_web_history_item_new_with_data(uri, title);
2308 h = g_malloc(sizeof(struct history));
2309 h->uri = g_strdup(uri);
2310 h->title = g_strdup(title);
2311 RB_INSERT(history_list, &hl, h);
2312 completion_add_uri(h->uri);
2313 } else {
2314 warnx("%s: failed to restore history\n", __func__);
2315 free(uri);
2316 free(title);
2317 return (1);
2320 free(uri);
2321 free(title);
2322 uri = NULL;
2323 title = NULL;
2326 return (0);
2330 save_global_history_to_disk(struct tab *t)
2332 char file[PATH_MAX];
2333 FILE *f;
2334 struct history *h;
2336 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2338 if ((f = fopen(file, "w")) == NULL) {
2339 show_oops(t, "%s: global history file: %s",
2340 __func__, strerror(errno));
2341 return (1);
2344 RB_FOREACH_REVERSE(h, history_list, &hl) {
2345 if (h->uri && h->title)
2346 fprintf(f, "%s\n%s\n", h->uri, h->title);
2349 fclose(f);
2351 return (0);
2355 quit(struct tab *t, struct karg *args)
2357 if (save_global_history)
2358 save_global_history_to_disk(t);
2360 gtk_main_quit();
2362 return (1);
2366 open_tabs(struct tab *t, struct karg *a)
2368 char file[PATH_MAX];
2369 FILE *f = NULL;
2370 char *uri = NULL;
2371 int rv = 1;
2372 struct tab *ti, *tt;
2374 if (a == NULL)
2375 goto done;
2377 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2378 if ((f = fopen(file, "r")) == NULL)
2379 goto done;
2381 ti = TAILQ_LAST(&tabs, tab_list);
2383 for (;;) {
2384 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2385 if (feof(f) || ferror(f))
2386 break;
2388 /* retrieve session name */
2389 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2390 strlcpy(named_session,
2391 &uri[strlen(XT_SAVE_SESSION_ID)],
2392 sizeof named_session);
2393 continue;
2396 if (uri && strlen(uri))
2397 create_new_tab(uri, NULL, 1, -1);
2399 free(uri);
2400 uri = NULL;
2403 /* close open tabs */
2404 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2405 for (;;) {
2406 tt = TAILQ_FIRST(&tabs);
2407 if (tt != ti) {
2408 delete_tab(tt);
2409 continue;
2411 delete_tab(tt);
2412 break;
2414 recalc_tabs();
2417 rv = 0;
2418 done:
2419 if (f)
2420 fclose(f);
2422 return (rv);
2426 restore_saved_tabs(void)
2428 char file[PATH_MAX];
2429 int unlink_file = 0;
2430 struct stat sb;
2431 struct karg a;
2432 int rv = 0;
2434 snprintf(file, sizeof file, "%s/%s",
2435 sessions_dir, XT_RESTART_TABS_FILE);
2436 if (stat(file, &sb) == -1)
2437 a.s = XT_SAVED_TABS_FILE;
2438 else {
2439 unlink_file = 1;
2440 a.s = XT_RESTART_TABS_FILE;
2443 a.i = XT_SES_DONOTHING;
2444 rv = open_tabs(NULL, &a);
2446 if (unlink_file)
2447 unlink(file);
2449 return (rv);
2453 save_tabs(struct tab *t, struct karg *a)
2455 char file[PATH_MAX];
2456 FILE *f;
2457 int num_tabs = 0, i;
2458 struct tab **stabs = NULL;
2460 if (a == NULL)
2461 return (1);
2462 if (a->s == NULL)
2463 snprintf(file, sizeof file, "%s/%s",
2464 sessions_dir, named_session);
2465 else
2466 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2468 if ((f = fopen(file, "w")) == NULL) {
2469 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2470 return (1);
2473 /* save session name */
2474 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2476 /* Save tabs, in the order they are arranged in the notebook. */
2477 num_tabs = sort_tabs_by_page_num(&stabs);
2479 for (i = 0; i < num_tabs; i++)
2480 if (stabs[i] && get_uri(stabs[i]) != NULL)
2481 fprintf(f, "%s\n", get_uri(stabs[i]));
2483 g_free(stabs);
2485 /* try and make sure this gets to disk NOW. XXX Backup first? */
2486 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2487 show_oops(t, "May not have managed to save session: %s",
2488 strerror(errno));
2491 fclose(f);
2493 return (0);
2497 save_tabs_and_quit(struct tab *t, struct karg *args)
2499 struct karg a;
2501 a.s = NULL;
2502 save_tabs(t, &a);
2503 quit(t, NULL);
2505 return (1);
2509 run_page_script(struct tab *t, struct karg *args)
2511 const gchar *uri;
2512 char *tmp, script[PATH_MAX];
2514 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2515 if (tmp[0] == '\0') {
2516 show_oops(t, "no script specified");
2517 return (1);
2520 if ((uri = get_uri(t)) == NULL) {
2521 show_oops(t, "tab is empty, not running script");
2522 return (1);
2525 if (tmp[0] == '~')
2526 snprintf(script, sizeof script, "%s/%s",
2527 pwd->pw_dir, &tmp[1]);
2528 else
2529 strlcpy(script, tmp, sizeof script);
2531 switch (fork()) {
2532 case -1:
2533 show_oops(t, "can't fork to run script");
2534 return (1);
2535 /* NOTREACHED */
2536 case 0:
2537 break;
2538 default:
2539 return (0);
2542 /* child */
2543 execlp(script, script, uri, (void *)NULL);
2545 _exit(0);
2547 /* NOTREACHED */
2549 return (0);
2553 yank_uri(struct tab *t, struct karg *args)
2555 const gchar *uri;
2556 GtkClipboard *clipboard;
2558 if ((uri = get_uri(t)) == NULL)
2559 return (1);
2561 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2562 gtk_clipboard_set_text(clipboard, uri, -1);
2564 return (0);
2568 paste_uri(struct tab *t, struct karg *args)
2570 GtkClipboard *clipboard;
2571 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2572 gint len;
2573 gchar *p = NULL, *uri;
2575 /* try primary clipboard first */
2576 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2577 p = gtk_clipboard_wait_for_text(clipboard);
2579 /* if it failed get whatever text is in cut_buffer0 */
2580 if (p == NULL && xterm_workaround)
2581 if (gdk_property_get(gdk_get_default_root_window(),
2582 atom,
2583 gdk_atom_intern("STRING", FALSE),
2585 1024 * 1024 /* picked out of my butt */,
2586 FALSE,
2587 NULL,
2588 NULL,
2589 &len,
2590 (guchar **)&p)) {
2591 /* yes sir, we need to NUL the string */
2592 p[len] = '\0';
2595 if (p) {
2596 uri = p;
2597 while (*uri && isspace(*uri))
2598 uri++;
2599 if (strlen(uri) == 0) {
2600 show_oops(t, "empty paste buffer");
2601 goto done;
2603 if (guess_search == 0 && valid_url_type(uri)) {
2604 /* we can be clever and paste this in search box */
2605 show_oops(t, "not a valid URL");
2606 goto done;
2609 if (args->i == XT_PASTE_CURRENT_TAB)
2610 load_uri(t, uri);
2611 else if (args->i == XT_PASTE_NEW_TAB)
2612 create_new_tab(uri, NULL, 1, -1);
2615 done:
2616 if (p)
2617 g_free(p);
2619 return (0);
2622 gchar *
2623 find_domain(const gchar *s, int toplevel)
2625 SoupURI *uri;
2626 gchar *ret, *p;
2628 if (s == NULL)
2629 return (NULL);
2631 uri = soup_uri_new(s);
2633 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2634 return (NULL);
2637 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2638 if ((p = strrchr(uri->host, '.')) != NULL) {
2639 while(--p >= uri->host && *p != '.');
2640 p++;
2641 } else
2642 p = uri->host;
2643 } else
2644 p = uri->host;
2646 if (uri->port == 80)
2647 ret = g_strdup_printf(".%s", p);
2648 else
2649 ret = g_strdup_printf(".%s:%d", p, uri->port);
2651 soup_uri_free(uri);
2653 return ret;
2657 toggle_cwl(struct tab *t, struct karg *args)
2659 struct domain *d;
2660 const gchar *uri;
2661 char *dom = NULL;
2662 int es;
2664 if (args == NULL)
2665 return (1);
2667 uri = get_uri(t);
2668 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2670 if (uri == NULL || dom == NULL ||
2671 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2672 show_oops(t, "Can't toggle domain in cookie white list");
2673 goto done;
2675 d = wl_find(dom, &c_wl);
2677 if (d == NULL)
2678 es = 0;
2679 else
2680 es = 1;
2682 if (args->i & XT_WL_TOGGLE)
2683 es = !es;
2684 else if ((args->i & XT_WL_ENABLE) && es != 1)
2685 es = 1;
2686 else if ((args->i & XT_WL_DISABLE) && es != 0)
2687 es = 0;
2689 if (es)
2690 /* enable cookies for domain */
2691 wl_add(dom, &c_wl, 0);
2692 else
2693 /* disable cookies for domain */
2694 RB_REMOVE(domain_list, &c_wl, d);
2696 if (args->i & XT_WL_RELOAD)
2697 webkit_web_view_reload(t->wv);
2699 done:
2700 g_free(dom);
2701 return (0);
2705 toggle_js(struct tab *t, struct karg *args)
2707 int es;
2708 const gchar *uri;
2709 struct domain *d;
2710 char *dom = NULL;
2712 if (args == NULL)
2713 return (1);
2715 g_object_get(G_OBJECT(t->settings),
2716 "enable-scripts", &es, (char *)NULL);
2717 if (args->i & XT_WL_TOGGLE)
2718 es = !es;
2719 else if ((args->i & XT_WL_ENABLE) && es != 1)
2720 es = 1;
2721 else if ((args->i & XT_WL_DISABLE) && es != 0)
2722 es = 0;
2723 else
2724 return (1);
2726 uri = get_uri(t);
2727 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2729 if (uri == NULL || dom == NULL ||
2730 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2731 show_oops(t, "Can't toggle domain in JavaScript white list");
2732 goto done;
2735 if (es) {
2736 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2737 wl_add(dom, &js_wl, 0 /* session */);
2738 } else {
2739 d = wl_find(dom, &js_wl);
2740 if (d)
2741 RB_REMOVE(domain_list, &js_wl, d);
2742 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2744 g_object_set(G_OBJECT(t->settings),
2745 "enable-scripts", es, (char *)NULL);
2746 g_object_set(G_OBJECT(t->settings),
2747 "javascript-can-open-windows-automatically", es, (char *)NULL);
2748 webkit_web_view_set_settings(t->wv, t->settings);
2750 if (args->i & XT_WL_RELOAD)
2751 webkit_web_view_reload(t->wv);
2752 done:
2753 if (dom)
2754 g_free(dom);
2755 return (0);
2758 void
2759 js_toggle_cb(GtkWidget *w, struct tab *t)
2761 struct karg a;
2763 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2764 toggle_cwl(t, &a);
2766 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2767 toggle_js(t, &a);
2771 toggle_src(struct tab *t, struct karg *args)
2773 gboolean mode;
2775 if (t == NULL)
2776 return (0);
2778 mode = webkit_web_view_get_view_source_mode(t->wv);
2779 webkit_web_view_set_view_source_mode(t->wv, !mode);
2780 webkit_web_view_reload(t->wv);
2782 return (0);
2785 void
2786 focus_webview(struct tab *t)
2788 if (t == NULL)
2789 return;
2791 /* only grab focus if we are visible */
2792 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2793 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2797 focus(struct tab *t, struct karg *args)
2799 if (t == NULL || args == NULL)
2800 return (1);
2802 if (show_url == 0)
2803 return (0);
2805 if (args->i == XT_FOCUS_URI)
2806 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2807 else if (args->i == XT_FOCUS_SEARCH)
2808 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2810 return (0);
2814 stats(struct tab *t, struct karg *args)
2816 char *page, *body, *s, line[64 * 1024];
2817 uint64_t line_count = 0;
2818 FILE *r_cookie_f;
2820 if (t == NULL)
2821 show_oops(NULL, "stats invalid parameters");
2823 line[0] = '\0';
2824 if (save_rejected_cookies) {
2825 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2826 for (;;) {
2827 s = fgets(line, sizeof line, r_cookie_f);
2828 if (s == NULL || feof(r_cookie_f) ||
2829 ferror(r_cookie_f))
2830 break;
2831 line_count++;
2833 fclose(r_cookie_f);
2834 snprintf(line, sizeof line,
2835 "<br/>Cookies blocked(*) total: %llu", line_count);
2836 } else
2837 show_oops(t, "Can't open blocked cookies file: %s",
2838 strerror(errno));
2841 body = g_strdup_printf(
2842 "Cookies blocked(*) this session: %llu"
2843 "%s"
2844 "<p><small><b>*</b> results vary based on settings</small></p>",
2845 blocked_cookies,
2846 line);
2848 page = get_html_page("Statistics", body, "", 0);
2849 g_free(body);
2851 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2852 g_free(page);
2854 return (0);
2858 marco(struct tab *t, struct karg *args)
2860 char *page, line[64 * 1024];
2861 int len;
2863 if (t == NULL)
2864 show_oops(NULL, "marco invalid parameters");
2866 line[0] = '\0';
2867 snprintf(line, sizeof line, "%s", marco_message(&len));
2869 page = get_html_page("Marco Sez...", line, "", 0);
2871 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2872 g_free(page);
2874 return (0);
2878 blank(struct tab *t, struct karg *args)
2880 if (t == NULL)
2881 show_oops(NULL, "blank invalid parameters");
2883 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2885 return (0);
2889 about(struct tab *t, struct karg *args)
2891 char *page, *body;
2893 if (t == NULL)
2894 show_oops(NULL, "about invalid parameters");
2896 body = g_strdup_printf("<b>Version: %s</b><p>"
2897 "Authors:"
2898 "<ul>"
2899 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2900 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2901 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2902 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2903 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2904 "</ul>"
2905 "Copyrights and licenses can be found on the XXXTerm "
2906 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2907 version
2910 page = get_html_page("About", body, "", 0);
2911 g_free(body);
2913 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2914 g_free(page);
2916 return (0);
2920 help(struct tab *t, struct karg *args)
2922 char *page, *head, *body;
2924 if (t == NULL)
2925 show_oops(NULL, "help invalid parameters");
2927 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2928 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2929 "</head>\n";
2930 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2931 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2932 "cgi-bin/man-cgi?xxxterm</a>";
2934 page = get_html_page(XT_NAME, body, head, FALSE);
2936 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2937 g_free(page);
2939 return (0);
2943 startpage(struct tab *t, struct karg *args)
2945 char *page, *body, *b;
2946 struct sp *s;
2948 if (t == NULL)
2949 show_oops(NULL, "startpage invalid parameters");
2951 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
2953 TAILQ_FOREACH(s, &spl, entry) {
2954 b = body;
2955 body = g_strdup_printf("%s%s<br>", body, s->line);
2956 g_free(b);
2959 page = get_html_page("Startup Exception", body, "", 0);
2960 g_free(body);
2962 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
2963 g_free(page);
2965 return (0);
2968 void
2969 startpage_add(const char *fmt, ...)
2971 va_list ap;
2972 char *msg;
2973 struct sp *s;
2975 if (fmt == NULL)
2976 return;
2978 va_start(ap, fmt);
2979 if (vasprintf(&msg, fmt, ap) == -1)
2980 errx(1, "startpage_add failed");
2981 va_end(ap);
2983 s = g_malloc0(sizeof *s);
2984 s->line = msg;
2986 TAILQ_INSERT_TAIL(&spl, s, entry);
2990 * update all favorite tabs apart from one. Pass NULL if
2991 * you want to update all.
2993 void
2994 update_favorite_tabs(struct tab *apart_from)
2996 struct tab *t;
2997 if (!updating_fl_tabs) {
2998 updating_fl_tabs = 1; /* stop infinite recursion */
2999 TAILQ_FOREACH(t, &tabs, entry)
3000 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3001 && (t != apart_from))
3002 xtp_page_fl(t, NULL);
3003 updating_fl_tabs = 0;
3007 /* show a list of favorites (bookmarks) */
3009 xtp_page_fl(struct tab *t, struct karg *args)
3011 char file[PATH_MAX];
3012 FILE *f;
3013 char *uri = NULL, *title = NULL;
3014 size_t len, lineno = 0;
3015 int i, failed = 0;
3016 char *body, *tmp, *page = NULL;
3017 const char delim[3] = {'\\', '\\', '\0'};
3019 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3021 if (t == NULL)
3022 warn("%s: bad param", __func__);
3024 /* new session key */
3025 if (!updating_fl_tabs)
3026 generate_xtp_session_key(&fl_session_key);
3028 /* open favorites */
3029 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3030 if ((f = fopen(file, "r")) == NULL) {
3031 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3032 return (1);
3035 /* body */
3036 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3037 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3038 "<th style='width: 40px'>Rm</th></tr>\n");
3040 for (i = 1;;) {
3041 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3042 if (feof(f) || ferror(f))
3043 break;
3044 if (strlen(title) == 0 || title[0] == '#') {
3045 free(title);
3046 title = NULL;
3047 continue;
3050 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3051 if (feof(f) || ferror(f)) {
3052 show_oops(t, "favorites file corrupt");
3053 failed = 1;
3054 break;
3057 tmp = body;
3058 body = g_strdup_printf("%s<tr>"
3059 "<td>%d</td>"
3060 "<td><a href='%s'>%s</a></td>"
3061 "<td style='text-align: center'>"
3062 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3063 "</tr>\n",
3064 body, i, uri, title,
3065 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3067 g_free(tmp);
3069 free(uri);
3070 uri = NULL;
3071 free(title);
3072 title = NULL;
3073 i++;
3075 fclose(f);
3077 /* if none, say so */
3078 if (i == 1) {
3079 tmp = body;
3080 body = g_strdup_printf("%s<tr>"
3081 "<td colspan='3' style='text-align: center'>"
3082 "No favorites - To add one use the 'favadd' command."
3083 "</td></tr>", body);
3084 g_free(tmp);
3087 tmp = body;
3088 body = g_strdup_printf("%s</table>", body);
3089 g_free(tmp);
3091 if (uri)
3092 free(uri);
3093 if (title)
3094 free(title);
3096 /* render */
3097 if (!failed) {
3098 page = get_html_page("Favorites", body, "", 1);
3099 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3100 g_free(page);
3103 update_favorite_tabs(t);
3105 if (body)
3106 g_free(body);
3108 return (failed);
3111 void
3112 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3113 size_t cert_count, char *title)
3115 gnutls_datum_t cinfo;
3116 char *tmp, *body;
3117 int i;
3119 body = g_strdup("");
3121 for (i = 0; i < cert_count; i++) {
3122 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3123 &cinfo))
3124 return;
3126 tmp = body;
3127 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3128 body, i, cinfo.data);
3129 gnutls_free(cinfo.data);
3130 g_free(tmp);
3133 tmp = get_html_page(title, body, "", 0);
3134 g_free(body);
3136 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3137 g_free(tmp);
3141 ca_cmd(struct tab *t, struct karg *args)
3143 FILE *f = NULL;
3144 int rv = 1, certs = 0, certs_read;
3145 struct stat sb;
3146 gnutls_datum_t dt;
3147 gnutls_x509_crt_t *c = NULL;
3148 char *certs_buf = NULL, *s;
3150 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3151 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3152 return (1);
3155 if (fstat(fileno(f), &sb) == -1) {
3156 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3157 goto done;
3160 certs_buf = g_malloc(sb.st_size + 1);
3161 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3162 show_oops(t, "Can't read CA file: %s", strerror(errno));
3163 goto done;
3165 certs_buf[sb.st_size] = '\0';
3167 s = certs_buf;
3168 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3169 certs++;
3170 s += strlen("BEGIN CERTIFICATE");
3173 bzero(&dt, sizeof dt);
3174 dt.data = (unsigned char *)certs_buf;
3175 dt.size = sb.st_size;
3176 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3177 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3178 GNUTLS_X509_FMT_PEM, 0);
3179 if (certs_read <= 0) {
3180 show_oops(t, "No cert(s) available");
3181 goto done;
3183 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3184 done:
3185 if (c)
3186 g_free(c);
3187 if (certs_buf)
3188 g_free(certs_buf);
3189 if (f)
3190 fclose(f);
3192 return (rv);
3196 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3197 size_t domain_sz)
3199 SoupURI *su = NULL;
3200 struct addrinfo hints, *res = NULL, *ai;
3201 int rv = -1, s = -1, on, error;
3202 char port[8];
3204 if (uri && !g_str_has_prefix(uri, "https://")) {
3205 show_oops(t, "invalid URI");
3206 goto done;
3209 su = soup_uri_new(uri);
3210 if (su == NULL) {
3211 show_oops(t, "invalid soup URI");
3212 goto done;
3214 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3215 show_oops(t, "invalid HTTPS URI");
3216 goto done;
3219 snprintf(port, sizeof port, "%d", su->port);
3220 bzero(&hints, sizeof(struct addrinfo));
3221 hints.ai_flags = AI_CANONNAME;
3222 hints.ai_family = AF_UNSPEC;
3223 hints.ai_socktype = SOCK_STREAM;
3225 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3226 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3227 goto done;
3230 for (ai = res; ai; ai = ai->ai_next) {
3231 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3232 continue;
3234 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3235 if (s == -1) {
3236 show_oops(t, "socket failed: %s", strerror(errno));
3237 goto done;
3239 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3240 sizeof(on)) == -1) {
3241 show_oops(t, "setsockopt failed: %s", strerror(errno));
3242 goto done;
3244 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3245 show_oops(t, "connect failed: %s", strerror(errno));
3246 goto done;
3249 break;
3252 if (domain)
3253 strlcpy(domain, su->host, domain_sz);
3254 rv = s;
3255 done:
3256 if (su)
3257 soup_uri_free(su);
3258 if (res)
3259 freeaddrinfo(res);
3260 if (rv == -1 && s != -1)
3261 close(s);
3263 return (rv);
3267 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3269 if (gsession)
3270 gnutls_deinit(gsession);
3271 if (xcred)
3272 gnutls_certificate_free_credentials(xcred);
3274 return (0);
3278 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3279 gnutls_certificate_credentials_t *xc)
3281 gnutls_certificate_credentials_t xcred;
3282 gnutls_session_t gsession;
3283 int rv = 1;
3285 if (gs == NULL || xc == NULL)
3286 goto done;
3288 *gs = NULL;
3289 *xc = NULL;
3291 gnutls_certificate_allocate_credentials(&xcred);
3292 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3293 GNUTLS_X509_FMT_PEM);
3295 gnutls_init(&gsession, GNUTLS_CLIENT);
3296 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3297 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3298 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3299 if ((rv = gnutls_handshake(gsession)) < 0) {
3300 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3302 gnutls_error_is_fatal(rv),
3303 gnutls_strerror_name(rv));
3304 stop_tls(gsession, xcred);
3305 goto done;
3308 gnutls_credentials_type_t cred;
3309 cred = gnutls_auth_get_type(gsession);
3310 if (cred != GNUTLS_CRD_CERTIFICATE) {
3311 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3312 stop_tls(gsession, xcred);
3313 goto done;
3316 *gs = gsession;
3317 *xc = xcred;
3318 rv = 0;
3319 done:
3320 return (rv);
3324 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3325 size_t *cert_count)
3327 unsigned int len;
3328 const gnutls_datum_t *cl;
3329 gnutls_x509_crt_t *all_certs;
3330 int i, rv = 1;
3332 if (certs == NULL || cert_count == NULL)
3333 goto done;
3334 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3335 goto done;
3336 cl = gnutls_certificate_get_peers(gsession, &len);
3337 if (len == 0)
3338 goto done;
3340 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3341 for (i = 0; i < len; i++) {
3342 gnutls_x509_crt_init(&all_certs[i]);
3343 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3344 GNUTLS_X509_FMT_PEM < 0)) {
3345 g_free(all_certs);
3346 goto done;
3350 *certs = all_certs;
3351 *cert_count = len;
3352 rv = 0;
3353 done:
3354 return (rv);
3357 void
3358 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3360 int i;
3362 for (i = 0; i < cert_count; i++)
3363 gnutls_x509_crt_deinit(certs[i]);
3364 g_free(certs);
3367 void
3368 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3370 GdkColor c_text, c_base;
3372 gdk_color_parse(text, &c_text);
3373 gdk_color_parse(base, &c_base);
3375 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3376 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3377 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3378 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3380 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3381 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3382 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3383 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3386 void
3387 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3388 size_t cert_count, char *domain)
3390 size_t cert_buf_sz;
3391 char cert_buf[64 * 1024], file[PATH_MAX];
3392 int i;
3393 FILE *f;
3394 GdkColor color;
3396 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3397 return;
3399 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3400 if ((f = fopen(file, "w")) == NULL) {
3401 show_oops(t, "Can't create cert file %s %s",
3402 file, strerror(errno));
3403 return;
3406 for (i = 0; i < cert_count; i++) {
3407 cert_buf_sz = sizeof cert_buf;
3408 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3409 cert_buf, &cert_buf_sz)) {
3410 show_oops(t, "gnutls_x509_crt_export failed");
3411 goto done;
3413 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3414 show_oops(t, "Can't write certs: %s", strerror(errno));
3415 goto done;
3419 /* not the best spot but oh well */
3420 gdk_color_parse("lightblue", &color);
3421 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3422 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3423 done:
3424 fclose(f);
3427 enum cert_trust {
3428 CERT_LOCAL,
3429 CERT_TRUSTED,
3430 CERT_UNTRUSTED,
3431 CERT_BAD
3434 enum cert_trust
3435 load_compare_cert(struct tab *t, struct karg *args)
3437 const gchar *uri;
3438 char domain[8182], file[PATH_MAX];
3439 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3440 int s = -1, i, error;
3441 FILE *f = NULL;
3442 size_t cert_buf_sz, cert_count;
3443 enum cert_trust rv = CERT_UNTRUSTED;
3444 char serr[80];
3445 gnutls_session_t gsession;
3446 gnutls_x509_crt_t *certs;
3447 gnutls_certificate_credentials_t xcred;
3449 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3451 if (t == NULL)
3452 return (rv);
3454 if ((uri = get_uri(t)) == NULL)
3455 return (rv);
3456 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3458 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3459 return (rv);
3460 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3462 /* go ssl/tls */
3463 if (start_tls(t, s, &gsession, &xcred))
3464 goto done;
3465 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3467 /* verify certs in case cert file doesn't exist */
3468 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3469 GNUTLS_E_SUCCESS) {
3470 show_oops(t, "Invalid certificates");
3471 goto done;
3474 /* get certs */
3475 if (get_connection_certs(gsession, &certs, &cert_count)) {
3476 show_oops(t, "Can't get connection certificates");
3477 goto done;
3480 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3481 if ((f = fopen(file, "r")) == NULL) {
3482 if (!error)
3483 rv = CERT_TRUSTED;
3484 goto freeit;
3487 for (i = 0; i < cert_count; i++) {
3488 cert_buf_sz = sizeof cert_buf;
3489 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3490 cert_buf, &cert_buf_sz)) {
3491 goto freeit;
3493 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3494 rv = CERT_BAD; /* critical */
3495 goto freeit;
3497 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3498 rv = CERT_BAD; /* critical */
3499 goto freeit;
3501 rv = CERT_LOCAL;
3504 freeit:
3505 if (f)
3506 fclose(f);
3507 free_connection_certs(certs, cert_count);
3508 done:
3509 /* we close the socket first for speed */
3510 if (s != -1)
3511 close(s);
3513 /* only complain if we didn't save it locally */
3514 if (error && rv != CERT_LOCAL) {
3515 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3516 if (error & GNUTLS_CERT_INVALID)
3517 strlcat(serr, "invalid, ", sizeof serr);
3518 if (error & GNUTLS_CERT_REVOKED)
3519 strlcat(serr, "revoked, ", sizeof serr);
3520 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3521 strlcat(serr, "signer not found, ", sizeof serr);
3522 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3523 strlcat(serr, "not signed by CA, ", sizeof serr);
3524 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3525 strlcat(serr, "insecure algorithm, ", sizeof serr);
3526 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3527 strlcat(serr, "not activated, ", sizeof serr);
3528 if (error & GNUTLS_CERT_EXPIRED)
3529 strlcat(serr, "expired, ", sizeof serr);
3530 for (i = strlen(serr) - 1; i > 0; i--)
3531 if (serr[i] == ',') {
3532 serr[i] = '\0';
3533 break;
3535 show_oops(t, serr);
3538 stop_tls(gsession, xcred);
3540 return (rv);
3544 cert_cmd(struct tab *t, struct karg *args)
3546 const gchar *uri;
3547 char domain[8182];
3548 int s = -1;
3549 size_t cert_count;
3550 gnutls_session_t gsession;
3551 gnutls_x509_crt_t *certs;
3552 gnutls_certificate_credentials_t xcred;
3554 if (t == NULL)
3555 return (1);
3557 if (ssl_ca_file == NULL) {
3558 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3559 return (1);
3562 if ((uri = get_uri(t)) == NULL) {
3563 show_oops(t, "Invalid URI");
3564 return (1);
3567 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3568 show_oops(t, "Invalid certificate URI: %s", uri);
3569 return (1);
3572 /* go ssl/tls */
3573 if (start_tls(t, s, &gsession, &xcred))
3574 goto done;
3576 /* get certs */
3577 if (get_connection_certs(gsession, &certs, &cert_count)) {
3578 show_oops(t, "get_connection_certs failed");
3579 goto done;
3582 if (args->i & XT_SHOW)
3583 show_certs(t, certs, cert_count, "Certificate Chain");
3584 else if (args->i & XT_SAVE)
3585 save_certs(t, certs, cert_count, domain);
3587 free_connection_certs(certs, cert_count);
3588 done:
3589 /* we close the socket first for speed */
3590 if (s != -1)
3591 close(s);
3592 stop_tls(gsession, xcred);
3594 return (0);
3598 remove_cookie(int index)
3600 int i, rv = 1;
3601 GSList *cf;
3602 SoupCookie *c;
3604 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3606 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3608 for (i = 1; cf; cf = cf->next, i++) {
3609 if (i != index)
3610 continue;
3611 c = cf->data;
3612 print_cookie("remove cookie", c);
3613 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3614 rv = 0;
3615 break;
3618 soup_cookies_free(cf);
3620 return (rv);
3624 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3626 struct domain *d;
3627 char *tmp, *body;
3629 body = g_strdup("");
3631 /* p list */
3632 if (args->i & XT_WL_PERSISTENT) {
3633 tmp = body;
3634 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3635 g_free(tmp);
3636 RB_FOREACH(d, domain_list, wl) {
3637 if (d->handy == 0)
3638 continue;
3639 tmp = body;
3640 body = g_strdup_printf("%s%s<br/>", body, d->d);
3641 g_free(tmp);
3645 /* s list */
3646 if (args->i & XT_WL_SESSION) {
3647 tmp = body;
3648 body = g_strdup_printf("%s<h2>Session</h2>", body);
3649 g_free(tmp);
3650 RB_FOREACH(d, domain_list, wl) {
3651 if (d->handy == 1)
3652 continue;
3653 tmp = body;
3654 body = g_strdup_printf("%s%s<br/>", body, d->d);
3655 g_free(tmp);
3659 tmp = get_html_page(title, body, "", 0);
3660 g_free(body);
3661 if (wl == &js_wl)
3662 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3663 else
3664 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3665 g_free(tmp);
3666 return (0);
3670 wl_save(struct tab *t, struct karg *args, int js)
3672 char file[PATH_MAX];
3673 FILE *f;
3674 char *line = NULL, *lt = NULL, *dom = NULL;
3675 size_t linelen;
3676 const gchar *uri;
3677 struct karg a;
3678 struct domain *d;
3679 GSList *cf;
3680 SoupCookie *ci, *c;
3682 if (t == NULL || args == NULL)
3683 return (1);
3685 if (runtime_settings[0] == '\0')
3686 return (1);
3688 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3689 if ((f = fopen(file, "r+")) == NULL)
3690 return (1);
3692 uri = get_uri(t);
3693 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3694 if (uri == NULL || dom == NULL ||
3695 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3696 show_oops(t, "Can't add domain to %s white list",
3697 js ? "JavaScript" : "cookie");
3698 goto done;
3701 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3703 while (!feof(f)) {
3704 line = fparseln(f, &linelen, NULL, NULL, 0);
3705 if (line == NULL)
3706 continue;
3707 if (!strcmp(line, lt))
3708 goto done;
3709 free(line);
3710 line = NULL;
3713 fprintf(f, "%s\n", lt);
3715 a.i = XT_WL_ENABLE;
3716 a.i |= args->i;
3717 if (js) {
3718 d = wl_find(dom, &js_wl);
3719 if (!d) {
3720 settings_add("js_wl", dom);
3721 d = wl_find(dom, &js_wl);
3723 toggle_js(t, &a);
3724 } else {
3725 d = wl_find(dom, &c_wl);
3726 if (!d) {
3727 settings_add("cookie_wl", dom);
3728 d = wl_find(dom, &c_wl);
3730 toggle_cwl(t, &a);
3732 /* find and add to persistent jar */
3733 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3734 for (;cf; cf = cf->next) {
3735 ci = cf->data;
3736 if (!strcmp(dom, ci->domain) ||
3737 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3738 c = soup_cookie_copy(ci);
3739 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3742 soup_cookies_free(cf);
3744 if (d)
3745 d->handy = 1;
3747 done:
3748 if (line)
3749 free(line);
3750 if (dom)
3751 g_free(dom);
3752 if (lt)
3753 g_free(lt);
3754 fclose(f);
3756 return (0);
3760 js_show_wl(struct tab *t, struct karg *args)
3762 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3763 wl_show(t, args, "JavaScript White List", &js_wl);
3765 return (0);
3769 cookie_show_wl(struct tab *t, struct karg *args)
3771 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3772 wl_show(t, args, "Cookie White List", &c_wl);
3774 return (0);
3778 cookie_cmd(struct tab *t, struct karg *args)
3780 if (args->i & XT_SHOW)
3781 wl_show(t, args, "Cookie White List", &c_wl);
3782 else if (args->i & XT_WL_TOGGLE) {
3783 args->i |= XT_WL_RELOAD;
3784 toggle_cwl(t, args);
3785 } else if (args->i & XT_SAVE) {
3786 args->i |= XT_WL_RELOAD;
3787 wl_save(t, args, 0);
3788 } else if (args->i & XT_DELETE)
3789 show_oops(t, "'cookie delete' currently unimplemented");
3791 return (0);
3795 js_cmd(struct tab *t, struct karg *args)
3797 if (args->i & XT_SHOW)
3798 wl_show(t, args, "JavaScript White List", &js_wl);
3799 else if (args->i & XT_SAVE) {
3800 args->i |= XT_WL_RELOAD;
3801 wl_save(t, args, 1);
3802 } else if (args->i & XT_WL_TOGGLE) {
3803 args->i |= XT_WL_RELOAD;
3804 toggle_js(t, args);
3805 } else if (args->i & XT_DELETE)
3806 show_oops(t, "'js delete' currently unimplemented");
3808 return (0);
3812 toplevel_cmd(struct tab *t, struct karg *args)
3814 js_toggle_cb(t->js_toggle, t);
3816 return (0);
3820 add_favorite(struct tab *t, struct karg *args)
3822 char file[PATH_MAX];
3823 FILE *f;
3824 char *line = NULL;
3825 size_t urilen, linelen;
3826 const gchar *uri, *title;
3828 if (t == NULL)
3829 return (1);
3831 /* don't allow adding of xtp pages to favorites */
3832 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3833 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3834 return (1);
3837 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3838 if ((f = fopen(file, "r+")) == NULL) {
3839 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3840 return (1);
3843 title = get_title(t, FALSE);
3844 uri = get_uri(t);
3846 if (title == NULL || uri == NULL) {
3847 show_oops(t, "can't add page to favorites");
3848 goto done;
3851 urilen = strlen(uri);
3853 for (;;) {
3854 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3855 if (feof(f) || ferror(f))
3856 break;
3858 if (linelen == urilen && !strcmp(line, uri))
3859 goto done;
3861 free(line);
3862 line = NULL;
3865 fprintf(f, "\n%s\n%s", title, uri);
3866 done:
3867 if (line)
3868 free(line);
3869 fclose(f);
3871 update_favorite_tabs(NULL);
3873 return (0);
3877 navaction(struct tab *t, struct karg *args)
3879 WebKitWebHistoryItem *item;
3881 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3882 t->tab_id, args->i);
3884 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3886 if (t->item) {
3887 if (args->i == XT_NAV_BACK)
3888 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3889 else
3890 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3891 if (item == NULL)
3892 return (XT_CB_PASSTHROUGH);
3893 webkit_web_view_go_to_back_forward_item(t->wv, item);
3894 t->item = NULL;
3895 return (XT_CB_PASSTHROUGH);
3898 switch (args->i) {
3899 case XT_NAV_BACK:
3900 marks_clear(t);
3901 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3902 if (item)
3903 webkit_web_view_go_to_back_forward_item(t->wv, item);
3904 break;
3905 case XT_NAV_FORWARD:
3906 marks_clear(t);
3907 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3908 if (item)
3909 webkit_web_view_go_to_back_forward_item(t->wv, item);
3910 break;
3911 case XT_NAV_RELOAD:
3912 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3913 if (item)
3914 webkit_web_view_go_to_back_forward_item(t->wv, item);
3915 break;
3917 return (XT_CB_PASSTHROUGH);
3921 move(struct tab *t, struct karg *args)
3923 GtkAdjustment *adjust;
3924 double pi, si, pos, ps, upper, lower, max;
3925 double percent;
3927 switch (args->i) {
3928 case XT_MOVE_DOWN:
3929 case XT_MOVE_UP:
3930 case XT_MOVE_BOTTOM:
3931 case XT_MOVE_TOP:
3932 case XT_MOVE_PAGEDOWN:
3933 case XT_MOVE_PAGEUP:
3934 case XT_MOVE_HALFDOWN:
3935 case XT_MOVE_HALFUP:
3936 case XT_MOVE_PERCENT:
3937 adjust = t->adjust_v;
3938 break;
3939 default:
3940 adjust = t->adjust_h;
3941 break;
3944 pos = gtk_adjustment_get_value(adjust);
3945 ps = gtk_adjustment_get_page_size(adjust);
3946 upper = gtk_adjustment_get_upper(adjust);
3947 lower = gtk_adjustment_get_lower(adjust);
3948 si = gtk_adjustment_get_step_increment(adjust);
3949 pi = gtk_adjustment_get_page_increment(adjust);
3950 max = upper - ps;
3952 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3953 "max %f si %f pi %f\n",
3954 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3955 pos, ps, upper, lower, max, si, pi);
3957 switch (args->i) {
3958 case XT_MOVE_DOWN:
3959 case XT_MOVE_RIGHT:
3960 pos += si;
3961 gtk_adjustment_set_value(adjust, MIN(pos, max));
3962 break;
3963 case XT_MOVE_UP:
3964 case XT_MOVE_LEFT:
3965 pos -= si;
3966 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3967 break;
3968 case XT_MOVE_BOTTOM:
3969 case XT_MOVE_FARRIGHT:
3970 gtk_adjustment_set_value(adjust, max);
3971 break;
3972 case XT_MOVE_TOP:
3973 case XT_MOVE_FARLEFT:
3974 gtk_adjustment_set_value(adjust, lower);
3975 break;
3976 case XT_MOVE_PAGEDOWN:
3977 pos += pi;
3978 gtk_adjustment_set_value(adjust, MIN(pos, max));
3979 break;
3980 case XT_MOVE_PAGEUP:
3981 pos -= pi;
3982 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3983 break;
3984 case XT_MOVE_HALFDOWN:
3985 pos += pi / 2;
3986 gtk_adjustment_set_value(adjust, MIN(pos, max));
3987 break;
3988 case XT_MOVE_HALFUP:
3989 pos -= pi / 2;
3990 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3991 break;
3992 case XT_MOVE_PERCENT:
3993 percent = atoi(args->s) / 100.0;
3994 pos = max * percent;
3995 if (pos < 0.0 || pos > max)
3996 break;
3997 gtk_adjustment_set_value(adjust, pos);
3998 break;
3999 default:
4000 return (XT_CB_PASSTHROUGH);
4003 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4005 return (XT_CB_HANDLED);
4008 void
4009 url_set_visibility(void)
4011 struct tab *t;
4013 TAILQ_FOREACH(t, &tabs, entry)
4014 if (show_url == 0) {
4015 gtk_widget_hide(t->toolbar);
4016 focus_webview(t);
4017 } else
4018 gtk_widget_show(t->toolbar);
4021 void
4022 notebook_tab_set_visibility(void)
4024 if (show_tabs == 0) {
4025 gtk_widget_hide(tab_bar);
4026 gtk_notebook_set_show_tabs(notebook, FALSE);
4027 } else {
4028 if (tab_style == XT_TABS_NORMAL) {
4029 gtk_widget_hide(tab_bar);
4030 gtk_notebook_set_show_tabs(notebook, TRUE);
4031 } else if (tab_style == XT_TABS_COMPACT) {
4032 gtk_widget_show(tab_bar);
4033 gtk_notebook_set_show_tabs(notebook, FALSE);
4038 void
4039 statusbar_set_visibility(void)
4041 struct tab *t;
4043 TAILQ_FOREACH(t, &tabs, entry)
4044 if (show_statusbar == 0) {
4045 gtk_widget_hide(t->statusbar_box);
4046 focus_webview(t);
4047 } else
4048 gtk_widget_show(t->statusbar_box);
4051 void
4052 url_set(struct tab *t, int enable_url_entry)
4054 GdkPixbuf *pixbuf;
4055 int progress;
4057 show_url = enable_url_entry;
4059 if (enable_url_entry) {
4060 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4061 GTK_ENTRY_ICON_PRIMARY, NULL);
4062 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4063 } else {
4064 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4065 GTK_ENTRY_ICON_PRIMARY);
4066 progress =
4067 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4068 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4069 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4070 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4071 progress);
4076 fullscreen(struct tab *t, struct karg *args)
4078 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4080 if (t == NULL)
4081 return (XT_CB_PASSTHROUGH);
4083 if (show_url == 0) {
4084 url_set(t, 1);
4085 show_tabs = 1;
4086 } else {
4087 url_set(t, 0);
4088 show_tabs = 0;
4091 url_set_visibility();
4092 notebook_tab_set_visibility();
4094 return (XT_CB_HANDLED);
4098 statustoggle(struct tab *t, struct karg *args)
4100 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4102 if (show_statusbar == 1) {
4103 show_statusbar = 0;
4104 statusbar_set_visibility();
4105 } else if (show_statusbar == 0) {
4106 show_statusbar = 1;
4107 statusbar_set_visibility();
4109 return (XT_CB_HANDLED);
4113 urlaction(struct tab *t, struct karg *args)
4115 int rv = XT_CB_HANDLED;
4117 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4119 if (t == NULL)
4120 return (XT_CB_PASSTHROUGH);
4122 switch (args->i) {
4123 case XT_URL_SHOW:
4124 if (show_url == 0) {
4125 url_set(t, 1);
4126 url_set_visibility();
4128 break;
4129 case XT_URL_HIDE:
4130 if (show_url == 1) {
4131 url_set(t, 0);
4132 url_set_visibility();
4134 break;
4136 return (rv);
4140 tabaction(struct tab *t, struct karg *args)
4142 int rv = XT_CB_HANDLED;
4143 char *url = args->s;
4144 struct undo *u;
4145 struct tab *tt;
4147 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4149 if (t == NULL)
4150 return (XT_CB_PASSTHROUGH);
4152 switch (args->i) {
4153 case XT_TAB_NEW:
4154 if (strlen(url) > 0)
4155 create_new_tab(url, NULL, 1, args->precount);
4156 else
4157 create_new_tab(NULL, NULL, 1, args->precount);
4158 break;
4159 case XT_TAB_DELETE:
4160 if (args->precount < 0)
4161 delete_tab(t);
4162 else
4163 TAILQ_FOREACH(tt, &tabs, entry)
4164 if (tt->tab_id == args->precount - 1) {
4165 delete_tab(tt);
4166 break;
4168 break;
4169 case XT_TAB_DELQUIT:
4170 if (gtk_notebook_get_n_pages(notebook) > 1)
4171 delete_tab(t);
4172 else
4173 quit(t, args);
4174 break;
4175 case XT_TAB_OPEN:
4176 if (strlen(url) > 0)
4178 else {
4179 rv = XT_CB_PASSTHROUGH;
4180 goto done;
4182 load_uri(t, url);
4183 break;
4184 case XT_TAB_SHOW:
4185 if (show_tabs == 0) {
4186 show_tabs = 1;
4187 notebook_tab_set_visibility();
4189 break;
4190 case XT_TAB_HIDE:
4191 if (show_tabs == 1) {
4192 show_tabs = 0;
4193 notebook_tab_set_visibility();
4195 break;
4196 case XT_TAB_NEXTSTYLE:
4197 if (tab_style == XT_TABS_NORMAL) {
4198 tab_style = XT_TABS_COMPACT;
4199 recolor_compact_tabs();
4201 else
4202 tab_style = XT_TABS_NORMAL;
4203 notebook_tab_set_visibility();
4204 break;
4205 case XT_TAB_UNDO_CLOSE:
4206 if (undo_count == 0) {
4207 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4208 __func__);
4209 goto done;
4210 } else {
4211 undo_count--;
4212 u = TAILQ_FIRST(&undos);
4213 create_new_tab(u->uri, u, 1, -1);
4215 TAILQ_REMOVE(&undos, u, entry);
4216 g_free(u->uri);
4217 /* u->history is freed in create_new_tab() */
4218 g_free(u);
4220 break;
4221 default:
4222 rv = XT_CB_PASSTHROUGH;
4223 goto done;
4226 done:
4227 if (args->s) {
4228 g_free(args->s);
4229 args->s = NULL;
4232 return (rv);
4236 resizetab(struct tab *t, struct karg *args)
4238 if (t == NULL || args == NULL) {
4239 show_oops(NULL, "resizetab invalid parameters");
4240 return (XT_CB_PASSTHROUGH);
4243 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4244 t->tab_id, args->i);
4246 setzoom_webkit(t, args->i);
4248 return (XT_CB_HANDLED);
4252 movetab(struct tab *t, struct karg *args)
4254 int n, dest;
4256 if (t == NULL || args == NULL) {
4257 show_oops(NULL, "movetab invalid parameters");
4258 return (XT_CB_PASSTHROUGH);
4261 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4262 t->tab_id, args->i);
4264 if (args->i >= XT_TAB_INVALID)
4265 return (XT_CB_PASSTHROUGH);
4267 if (TAILQ_EMPTY(&tabs))
4268 return (XT_CB_PASSTHROUGH);
4270 n = gtk_notebook_get_n_pages(notebook);
4271 dest = gtk_notebook_get_current_page(notebook);
4273 switch (args->i) {
4274 case XT_TAB_NEXT:
4275 if (args->precount < 0)
4276 dest = dest == n - 1 ? 0 : dest + 1;
4277 else
4278 dest = args->precount - 1;
4280 break;
4281 case XT_TAB_PREV:
4282 if (args->precount < 0)
4283 dest -= 1;
4284 else
4285 dest -= args->precount % n;
4287 if (dest < 0)
4288 dest += n;
4290 break;
4291 case XT_TAB_FIRST:
4292 dest = 0;
4293 break;
4294 case XT_TAB_LAST:
4295 dest = n - 1;
4296 break;
4297 default:
4298 return (XT_CB_PASSTHROUGH);
4301 if (dest < 0 || dest >= n)
4302 return (XT_CB_PASSTHROUGH);
4303 if (t->tab_id == dest) {
4304 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4305 return (XT_CB_HANDLED);
4308 set_current_tab(dest);
4310 return (XT_CB_HANDLED);
4313 int cmd_prefix = 0;
4316 command(struct tab *t, struct karg *args)
4318 char *s = NULL, *ss = NULL;
4319 GdkColor color;
4320 const gchar *uri;
4322 if (t == NULL || args == NULL) {
4323 show_oops(NULL, "command invalid parameters");
4324 return (XT_CB_PASSTHROUGH);
4327 switch (args->i) {
4328 case '/':
4329 s = "/";
4330 break;
4331 case '?':
4332 s = "?";
4333 break;
4334 case ':':
4335 if (cmd_prefix == 0)
4336 s = ":";
4337 else {
4338 ss = g_strdup_printf(":%d", cmd_prefix);
4339 s = ss;
4340 cmd_prefix = 0;
4342 break;
4343 case XT_CMD_OPEN:
4344 s = ":open ";
4345 break;
4346 case XT_CMD_TABNEW:
4347 s = ":tabnew ";
4348 break;
4349 case XT_CMD_OPEN_CURRENT:
4350 s = ":open ";
4351 /* FALL THROUGH */
4352 case XT_CMD_TABNEW_CURRENT:
4353 if (!s) /* FALL THROUGH? */
4354 s = ":tabnew ";
4355 if ((uri = get_uri(t)) != NULL) {
4356 ss = g_strdup_printf("%s%s", s, uri);
4357 s = ss;
4359 break;
4360 default:
4361 show_oops(t, "command: invalid opcode %d", args->i);
4362 return (XT_CB_PASSTHROUGH);
4365 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4367 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4368 gdk_color_parse(XT_COLOR_WHITE, &color);
4369 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4370 show_cmd(t);
4371 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4372 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4374 if (ss)
4375 g_free(ss);
4377 return (XT_CB_HANDLED);
4381 * Return a new string with a download row (in html)
4382 * appended. Old string is freed.
4384 char *
4385 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4388 WebKitDownloadStatus stat;
4389 char *status_html = NULL, *cmd_html = NULL, *new_html;
4390 gdouble progress;
4391 char cur_sz[FMT_SCALED_STRSIZE];
4392 char tot_sz[FMT_SCALED_STRSIZE];
4393 char *xtp_prefix;
4395 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4397 /* All actions wil take this form:
4398 * xxxt://class/seskey
4400 xtp_prefix = g_strdup_printf("%s%d/%s/",
4401 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4403 stat = webkit_download_get_status(dl->download);
4405 switch (stat) {
4406 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4407 status_html = g_strdup_printf("Finished");
4408 cmd_html = g_strdup_printf(
4409 "<a href='%s%d/%d'>Remove</a>",
4410 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4411 break;
4412 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4413 /* gather size info */
4414 progress = 100 * webkit_download_get_progress(dl->download);
4416 fmt_scaled(
4417 webkit_download_get_current_size(dl->download), cur_sz);
4418 fmt_scaled(
4419 webkit_download_get_total_size(dl->download), tot_sz);
4421 status_html = g_strdup_printf(
4422 "<div style='width: 100%%' align='center'>"
4423 "<div class='progress-outer'>"
4424 "<div class='progress-inner' style='width: %.2f%%'>"
4425 "</div></div></div>"
4426 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4427 progress, cur_sz, tot_sz, progress);
4429 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4430 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4432 break;
4433 /* LLL */
4434 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4435 status_html = g_strdup_printf("Cancelled");
4436 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4437 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4438 break;
4439 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4440 status_html = g_strdup_printf("Error!");
4441 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4442 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4443 break;
4444 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4445 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4446 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4447 status_html = g_strdup_printf("Starting");
4448 break;
4449 default:
4450 show_oops(t, "%s: unknown download status", __func__);
4453 new_html = g_strdup_printf(
4454 "%s\n<tr><td>%s</td><td>%s</td>"
4455 "<td style='text-align:center'>%s</td></tr>\n",
4456 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4457 status_html, cmd_html);
4458 g_free(html);
4460 if (status_html)
4461 g_free(status_html);
4463 if (cmd_html)
4464 g_free(cmd_html);
4466 g_free(xtp_prefix);
4468 return new_html;
4472 * update all download tabs apart from one. Pass NULL if
4473 * you want to update all.
4475 void
4476 update_download_tabs(struct tab *apart_from)
4478 struct tab *t;
4479 if (!updating_dl_tabs) {
4480 updating_dl_tabs = 1; /* stop infinite recursion */
4481 TAILQ_FOREACH(t, &tabs, entry)
4482 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4483 && (t != apart_from))
4484 xtp_page_dl(t, NULL);
4485 updating_dl_tabs = 0;
4490 * update all cookie tabs apart from one. Pass NULL if
4491 * you want to update all.
4493 void
4494 update_cookie_tabs(struct tab *apart_from)
4496 struct tab *t;
4497 if (!updating_cl_tabs) {
4498 updating_cl_tabs = 1; /* stop infinite recursion */
4499 TAILQ_FOREACH(t, &tabs, entry)
4500 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4501 && (t != apart_from))
4502 xtp_page_cl(t, NULL);
4503 updating_cl_tabs = 0;
4508 * update all history tabs apart from one. Pass NULL if
4509 * you want to update all.
4511 void
4512 update_history_tabs(struct tab *apart_from)
4514 struct tab *t;
4516 if (!updating_hl_tabs) {
4517 updating_hl_tabs = 1; /* stop infinite recursion */
4518 TAILQ_FOREACH(t, &tabs, entry)
4519 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4520 && (t != apart_from))
4521 xtp_page_hl(t, NULL);
4522 updating_hl_tabs = 0;
4526 /* cookie management XTP page */
4528 xtp_page_cl(struct tab *t, struct karg *args)
4530 char *body, *page, *tmp;
4531 int i = 1; /* all ids start 1 */
4532 GSList *sc, *pc, *pc_start;
4533 SoupCookie *c;
4534 char *type, *table_headers, *last_domain;
4536 DNPRINTF(XT_D_CMD, "%s", __func__);
4538 if (t == NULL) {
4539 show_oops(NULL, "%s invalid parameters", __func__);
4540 return (1);
4543 /* Generate a new session key */
4544 if (!updating_cl_tabs)
4545 generate_xtp_session_key(&cl_session_key);
4547 /* table headers */
4548 table_headers = g_strdup_printf("<table><tr>"
4549 "<th>Type</th>"
4550 "<th>Name</th>"
4551 "<th style='width:200px'>Value</th>"
4552 "<th>Path</th>"
4553 "<th>Expires</th>"
4554 "<th>Secure</th>"
4555 "<th>HTTP<br />only</th>"
4556 "<th style='width:40px'>Rm</th></tr>\n");
4558 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4559 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4560 pc_start = pc;
4562 body = NULL;
4563 last_domain = strdup("");
4564 for (; sc; sc = sc->next) {
4565 c = sc->data;
4567 if (strcmp(last_domain, c->domain) != 0) {
4568 /* new domain */
4569 free(last_domain);
4570 last_domain = strdup(c->domain);
4572 if (body != NULL) {
4573 tmp = body;
4574 body = g_strdup_printf("%s</table>"
4575 "<h2>%s</h2>%s\n",
4576 body, c->domain, table_headers);
4577 g_free(tmp);
4578 } else {
4579 /* first domain */
4580 body = g_strdup_printf("<h2>%s</h2>%s\n",
4581 c->domain, table_headers);
4585 type = "Session";
4586 for (pc = pc_start; pc; pc = pc->next)
4587 if (soup_cookie_equal(pc->data, c)) {
4588 type = "Session + Persistent";
4589 break;
4592 tmp = body;
4593 body = g_strdup_printf(
4594 "%s\n<tr>"
4595 "<td>%s</td>"
4596 "<td style='word-wrap:normal'>%s</td>"
4597 "<td>"
4598 " <textarea rows='4'>%s</textarea>"
4599 "</td>"
4600 "<td>%s</td>"
4601 "<td>%s</td>"
4602 "<td>%d</td>"
4603 "<td>%d</td>"
4604 "<td style='text-align:center'>"
4605 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4606 body,
4607 type,
4608 c->name,
4609 c->value,
4610 c->path,
4611 c->expires ?
4612 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4613 c->secure,
4614 c->http_only,
4616 XT_XTP_STR,
4617 XT_XTP_CL,
4618 cl_session_key,
4619 XT_XTP_CL_REMOVE,
4623 g_free(tmp);
4624 i++;
4627 soup_cookies_free(sc);
4628 soup_cookies_free(pc);
4630 /* small message if there are none */
4631 if (i == 1) {
4632 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4633 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4635 tmp = body;
4636 body = g_strdup_printf("%s</table>", body);
4637 g_free(tmp);
4639 page = get_html_page("Cookie Jar", body, "", TRUE);
4640 g_free(body);
4641 g_free(table_headers);
4642 g_free(last_domain);
4644 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4645 update_cookie_tabs(t);
4647 g_free(page);
4649 return (0);
4653 xtp_page_hl(struct tab *t, struct karg *args)
4655 char *body, *page, *tmp;
4656 struct history *h;
4657 int i = 1; /* all ids start 1 */
4659 DNPRINTF(XT_D_CMD, "%s", __func__);
4661 if (t == NULL) {
4662 show_oops(NULL, "%s invalid parameters", __func__);
4663 return (1);
4666 /* Generate a new session key */
4667 if (!updating_hl_tabs)
4668 generate_xtp_session_key(&hl_session_key);
4670 /* body */
4671 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4672 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4674 RB_FOREACH_REVERSE(h, history_list, &hl) {
4675 tmp = body;
4676 body = g_strdup_printf(
4677 "%s\n<tr>"
4678 "<td><a href='%s'>%s</a></td>"
4679 "<td>%s</td>"
4680 "<td style='text-align: center'>"
4681 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4682 body, h->uri, h->uri, h->title,
4683 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4684 XT_XTP_HL_REMOVE, i);
4686 g_free(tmp);
4687 i++;
4690 /* small message if there are none */
4691 if (i == 1) {
4692 tmp = body;
4693 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4694 "colspan='3'>No History</td></tr>\n", body);
4695 g_free(tmp);
4698 tmp = body;
4699 body = g_strdup_printf("%s</table>", body);
4700 g_free(tmp);
4702 page = get_html_page("History", body, "", TRUE);
4703 g_free(body);
4706 * update all history manager tabs as the xtp session
4707 * key has now changed. No need to update the current tab.
4708 * Already did that above.
4710 update_history_tabs(t);
4712 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4713 g_free(page);
4715 return (0);
4719 * Generate a web page detailing the status of any downloads
4722 xtp_page_dl(struct tab *t, struct karg *args)
4724 struct download *dl;
4725 char *body, *page, *tmp;
4726 char *ref;
4727 int n_dl = 1;
4729 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4731 if (t == NULL) {
4732 show_oops(NULL, "%s invalid parameters", __func__);
4733 return (1);
4737 * Generate a new session key for next page instance.
4738 * This only happens for the top level call to xtp_page_dl()
4739 * in which case updating_dl_tabs is 0.
4741 if (!updating_dl_tabs)
4742 generate_xtp_session_key(&dl_session_key);
4744 /* header - with refresh so as to update */
4745 if (refresh_interval >= 1)
4746 ref = g_strdup_printf(
4747 "<meta http-equiv='refresh' content='%u"
4748 ";url=%s%d/%s/%d' />\n",
4749 refresh_interval,
4750 XT_XTP_STR,
4751 XT_XTP_DL,
4752 dl_session_key,
4753 XT_XTP_DL_LIST);
4754 else
4755 ref = g_strdup("");
4757 body = g_strdup_printf("<div align='center'>"
4758 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4759 "</p><table><tr><th style='width: 60%%'>"
4760 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4761 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4763 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4764 body = xtp_page_dl_row(t, body, dl);
4765 n_dl++;
4768 /* message if no downloads in list */
4769 if (n_dl == 1) {
4770 tmp = body;
4771 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4772 " style='text-align: center'>"
4773 "No downloads</td></tr>\n", body);
4774 g_free(tmp);
4777 tmp = body;
4778 body = g_strdup_printf("%s</table></div>", body);
4779 g_free(tmp);
4781 page = get_html_page("Downloads", body, ref, 1);
4782 g_free(ref);
4783 g_free(body);
4786 * update all download manager tabs as the xtp session
4787 * key has now changed. No need to update the current tab.
4788 * Already did that above.
4790 update_download_tabs(t);
4792 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4793 g_free(page);
4795 return (0);
4799 search(struct tab *t, struct karg *args)
4801 gboolean d;
4803 if (t == NULL || args == NULL) {
4804 show_oops(NULL, "search invalid parameters");
4805 return (1);
4807 if (t->search_text == NULL) {
4808 if (global_search == NULL)
4809 return (XT_CB_PASSTHROUGH);
4810 else {
4811 t->search_text = g_strdup(global_search);
4812 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4813 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4817 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4818 t->tab_id, args->i, t->search_forward, t->search_text);
4820 switch (args->i) {
4821 case XT_SEARCH_NEXT:
4822 d = t->search_forward;
4823 break;
4824 case XT_SEARCH_PREV:
4825 d = !t->search_forward;
4826 break;
4827 default:
4828 return (XT_CB_PASSTHROUGH);
4831 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4833 return (XT_CB_HANDLED);
4836 struct settings_args {
4837 char **body;
4838 int i;
4841 void
4842 print_setting(struct settings *s, char *val, void *cb_args)
4844 char *tmp, *color;
4845 struct settings_args *sa = cb_args;
4847 if (sa == NULL)
4848 return;
4850 if (s->flags & XT_SF_RUNTIME)
4851 color = "#22cc22";
4852 else
4853 color = "#cccccc";
4855 tmp = *sa->body;
4856 *sa->body = g_strdup_printf(
4857 "%s\n<tr>"
4858 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4859 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4860 *sa->body,
4861 color,
4862 s->name,
4863 color,
4866 g_free(tmp);
4867 sa->i++;
4871 set(struct tab *t, struct karg *args)
4873 char *body, *page, *tmp;
4874 int i = 1;
4875 struct settings_args sa;
4877 bzero(&sa, sizeof sa);
4878 sa.body = &body;
4880 /* body */
4881 body = g_strdup_printf("<div align='center'><table><tr>"
4882 "<th align='left'>Setting</th>"
4883 "<th align='left'>Value</th></tr>\n");
4885 settings_walk(print_setting, &sa);
4886 i = sa.i;
4888 /* small message if there are none */
4889 if (i == 1) {
4890 tmp = body;
4891 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4892 "colspan='2'>No settings</td></tr>\n", body);
4893 g_free(tmp);
4896 tmp = body;
4897 body = g_strdup_printf("%s</table></div>", body);
4898 g_free(tmp);
4900 page = get_html_page("Settings", body, "", 0);
4902 g_free(body);
4904 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4906 g_free(page);
4908 return (XT_CB_PASSTHROUGH);
4912 session_save(struct tab *t, char *filename)
4914 struct karg a;
4915 int rv = 1;
4917 if (strlen(filename) == 0)
4918 goto done;
4920 if (filename[0] == '.' || filename[0] == '/')
4921 goto done;
4923 a.s = filename;
4924 if (save_tabs(t, &a))
4925 goto done;
4926 strlcpy(named_session, filename, sizeof named_session);
4928 rv = 0;
4929 done:
4930 return (rv);
4934 session_open(struct tab *t, char *filename)
4936 struct karg a;
4937 int rv = 1;
4939 if (strlen(filename) == 0)
4940 goto done;
4942 if (filename[0] == '.' || filename[0] == '/')
4943 goto done;
4945 a.s = filename;
4946 a.i = XT_SES_CLOSETABS;
4947 if (open_tabs(t, &a))
4948 goto done;
4950 strlcpy(named_session, filename, sizeof named_session);
4952 rv = 0;
4953 done:
4954 return (rv);
4958 session_delete(struct tab *t, char *filename)
4960 char file[PATH_MAX];
4961 int rv = 1;
4963 if (strlen(filename) == 0)
4964 goto done;
4966 if (filename[0] == '.' || filename[0] == '/')
4967 goto done;
4969 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4970 if (unlink(file))
4971 goto done;
4973 if (!strcmp(filename, named_session))
4974 strlcpy(named_session, XT_SAVED_TABS_FILE,
4975 sizeof named_session);
4977 rv = 0;
4978 done:
4979 return (rv);
4983 session_cmd(struct tab *t, struct karg *args)
4985 char *filename = args->s;
4987 if (t == NULL)
4988 return (1);
4990 if (args->i & XT_SHOW)
4991 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4992 XT_SAVED_TABS_FILE : named_session);
4993 else if (args->i & XT_SAVE) {
4994 if (session_save(t, filename)) {
4995 show_oops(t, "Can't save session: %s",
4996 filename ? filename : "INVALID");
4997 goto done;
4999 } else if (args->i & XT_OPEN) {
5000 if (session_open(t, filename)) {
5001 show_oops(t, "Can't open session: %s",
5002 filename ? filename : "INVALID");
5003 goto done;
5005 } else if (args->i & XT_DELETE) {
5006 if (session_delete(t, filename)) {
5007 show_oops(t, "Can't delete session: %s",
5008 filename ? filename : "INVALID");
5009 goto done;
5012 done:
5013 return (XT_CB_PASSTHROUGH);
5017 * Make a hardcopy of the page
5020 print_page(struct tab *t, struct karg *args)
5022 WebKitWebFrame *frame;
5023 GtkPageSetup *ps;
5024 GtkPrintOperation *op;
5025 GtkPrintOperationAction action;
5026 GtkPrintOperationResult print_res;
5027 GError *g_err = NULL;
5028 int marg_l, marg_r, marg_t, marg_b;
5030 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5032 ps = gtk_page_setup_new();
5033 op = gtk_print_operation_new();
5034 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5035 frame = webkit_web_view_get_main_frame(t->wv);
5037 /* the default margins are too small, so we will bump them */
5038 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5039 XT_PRINT_EXTRA_MARGIN;
5040 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5041 XT_PRINT_EXTRA_MARGIN;
5042 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5043 XT_PRINT_EXTRA_MARGIN;
5044 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5045 XT_PRINT_EXTRA_MARGIN;
5047 /* set margins */
5048 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5049 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5050 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5051 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5053 gtk_print_operation_set_default_page_setup(op, ps);
5055 /* this appears to free 'op' and 'ps' */
5056 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5058 /* check it worked */
5059 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5060 show_oops(NULL, "can't print: %s", g_err->message);
5061 g_error_free (g_err);
5062 return (1);
5065 return (0);
5069 go_home(struct tab *t, struct karg *args)
5071 load_uri(t, home);
5072 return (0);
5076 restart(struct tab *t, struct karg *args)
5078 struct karg a;
5080 a.s = XT_RESTART_TABS_FILE;
5081 save_tabs(t, &a);
5082 execvp(start_argv[0], start_argv);
5083 /* NOTREACHED */
5085 return (0);
5088 #define CTRL GDK_CONTROL_MASK
5089 #define MOD1 GDK_MOD1_MASK
5090 #define SHFT GDK_SHIFT_MASK
5092 /* inherent to GTK not all keys will be caught at all times */
5093 /* XXX sort key bindings */
5094 struct key_binding {
5095 char *cmd;
5096 guint mask;
5097 guint use_in_entry;
5098 guint key;
5099 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5100 } keys[] = {
5101 { "cookiejar", MOD1, 0, GDK_j },
5102 { "downloadmgr", MOD1, 0, GDK_d },
5103 { "history", MOD1, 0, GDK_h },
5104 { "print", CTRL, 0, GDK_p },
5105 { "search", 0, 0, GDK_slash },
5106 { "searchb", 0, 0, GDK_question },
5107 { "statustoggle", CTRL, 0, GDK_n },
5108 { "command", 0, 0, GDK_colon },
5109 { "qa", CTRL, 0, GDK_q },
5110 { "restart", MOD1, 0, GDK_q },
5111 { "js toggle", CTRL, 0, GDK_j },
5112 { "cookie toggle", MOD1, 0, GDK_c },
5113 { "togglesrc", CTRL, 0, GDK_s },
5114 { "yankuri", 0, 0, GDK_y },
5115 { "pasteuricur", 0, 0, GDK_p },
5116 { "pasteurinew", 0, 0, GDK_P },
5117 { "toplevel toggle", 0, 0, GDK_F4 },
5118 { "help", 0, 0, GDK_F1 },
5119 { "run_script", MOD1, 0, GDK_r },
5121 /* search */
5122 { "searchnext", 0, 0, GDK_n },
5123 { "searchprevious", 0, 0, GDK_N },
5125 /* focus */
5126 { "focusaddress", 0, 0, GDK_F6 },
5127 { "focussearch", 0, 0, GDK_F7 },
5129 /* hinting */
5130 { "hinting", 0, 0, GDK_f },
5132 /* custom stylesheet */
5133 { "userstyle", 0, 0, GDK_i },
5135 /* navigation */
5136 { "goback", 0, 0, GDK_BackSpace },
5137 { "goback", MOD1, 0, GDK_Left },
5138 { "goforward", SHFT, 0, GDK_BackSpace },
5139 { "goforward", MOD1, 0, GDK_Right },
5140 { "reload", 0, 0, GDK_F5 },
5141 { "reload", CTRL, 0, GDK_r },
5142 { "reload", CTRL, 0, GDK_l },
5143 { "favorites", MOD1, 1, GDK_f },
5145 /* vertical movement */
5146 { "scrolldown", 0, 0, GDK_j },
5147 { "scrolldown", 0, 0, GDK_Down },
5148 { "scrollup", 0, 0, GDK_Up },
5149 { "scrollup", 0, 0, GDK_k },
5150 { "scrollbottom", 0, 0, GDK_G },
5151 { "scrollbottom", 0, 0, GDK_End },
5152 { "scrolltop", 0, 0, GDK_Home },
5153 { "scrollpagedown", 0, 0, GDK_space },
5154 { "scrollpagedown", CTRL, 0, GDK_f },
5155 { "scrollhalfdown", CTRL, 0, GDK_d },
5156 { "scrollpagedown", 0, 0, GDK_Page_Down },
5157 { "scrollpageup", 0, 0, GDK_Page_Up },
5158 { "scrollpageup", CTRL, 0, GDK_b },
5159 { "scrollhalfup", CTRL, 0, GDK_u },
5160 /* horizontal movement */
5161 { "scrollright", 0, 0, GDK_l },
5162 { "scrollright", 0, 0, GDK_Right },
5163 { "scrollleft", 0, 0, GDK_Left },
5164 { "scrollleft", 0, 0, GDK_h },
5165 { "scrollfarright", 0, 0, GDK_dollar },
5166 { "scrollfarleft", 0, 0, GDK_0 },
5168 /* tabs */
5169 { "tabnew", CTRL, 0, GDK_t },
5170 { "999tabnew", CTRL, 0, GDK_T },
5171 { "tabclose", CTRL, 1, GDK_w },
5172 { "tabundoclose", 0, 0, GDK_U },
5173 { "tabnext 1", CTRL, 0, GDK_1 },
5174 { "tabnext 2", CTRL, 0, GDK_2 },
5175 { "tabnext 3", CTRL, 0, GDK_3 },
5176 { "tabnext 4", CTRL, 0, GDK_4 },
5177 { "tabnext 5", CTRL, 0, GDK_5 },
5178 { "tabnext 6", CTRL, 0, GDK_6 },
5179 { "tabnext 7", CTRL, 0, GDK_7 },
5180 { "tabnext 8", CTRL, 0, GDK_8 },
5181 { "tabnext 9", CTRL, 0, GDK_9 },
5182 { "tabfirst", CTRL, 0, GDK_less },
5183 { "tablast", CTRL, 0, GDK_greater },
5184 { "tabprevious", CTRL, 0, GDK_Left },
5185 { "tabnext", CTRL, 0, GDK_Right },
5186 { "focusout", CTRL, 0, GDK_minus },
5187 { "focusin", CTRL, 0, GDK_plus },
5188 { "focusin", CTRL, 0, GDK_equal },
5189 { "focusreset", CTRL, 0, GDK_0 },
5191 /* command aliases (handy when -S flag is used) */
5192 { "promptopen", 0, 0, GDK_F9 },
5193 { "promptopencurrent", 0, 0, GDK_F10 },
5194 { "prompttabnew", 0, 0, GDK_F11 },
5195 { "prompttabnewcurrent",0, 0, GDK_F12 },
5197 TAILQ_HEAD(keybinding_list, key_binding);
5199 void
5200 walk_kb(struct settings *s,
5201 void (*cb)(struct settings *, char *, void *), void *cb_args)
5203 struct key_binding *k;
5204 char str[1024];
5206 if (s == NULL || cb == NULL) {
5207 show_oops(NULL, "walk_kb invalid parameters");
5208 return;
5211 TAILQ_FOREACH(k, &kbl, entry) {
5212 if (k->cmd == NULL)
5213 continue;
5214 str[0] = '\0';
5216 /* sanity */
5217 if (gdk_keyval_name(k->key) == NULL)
5218 continue;
5220 strlcat(str, k->cmd, sizeof str);
5221 strlcat(str, ",", sizeof str);
5223 if (k->mask & GDK_SHIFT_MASK)
5224 strlcat(str, "S-", sizeof str);
5225 if (k->mask & GDK_CONTROL_MASK)
5226 strlcat(str, "C-", sizeof str);
5227 if (k->mask & GDK_MOD1_MASK)
5228 strlcat(str, "M1-", sizeof str);
5229 if (k->mask & GDK_MOD2_MASK)
5230 strlcat(str, "M2-", sizeof str);
5231 if (k->mask & GDK_MOD3_MASK)
5232 strlcat(str, "M3-", sizeof str);
5233 if (k->mask & GDK_MOD4_MASK)
5234 strlcat(str, "M4-", sizeof str);
5235 if (k->mask & GDK_MOD5_MASK)
5236 strlcat(str, "M5-", sizeof str);
5238 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5239 cb(s, str, cb_args);
5243 void
5244 init_keybindings(void)
5246 int i;
5247 struct key_binding *k;
5249 for (i = 0; i < LENGTH(keys); i++) {
5250 k = g_malloc0(sizeof *k);
5251 k->cmd = keys[i].cmd;
5252 k->mask = keys[i].mask;
5253 k->use_in_entry = keys[i].use_in_entry;
5254 k->key = keys[i].key;
5255 TAILQ_INSERT_HEAD(&kbl, k, entry);
5257 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5258 k->cmd ? k->cmd : "unnamed key");
5262 void
5263 keybinding_clearall(void)
5265 struct key_binding *k, *next;
5267 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5268 next = TAILQ_NEXT(k, entry);
5269 if (k->cmd == NULL)
5270 continue;
5272 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5273 k->cmd ? k->cmd : "unnamed key");
5274 TAILQ_REMOVE(&kbl, k, entry);
5275 g_free(k);
5280 keybinding_add(char *cmd, char *key, int use_in_entry)
5282 struct key_binding *k;
5283 guint keyval, mask = 0;
5284 int i;
5286 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5288 /* Keys which are to be used in entry have been prefixed with an
5289 * exclamation mark. */
5290 if (use_in_entry)
5291 key++;
5293 /* find modifier keys */
5294 if (strstr(key, "S-"))
5295 mask |= GDK_SHIFT_MASK;
5296 if (strstr(key, "C-"))
5297 mask |= GDK_CONTROL_MASK;
5298 if (strstr(key, "M1-"))
5299 mask |= GDK_MOD1_MASK;
5300 if (strstr(key, "M2-"))
5301 mask |= GDK_MOD2_MASK;
5302 if (strstr(key, "M3-"))
5303 mask |= GDK_MOD3_MASK;
5304 if (strstr(key, "M4-"))
5305 mask |= GDK_MOD4_MASK;
5306 if (strstr(key, "M5-"))
5307 mask |= GDK_MOD5_MASK;
5309 /* find keyname */
5310 for (i = strlen(key) - 1; i > 0; i--)
5311 if (key[i] == '-')
5312 key = &key[i + 1];
5314 /* validate keyname */
5315 keyval = gdk_keyval_from_name(key);
5316 if (keyval == GDK_VoidSymbol) {
5317 warnx("invalid keybinding name %s", key);
5318 return (1);
5320 /* must run this test too, gtk+ doesn't handle 10 for example */
5321 if (gdk_keyval_name(keyval) == NULL) {
5322 warnx("invalid keybinding name %s", key);
5323 return (1);
5326 /* Remove eventual dupes. */
5327 TAILQ_FOREACH(k, &kbl, entry)
5328 if (k->key == keyval && k->mask == mask) {
5329 TAILQ_REMOVE(&kbl, k, entry);
5330 g_free(k);
5331 break;
5334 /* add keyname */
5335 k = g_malloc0(sizeof *k);
5336 k->cmd = g_strdup(cmd);
5337 k->mask = mask;
5338 k->use_in_entry = use_in_entry;
5339 k->key = keyval;
5341 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5342 k->cmd,
5343 k->mask,
5344 k->use_in_entry,
5345 k->key);
5346 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5347 k->cmd, gdk_keyval_name(keyval));
5349 TAILQ_INSERT_HEAD(&kbl, k, entry);
5351 return (0);
5355 add_kb(struct settings *s, char *entry)
5357 char *kb, *key;
5359 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5361 /* clearall is special */
5362 if (!strcmp(entry, "clearall")) {
5363 keybinding_clearall();
5364 return (0);
5367 kb = strstr(entry, ",");
5368 if (kb == NULL)
5369 return (1);
5370 *kb = '\0';
5371 key = kb + 1;
5373 return (keybinding_add(entry, key, key[0] == '!'));
5376 struct cmd {
5377 char *cmd;
5378 int level;
5379 int (*func)(struct tab *, struct karg *);
5380 int arg;
5381 int type;
5382 } cmds[] = {
5383 { "command", 0, command, ':', 0 },
5384 { "search", 0, command, '/', 0 },
5385 { "searchb", 0, command, '?', 0 },
5386 { "togglesrc", 0, toggle_src, 0, 0 },
5388 /* yanking and pasting */
5389 { "yankuri", 0, yank_uri, 0, 0 },
5390 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5391 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5392 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5394 /* search */
5395 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5396 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5398 /* focus */
5399 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5400 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5402 /* hinting */
5403 { "hinting", 0, hint, 0, 0 },
5405 /* custom stylesheet */
5406 { "userstyle", 0, userstyle, 0, 0 },
5408 /* navigation */
5409 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5410 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5411 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5413 /* vertical movement */
5414 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5415 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5416 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5417 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5418 { "1", 0, move, XT_MOVE_TOP, 0 },
5419 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5420 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5421 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5422 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5423 /* horizontal movement */
5424 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5425 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5426 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5427 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5429 { "favorites", 0, xtp_page_fl, 0, 0 },
5430 { "fav", 0, xtp_page_fl, 0, 0 },
5431 { "favadd", 0, add_favorite, 0, 0 },
5433 { "qall", 0, quit, 0, 0 },
5434 { "quitall", 0, quit, 0, 0 },
5435 { "w", 0, save_tabs, 0, 0 },
5436 { "wq", 0, save_tabs_and_quit, 0, 0 },
5437 { "help", 0, help, 0, 0 },
5438 { "about", 0, about, 0, 0 },
5439 { "stats", 0, stats, 0, 0 },
5440 { "version", 0, about, 0, 0 },
5442 /* js command */
5443 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5444 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5445 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5446 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5447 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5448 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5449 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5450 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5451 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5452 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5453 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5455 /* cookie command */
5456 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5457 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5458 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5459 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5460 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5461 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5462 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5463 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5464 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5465 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5466 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5468 /* toplevel (domain) command */
5469 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5470 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5472 /* cookie jar */
5473 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5475 /* cert command */
5476 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5477 { "save", 1, cert_cmd, XT_SAVE, 0 },
5478 { "show", 1, cert_cmd, XT_SHOW, 0 },
5480 { "ca", 0, ca_cmd, 0, 0 },
5481 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5482 { "dl", 0, xtp_page_dl, 0, 0 },
5483 { "h", 0, xtp_page_hl, 0, 0 },
5484 { "history", 0, xtp_page_hl, 0, 0 },
5485 { "home", 0, go_home, 0, 0 },
5486 { "restart", 0, restart, 0, 0 },
5487 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5488 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5489 { "statustoggle", 0, statustoggle, 0, 0 },
5490 { "run_script", 0, run_page_script, 0, XT_USERARG },
5492 { "print", 0, print_page, 0, 0 },
5494 /* tabs */
5495 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5496 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5497 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5498 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5499 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5500 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5501 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5502 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5503 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5504 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5505 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5506 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5507 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5508 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5509 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5510 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5511 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5512 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5513 { "buffers", 0, buffers, 0, 0 },
5514 { "ls", 0, buffers, 0, 0 },
5515 { "tabs", 0, buffers, 0, 0 },
5517 /* command aliases (handy when -S flag is used) */
5518 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5519 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5520 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5521 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5523 /* settings */
5524 { "set", 0, set, 0, 0 },
5525 { "fullscreen", 0, fullscreen, 0, 0 },
5526 { "f", 0, fullscreen, 0, 0 },
5528 /* sessions */
5529 { "session", 0, session_cmd, XT_SHOW, 0 },
5530 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5531 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5532 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5533 { "show", 1, session_cmd, XT_SHOW, 0 },
5536 struct {
5537 int index;
5538 int len;
5539 gchar *list[256];
5540 } cmd_status = {-1, 0};
5542 gboolean
5543 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5546 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5547 btn_down = 0;
5549 return (FALSE);
5552 gboolean
5553 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5555 struct karg a;
5557 hide_oops(t);
5558 hide_buffers(t);
5560 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5561 btn_down = 1;
5562 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5563 /* go backward */
5564 a.i = XT_NAV_BACK;
5565 navaction(t, &a);
5567 return (TRUE);
5568 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5569 /* go forward */
5570 a.i = XT_NAV_FORWARD;
5571 navaction(t, &a);
5573 return (TRUE);
5576 return (FALSE);
5579 gboolean
5580 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5582 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5584 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5585 delete_tab(t);
5587 return (FALSE);
5591 * cancel, remove, etc. downloads
5593 void
5594 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5596 struct download find, *d = NULL;
5598 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5600 /* some commands require a valid download id */
5601 if (cmd != XT_XTP_DL_LIST) {
5602 /* lookup download in question */
5603 find.id = id;
5604 d = RB_FIND(download_list, &downloads, &find);
5606 if (d == NULL) {
5607 show_oops(t, "%s: no such download", __func__);
5608 return;
5612 /* decide what to do */
5613 switch (cmd) {
5614 case XT_XTP_DL_CANCEL:
5615 webkit_download_cancel(d->download);
5616 break;
5617 case XT_XTP_DL_REMOVE:
5618 webkit_download_cancel(d->download); /* just incase */
5619 g_object_unref(d->download);
5620 RB_REMOVE(download_list, &downloads, d);
5621 break;
5622 case XT_XTP_DL_LIST:
5623 /* Nothing */
5624 break;
5625 default:
5626 show_oops(t, "%s: unknown command", __func__);
5627 break;
5629 xtp_page_dl(t, NULL);
5633 * Actions on history, only does one thing for now, but
5634 * we provide the function for future actions
5636 void
5637 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5639 struct history *h, *next;
5640 int i = 1;
5642 switch (cmd) {
5643 case XT_XTP_HL_REMOVE:
5644 /* walk backwards, as listed in reverse */
5645 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5646 next = RB_PREV(history_list, &hl, h);
5647 if (id == i) {
5648 RB_REMOVE(history_list, &hl, h);
5649 g_free((gpointer) h->title);
5650 g_free((gpointer) h->uri);
5651 g_free(h);
5652 break;
5654 i++;
5656 break;
5657 case XT_XTP_HL_LIST:
5658 /* Nothing - just xtp_page_hl() below */
5659 break;
5660 default:
5661 show_oops(t, "%s: unknown command", __func__);
5662 break;
5665 xtp_page_hl(t, NULL);
5668 /* remove a favorite */
5669 void
5670 remove_favorite(struct tab *t, int index)
5672 char file[PATH_MAX], *title, *uri = NULL;
5673 char *new_favs, *tmp;
5674 FILE *f;
5675 int i;
5676 size_t len, lineno;
5678 /* open favorites */
5679 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5681 if ((f = fopen(file, "r")) == NULL) {
5682 show_oops(t, "%s: can't open favorites: %s",
5683 __func__, strerror(errno));
5684 return;
5687 /* build a string which will become the new favroites file */
5688 new_favs = g_strdup("");
5690 for (i = 1;;) {
5691 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5692 if (feof(f) || ferror(f))
5693 break;
5694 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5695 if (len == 0) {
5696 free(title);
5697 title = NULL;
5698 continue;
5701 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5702 if (feof(f) || ferror(f)) {
5703 show_oops(t, "%s: can't parse favorites %s",
5704 __func__, strerror(errno));
5705 goto clean;
5709 /* as long as this isn't the one we are deleting add to file */
5710 if (i != index) {
5711 tmp = new_favs;
5712 new_favs = g_strdup_printf("%s%s\n%s\n",
5713 new_favs, title, uri);
5714 g_free(tmp);
5717 free(uri);
5718 uri = NULL;
5719 free(title);
5720 title = NULL;
5721 i++;
5723 fclose(f);
5725 /* write back new favorites file */
5726 if ((f = fopen(file, "w")) == NULL) {
5727 show_oops(t, "%s: can't open favorites: %s",
5728 __func__, strerror(errno));
5729 goto clean;
5732 fwrite(new_favs, strlen(new_favs), 1, f);
5733 fclose(f);
5735 clean:
5736 if (uri)
5737 free(uri);
5738 if (title)
5739 free(title);
5741 g_free(new_favs);
5744 void
5745 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5747 switch (cmd) {
5748 case XT_XTP_FL_LIST:
5749 /* nothing, just the below call to xtp_page_fl() */
5750 break;
5751 case XT_XTP_FL_REMOVE:
5752 remove_favorite(t, arg);
5753 break;
5754 default:
5755 show_oops(t, "%s: invalid favorites command", __func__);
5756 break;
5759 xtp_page_fl(t, NULL);
5762 void
5763 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5765 switch (cmd) {
5766 case XT_XTP_CL_LIST:
5767 /* nothing, just xtp_page_cl() */
5768 break;
5769 case XT_XTP_CL_REMOVE:
5770 remove_cookie(arg);
5771 break;
5772 default:
5773 show_oops(t, "%s: unknown cookie xtp command", __func__);
5774 break;
5777 xtp_page_cl(t, NULL);
5780 /* link an XTP class to it's session key and handler function */
5781 struct xtp_despatch {
5782 uint8_t xtp_class;
5783 char **session_key;
5784 void (*handle_func)(struct tab *, uint8_t, int);
5787 struct xtp_despatch xtp_despatches[] = {
5788 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5789 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5790 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5791 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5792 { XT_XTP_INVALID, NULL, NULL }
5796 * is the url xtp protocol? (xxxt://)
5797 * if so, parse and despatch correct bahvior
5800 parse_xtp_url(struct tab *t, const char *url)
5802 char *dup = NULL, *p, *last;
5803 uint8_t n_tokens = 0;
5804 char *tokens[4] = {NULL, NULL, NULL, ""};
5805 struct xtp_despatch *dsp, *dsp_match = NULL;
5806 uint8_t req_class;
5807 int ret = FALSE;
5810 * tokens array meaning:
5811 * tokens[0] = class
5812 * tokens[1] = session key
5813 * tokens[2] = action
5814 * tokens[3] = optional argument
5817 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5819 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5820 goto clean;
5822 dup = g_strdup(url + strlen(XT_XTP_STR));
5824 /* split out the url */
5825 for ((p = strtok_r(dup, "/", &last)); p;
5826 (p = strtok_r(NULL, "/", &last))) {
5827 if (n_tokens < 4)
5828 tokens[n_tokens++] = p;
5831 /* should be atleast three fields 'class/seskey/command/arg' */
5832 if (n_tokens < 3)
5833 goto clean;
5835 dsp = xtp_despatches;
5836 req_class = atoi(tokens[0]);
5837 while (dsp->xtp_class) {
5838 if (dsp->xtp_class == req_class) {
5839 dsp_match = dsp;
5840 break;
5842 dsp++;
5845 /* did we find one atall? */
5846 if (dsp_match == NULL) {
5847 show_oops(t, "%s: no matching xtp despatch found", __func__);
5848 goto clean;
5851 /* check session key and call despatch function */
5852 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5853 ret = TRUE; /* all is well, this was a valid xtp request */
5854 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5857 clean:
5858 if (dup)
5859 g_free(dup);
5861 return (ret);
5866 void
5867 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5869 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5871 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5873 if (t == NULL) {
5874 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5875 return;
5878 if (uri == NULL) {
5879 show_oops(t, "activate_uri_entry_cb no uri");
5880 return;
5883 uri += strspn(uri, "\t ");
5885 /* if xxxt:// treat specially */
5886 if (parse_xtp_url(t, uri))
5887 return;
5889 /* otherwise continue to load page normally */
5890 load_uri(t, (gchar *)uri);
5891 focus_webview(t);
5894 void
5895 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5897 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5898 char *newuri = NULL;
5899 gchar *enc_search;
5901 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5903 if (t == NULL) {
5904 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5905 return;
5908 if (search_string == NULL) {
5909 show_oops(t, "no search_string");
5910 return;
5913 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5915 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5916 newuri = g_strdup_printf(search_string, enc_search);
5917 g_free(enc_search);
5919 marks_clear(t);
5920 webkit_web_view_load_uri(t->wv, newuri);
5921 focus_webview(t);
5923 if (newuri)
5924 g_free(newuri);
5927 void
5928 check_and_set_cookie(const gchar *uri, struct tab *t)
5930 struct domain *d = NULL;
5931 int es = 0;
5933 if (uri == NULL || t == NULL)
5934 return;
5936 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
5937 es = 0;
5938 else
5939 es = 1;
5941 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
5942 es ? "enable" : "disable", uri);
5944 g_object_set(G_OBJECT(t->settings),
5945 "enable-html5-local-storage", es, (char *)NULL);
5946 webkit_web_view_set_settings(t->wv, t->settings);
5949 void
5950 check_and_set_js(const gchar *uri, struct tab *t)
5952 struct domain *d = NULL;
5953 int es = 0;
5955 if (uri == NULL || t == NULL)
5956 return;
5958 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5959 es = 0;
5960 else
5961 es = 1;
5963 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5964 es ? "enable" : "disable", uri);
5966 g_object_set(G_OBJECT(t->settings),
5967 "enable-scripts", es, (char *)NULL);
5968 g_object_set(G_OBJECT(t->settings),
5969 "javascript-can-open-windows-automatically", es, (char *)NULL);
5970 webkit_web_view_set_settings(t->wv, t->settings);
5972 button_set_stockid(t->js_toggle,
5973 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5976 gboolean
5977 color_address_bar(gpointer p)
5979 GdkColor color;
5980 struct tab *tt, *t = p;
5981 gchar *col_str = XT_COLOR_YELLOW;
5983 DNPRINTF(XT_D_URL, "%s:\n", __func__);
5985 /* make sure t still exists */
5986 if (t == NULL)
5987 goto done;
5988 TAILQ_FOREACH(tt, &tabs, entry)
5989 if (t == tt)
5990 break;
5991 if (t != tt)
5992 goto done;
5994 switch (load_compare_cert(t, NULL)) {
5995 case CERT_LOCAL:
5996 col_str = XT_COLOR_BLUE;
5997 break;
5998 case CERT_TRUSTED:
5999 col_str = XT_COLOR_GREEN;
6000 break;
6001 case CERT_UNTRUSTED:
6002 col_str = XT_COLOR_YELLOW;
6003 break;
6004 case CERT_BAD:
6005 col_str = XT_COLOR_RED;
6006 break;
6009 gdk_color_parse(col_str, &color);
6010 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6012 if (!strcmp(col_str, XT_COLOR_WHITE))
6013 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6014 else
6015 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6017 col_str = NULL;
6018 done:
6019 return (FALSE /* kill thread */);
6022 void
6023 show_ca_status(struct tab *t, const char *uri)
6025 GdkColor color;
6026 gchar *col_str = XT_COLOR_WHITE;
6028 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6029 ssl_strict_certs, ssl_ca_file, uri);
6031 if (t == NULL)
6032 return;
6034 if (uri == NULL)
6035 goto done;
6036 if (ssl_ca_file == NULL) {
6037 if (g_str_has_prefix(uri, "http://"))
6038 goto done;
6039 if (g_str_has_prefix(uri, "https://")) {
6040 col_str = XT_COLOR_RED;
6041 goto done;
6043 return;
6045 if (g_str_has_prefix(uri, "http://") ||
6046 !g_str_has_prefix(uri, "https://"))
6047 goto done;
6049 /* thread the coloring of the address bar */
6050 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6051 color_address_bar, t, NULL);
6052 return;
6054 done:
6055 if (col_str) {
6056 gdk_color_parse(col_str, &color);
6057 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6059 if (!strcmp(col_str, XT_COLOR_WHITE))
6060 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6061 else
6062 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6066 void
6067 free_favicon(struct tab *t)
6069 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6070 __func__, t->icon_download, t->icon_request);
6072 if (t->icon_request)
6073 g_object_unref(t->icon_request);
6074 if (t->icon_dest_uri)
6075 g_free(t->icon_dest_uri);
6077 t->icon_request = NULL;
6078 t->icon_dest_uri = NULL;
6081 void
6082 xt_icon_from_name(struct tab *t, gchar *name)
6084 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6085 GTK_ENTRY_ICON_PRIMARY, "text-html");
6086 if (show_url == 0)
6087 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6088 GTK_ENTRY_ICON_PRIMARY, "text-html");
6089 else
6090 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6091 GTK_ENTRY_ICON_PRIMARY, NULL);
6094 void
6095 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6097 GdkPixbuf *pb_scaled;
6099 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6100 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6101 GDK_INTERP_BILINEAR);
6102 else
6103 pb_scaled = pb;
6105 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6106 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6107 if (show_url == 0)
6108 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6109 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6110 else
6111 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6112 GTK_ENTRY_ICON_PRIMARY, NULL);
6114 if (pb_scaled != pb)
6115 g_object_unref(pb_scaled);
6118 void
6119 xt_icon_from_file(struct tab *t, char *file)
6121 GdkPixbuf *pb;
6123 if (g_str_has_prefix(file, "file://"))
6124 file += strlen("file://");
6126 pb = gdk_pixbuf_new_from_file(file, NULL);
6127 if (pb) {
6128 xt_icon_from_pixbuf(t, pb);
6129 g_object_unref(pb);
6130 } else
6131 xt_icon_from_name(t, "text-html");
6134 gboolean
6135 is_valid_icon(char *file)
6137 gboolean valid = 0;
6138 const char *mime_type;
6139 GFileInfo *fi;
6140 GFile *gf;
6142 gf = g_file_new_for_path(file);
6143 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6144 NULL, NULL);
6145 mime_type = g_file_info_get_content_type(fi);
6146 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6147 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6148 g_strcmp0(mime_type, "image/png") == 0 ||
6149 g_strcmp0(mime_type, "image/gif") == 0 ||
6150 g_strcmp0(mime_type, "application/octet-stream") == 0;
6151 g_object_unref(fi);
6152 g_object_unref(gf);
6154 return (valid);
6157 void
6158 set_favicon_from_file(struct tab *t, char *file)
6160 struct stat sb;
6162 if (t == NULL || file == NULL)
6163 return;
6165 if (g_str_has_prefix(file, "file://"))
6166 file += strlen("file://");
6167 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6169 if (!stat(file, &sb)) {
6170 if (sb.st_size == 0 || !is_valid_icon(file)) {
6171 /* corrupt icon so trash it */
6172 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6173 __func__, file);
6174 unlink(file);
6175 /* no need to set icon to default here */
6176 return;
6179 xt_icon_from_file(t, file);
6182 void
6183 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6184 WebKitWebView *wv)
6186 WebKitDownloadStatus status = webkit_download_get_status(download);
6187 struct tab *tt = NULL, *t = NULL;
6190 * find the webview instead of passing in the tab as it could have been
6191 * deleted from underneath us.
6193 TAILQ_FOREACH(tt, &tabs, entry) {
6194 if (tt->wv == wv) {
6195 t = tt;
6196 break;
6199 if (t == NULL)
6200 return;
6202 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6203 __func__, t->tab_id, status);
6205 switch (status) {
6206 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6207 /* -1 */
6208 t->icon_download = NULL;
6209 free_favicon(t);
6210 break;
6211 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6212 /* 0 */
6213 break;
6214 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6215 /* 1 */
6216 break;
6217 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6218 /* 2 */
6219 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6220 __func__, t->tab_id);
6221 t->icon_download = NULL;
6222 free_favicon(t);
6223 break;
6224 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6225 /* 3 */
6227 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6228 __func__, t->icon_dest_uri);
6229 set_favicon_from_file(t, t->icon_dest_uri);
6230 /* these will be freed post callback */
6231 t->icon_request = NULL;
6232 t->icon_download = NULL;
6233 break;
6234 default:
6235 break;
6239 void
6240 abort_favicon_download(struct tab *t)
6242 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6244 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6245 if (t->icon_download) {
6246 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6247 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6248 webkit_download_cancel(t->icon_download);
6249 t->icon_download = NULL;
6250 } else
6251 free_favicon(t);
6252 #endif
6254 xt_icon_from_name(t, "text-html");
6257 void
6258 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6260 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6262 if (uri == NULL || t == NULL)
6263 return;
6265 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6266 /* take icon from WebKitIconDatabase */
6267 GdkPixbuf *pb;
6269 pb = webkit_web_view_get_icon_pixbuf(wv);
6270 if (pb) {
6271 xt_icon_from_pixbuf(t, pb);
6272 g_object_unref(pb);
6273 } else
6274 xt_icon_from_name(t, "text-html");
6275 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6276 /* download icon to cache dir */
6277 gchar *name_hash, file[PATH_MAX];
6278 struct stat sb;
6280 if (t->icon_request) {
6281 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6282 return;
6285 /* check to see if we got the icon in cache */
6286 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6287 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6288 g_free(name_hash);
6290 if (!stat(file, &sb)) {
6291 if (sb.st_size > 0) {
6292 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6293 __func__, file);
6294 set_favicon_from_file(t, file);
6295 return;
6298 /* corrupt icon so trash it */
6299 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6300 __func__, file);
6301 unlink(file);
6304 /* create download for icon */
6305 t->icon_request = webkit_network_request_new(uri);
6306 if (t->icon_request == NULL) {
6307 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6308 __func__, uri);
6309 return;
6312 t->icon_download = webkit_download_new(t->icon_request);
6313 if (t->icon_download == NULL)
6314 return;
6316 /* we have to free icon_dest_uri later */
6317 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6318 webkit_download_set_destination_uri(t->icon_download,
6319 t->icon_dest_uri);
6321 if (webkit_download_get_status(t->icon_download) ==
6322 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6323 g_object_unref(t->icon_request);
6324 g_free(t->icon_dest_uri);
6325 t->icon_request = NULL;
6326 t->icon_dest_uri = NULL;
6327 return;
6330 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6331 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6333 webkit_download_start(t->icon_download);
6334 #endif
6337 void
6338 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6340 const gchar *uri = NULL, *title = NULL;
6341 struct history *h, find;
6342 struct karg a;
6343 GdkColor color;
6345 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6346 webkit_web_view_get_load_status(wview),
6347 get_uri(t) ? get_uri(t) : "NOTHING");
6349 if (t == NULL) {
6350 show_oops(NULL, "notify_load_status_cb invalid parameters");
6351 return;
6354 switch (webkit_web_view_get_load_status(wview)) {
6355 case WEBKIT_LOAD_PROVISIONAL:
6356 /* 0 */
6357 abort_favicon_download(t);
6358 #if GTK_CHECK_VERSION(2, 20, 0)
6359 gtk_widget_show(t->spinner);
6360 gtk_spinner_start(GTK_SPINNER(t->spinner));
6361 #endif
6362 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6364 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6366 /* assume we are a new address */
6367 gdk_color_parse("white", &color);
6368 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6369 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6371 /* take focus if we are visible */
6372 focus_webview(t);
6373 t->focus_wv = 1;
6375 break;
6377 case WEBKIT_LOAD_COMMITTED:
6378 /* 1 */
6379 uri = get_uri(t);
6380 if (uri == NULL)
6381 return;
6382 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6384 if (t->status) {
6385 g_free(t->status);
6386 t->status = NULL;
6388 set_status(t, (char *)uri, XT_STATUS_LOADING);
6390 /* check if js white listing is enabled */
6391 if (enable_cookie_whitelist)
6392 check_and_set_cookie(uri, t);
6393 if (enable_js_whitelist)
6394 check_and_set_js(uri, t);
6396 if (t->styled)
6397 apply_style(t);
6400 /* we know enough to autosave the session */
6401 if (session_autosave) {
6402 a.s = NULL;
6403 save_tabs(t, &a);
6406 show_ca_status(t, uri);
6407 break;
6409 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6410 /* 3 */
6411 break;
6413 case WEBKIT_LOAD_FINISHED:
6414 /* 2 */
6415 uri = get_uri(t);
6416 if (uri == NULL)
6417 return;
6419 if (!strncmp(uri, "http://", strlen("http://")) ||
6420 !strncmp(uri, "https://", strlen("https://")) ||
6421 !strncmp(uri, "file://", strlen("file://"))) {
6422 find.uri = uri;
6423 h = RB_FIND(history_list, &hl, &find);
6424 if (!h) {
6425 title = get_title(t, FALSE);
6426 h = g_malloc(sizeof *h);
6427 h->uri = g_strdup(uri);
6428 h->title = g_strdup(title);
6429 RB_INSERT(history_list, &hl, h);
6430 completion_add_uri(h->uri);
6431 update_history_tabs(NULL);
6435 set_status(t, (char *)uri, XT_STATUS_URI);
6436 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6437 case WEBKIT_LOAD_FAILED:
6438 /* 4 */
6439 #endif
6440 #if GTK_CHECK_VERSION(2, 20, 0)
6441 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6442 gtk_widget_hide(t->spinner);
6443 #endif
6444 default:
6445 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6446 break;
6449 if (t->item)
6450 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6451 else
6452 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6453 webkit_web_view_can_go_back(wview));
6455 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6456 webkit_web_view_can_go_forward(wview));
6459 #if 0
6460 gboolean
6461 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6462 gchar *uri, gpointer web_error,struct tab *t)
6465 * XXX this function is wrong
6466 * it overwrites perfectly good urls with garbage on load errors
6467 * those happen often when popups fail to resolve dns
6469 if (t->tmp_uri)
6470 g_free(t->tmp_uri);
6471 t->tmp_uri = g_strdup(uri);
6472 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6473 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6474 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6475 set_status(t, uri, XT_STATUS_NOTHING);
6477 return (FALSE);
6479 #endif
6481 void
6482 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6484 const gchar *title = NULL, *win_title = NULL;
6486 title = get_title(t, FALSE);
6487 win_title = get_title(t, TRUE);
6488 gtk_label_set_text(GTK_LABEL(t->label), title);
6489 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6490 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6491 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6494 void
6495 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6497 run_script(t, JS_HINTING);
6500 void
6501 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6503 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6504 progress == 100 ? 0 : (double)progress / 100);
6505 if (show_url == 0) {
6506 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6507 progress == 100 ? 0 : (double)progress / 100);
6510 update_statusbar_position(NULL, NULL);
6514 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6515 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6516 WebKitWebPolicyDecision *pd, struct tab *t)
6518 char *uri;
6519 WebKitWebNavigationReason reason;
6520 struct domain *d = NULL;
6522 if (t == NULL) {
6523 show_oops(NULL, "webview_npd_cb invalid parameters");
6524 return (FALSE);
6527 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6528 t->ctrl_click,
6529 webkit_network_request_get_uri(request));
6531 uri = (char *)webkit_network_request_get_uri(request);
6533 /* if this is an xtp url, we don't load anything else */
6534 if (parse_xtp_url(t, uri))
6535 return (TRUE);
6537 if (t->ctrl_click) {
6538 t->ctrl_click = 0;
6539 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6540 webkit_web_policy_decision_ignore(pd);
6541 return (TRUE); /* we made the decission */
6545 * This is a little hairy but it comes down to this:
6546 * when we run in whitelist mode we have to assist the browser in
6547 * opening the URL that it would have opened in a new tab.
6549 reason = webkit_web_navigation_action_get_reason(na);
6550 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6551 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6552 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6553 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6554 load_uri(t, uri);
6555 webkit_web_policy_decision_use(pd);
6556 return (TRUE); /* we made the decision */
6559 return (FALSE);
6562 WebKitWebView *
6563 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6565 struct tab *tt;
6566 struct domain *d = NULL;
6567 const gchar *uri;
6568 WebKitWebView *webview = NULL;
6570 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6571 webkit_web_view_get_uri(wv));
6573 if (tabless) {
6574 /* open in current tab */
6575 webview = t->wv;
6576 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6577 uri = webkit_web_view_get_uri(wv);
6578 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6579 return (NULL);
6581 tt = create_new_tab(NULL, NULL, 1, -1);
6582 webview = tt->wv;
6583 } else if (enable_scripts == 1) {
6584 tt = create_new_tab(NULL, NULL, 1, -1);
6585 webview = tt->wv;
6588 return (webview);
6591 gboolean
6592 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6594 const gchar *uri;
6595 struct domain *d = NULL;
6597 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6599 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6600 uri = webkit_web_view_get_uri(wv);
6601 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6602 return (FALSE);
6604 delete_tab(t);
6605 } else if (enable_scripts == 1)
6606 delete_tab(t);
6608 return (TRUE);
6612 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6614 /* we can not eat the event without throwing gtk off so defer it */
6616 /* catch middle click */
6617 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6618 t->ctrl_click = 1;
6619 goto done;
6622 /* catch ctrl click */
6623 if (e->type == GDK_BUTTON_RELEASE &&
6624 CLEAN(e->state) == GDK_CONTROL_MASK)
6625 t->ctrl_click = 1;
6626 else
6627 t->ctrl_click = 0;
6628 done:
6629 return (XT_CB_PASSTHROUGH);
6633 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6635 struct mime_type *m;
6637 m = find_mime_type(mime_type);
6638 if (m == NULL)
6639 return (1);
6640 if (m->mt_download)
6641 return (1);
6643 switch (fork()) {
6644 case -1:
6645 show_oops(t, "can't fork mime handler");
6646 return (1);
6647 /* NOTREACHED */
6648 case 0:
6649 break;
6650 default:
6651 return (0);
6654 /* child */
6655 execlp(m->mt_action, m->mt_action,
6656 webkit_network_request_get_uri(request), (void *)NULL);
6658 _exit(0);
6660 /* NOTREACHED */
6661 return (0);
6664 const gchar *
6665 get_mime_type(char *file)
6667 const char *mime_type;
6668 GFileInfo *fi;
6669 GFile *gf;
6671 if (g_str_has_prefix(file, "file://"))
6672 file += strlen("file://");
6674 gf = g_file_new_for_path(file);
6675 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6676 NULL, NULL);
6677 mime_type = g_file_info_get_content_type(fi);
6678 g_object_unref(fi);
6679 g_object_unref(gf);
6681 return (mime_type);
6685 run_download_mimehandler(char *mime_type, char *file)
6687 struct mime_type *m;
6689 m = find_mime_type(mime_type);
6690 if (m == NULL)
6691 return (1);
6693 switch (fork()) {
6694 case -1:
6695 show_oops(NULL, "can't fork download mime handler");
6696 return (1);
6697 /* NOTREACHED */
6698 case 0:
6699 break;
6700 default:
6701 return (0);
6704 /* child */
6705 if (g_str_has_prefix(file, "file://"))
6706 file += strlen("file://");
6707 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6709 _exit(0);
6711 /* NOTREACHED */
6712 return (0);
6715 void
6716 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6717 WebKitWebView *wv)
6719 WebKitDownloadStatus status;
6720 const gchar *file = NULL, *mime = NULL;
6722 if (download == NULL)
6723 return;
6724 status = webkit_download_get_status(download);
6725 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6726 return;
6728 file = webkit_download_get_destination_uri(download);
6729 if (file == NULL)
6730 return;
6731 mime = get_mime_type((char *)file);
6732 if (mime == NULL)
6733 return;
6735 run_download_mimehandler((char *)mime, (char *)file);
6739 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6740 WebKitNetworkRequest *request, char *mime_type,
6741 WebKitWebPolicyDecision *decision, struct tab *t)
6743 if (t == NULL) {
6744 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6745 return (FALSE);
6748 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6749 t->tab_id, mime_type);
6751 if (run_mimehandler(t, mime_type, request) == 0) {
6752 webkit_web_policy_decision_ignore(decision);
6753 focus_webview(t);
6754 return (TRUE);
6757 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6758 webkit_web_policy_decision_download(decision);
6759 return (TRUE);
6762 return (FALSE);
6766 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6767 struct tab *t)
6769 struct stat sb;
6770 const gchar *suggested_name;
6771 gchar *filename = NULL;
6772 char *uri = NULL;
6773 struct download *download_entry;
6774 int i, ret = TRUE;
6776 if (wk_download == NULL || t == NULL) {
6777 show_oops(NULL, "%s invalid parameters", __func__);
6778 return (FALSE);
6781 suggested_name = webkit_download_get_suggested_filename(wk_download);
6782 if (suggested_name == NULL)
6783 return (FALSE); /* abort download */
6785 i = 0;
6786 do {
6787 if (filename) {
6788 g_free(filename);
6789 filename = NULL;
6791 if (i) {
6792 g_free(uri);
6793 uri = NULL;
6794 filename = g_strdup_printf("%d%s", i, suggested_name);
6796 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6797 filename : suggested_name);
6798 i++;
6799 } while (!stat(uri + strlen("file://"), &sb));
6801 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6802 "local %s\n", __func__, t->tab_id, filename, uri);
6804 webkit_download_set_destination_uri(wk_download, uri);
6806 if (webkit_download_get_status(wk_download) ==
6807 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6808 show_oops(t, "%s: download failed to start", __func__);
6809 ret = FALSE;
6810 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6811 } else {
6812 /* connect "download first" mime handler */
6813 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6814 G_CALLBACK(download_status_changed_cb), NULL);
6816 download_entry = g_malloc(sizeof(struct download));
6817 download_entry->download = wk_download;
6818 download_entry->tab = t;
6819 download_entry->id = next_download_id++;
6820 RB_INSERT(download_list, &downloads, download_entry);
6821 /* get from history */
6822 g_object_ref(wk_download);
6823 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6824 show_oops(t, "Download of '%s' started...",
6825 basename((char *)webkit_download_get_destination_uri(wk_download)));
6828 if (uri)
6829 g_free(uri);
6831 if (filename)
6832 g_free(filename);
6834 /* sync other download manager tabs */
6835 update_download_tabs(NULL);
6838 * NOTE: never redirect/render the current tab before this
6839 * function returns. This will cause the download to never start.
6841 return (ret); /* start download */
6844 void
6845 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6847 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6849 if (t == NULL) {
6850 show_oops(NULL, "webview_hover_cb");
6851 return;
6854 if (uri)
6855 set_status(t, uri, XT_STATUS_LINK);
6856 else {
6857 if (t->status)
6858 set_status(t, t->status, XT_STATUS_NOTHING);
6863 mark(struct tab *t, struct karg *arg)
6865 char mark;
6866 int index;
6868 mark = arg->s[1];
6869 if ((index = marktoindex(mark)) == -1)
6870 return -1;
6872 if (arg->i == XT_MARK_SET)
6873 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6874 else if (arg->i == XT_MARK_GOTO) {
6875 if (t->mark[index] == XT_INVALID_MARK) {
6876 show_oops(t, "mark '%c' does not exist", mark);
6877 return -1;
6879 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6880 something changes the document size */
6881 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6884 return 0;
6887 void
6888 marks_clear(struct tab *t)
6890 int i;
6892 for (i = 0; i < LENGTH(t->mark); i++)
6893 t->mark[i] = XT_INVALID_MARK;
6897 qmarks_load(void)
6899 char file[PATH_MAX];
6900 char *line = NULL, *p, mark;
6901 int index, i;
6902 FILE *f;
6903 size_t linelen;
6905 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6906 if ((f = fopen(file, "r+")) == NULL) {
6907 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6908 return (1);
6911 for (i = 1; ; i++) {
6912 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
6913 if (feof(f) || ferror(f))
6914 break;
6915 if (strlen(line) == 0 || line[0] == '#') {
6916 free(line);
6917 line = NULL;
6918 continue;
6921 p = strtok(line, " \t");
6923 if (p == NULL || strlen(p) != 1 ||
6924 (index = marktoindex(*p)) == -1) {
6925 warnx("corrupt quickmarks file, line %d", i);
6926 break;
6929 mark = *p;
6930 p = strtok(NULL, " \t");
6931 if (qmarks[index] != NULL)
6932 g_free(qmarks[index]);
6933 qmarks[index] = g_strdup(p);
6936 fclose(f);
6938 return (0);
6942 qmarks_save(void)
6944 char file[PATH_MAX];
6945 int i;
6946 FILE *f;
6948 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6949 if ((f = fopen(file, "r+")) == NULL) {
6950 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6951 return (1);
6954 for (i = 0; i < XT_NOMARKS; i++)
6955 if (qmarks[i] != NULL)
6956 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
6958 fclose(f);
6960 return (0);
6964 qmark(struct tab *t, struct karg *arg)
6966 char mark;
6967 int index;
6969 mark = arg->s[strlen(arg->s)-1];
6970 index = marktoindex(mark);
6971 if (index == -1)
6972 return (-1);
6974 switch (arg->i) {
6975 case XT_QMARK_SET:
6976 if (qmarks[index] != NULL)
6977 g_free(qmarks[index]);
6979 qmarks_load(); /* sync if multiple instances */
6980 qmarks[index] = g_strdup(get_uri(t));
6981 qmarks_save();
6982 break;
6983 case XT_QMARK_OPEN:
6984 if (qmarks[index] != NULL)
6985 load_uri(t, qmarks[index]);
6986 else {
6987 show_oops(t, "quickmark \"%c\" does not exist",
6988 mark);
6989 return (-1);
6991 break;
6992 case XT_QMARK_TAB:
6993 if (qmarks[index] != NULL)
6994 create_new_tab(qmarks[index], NULL, 1, -1);
6995 else {
6996 show_oops(t, "quickmark \"%c\" does not exist",
6997 mark);
6998 return (-1);
7000 break;
7003 return (0);
7007 go_up(struct tab *t, struct karg *args)
7009 int levels;
7010 char *uri;
7011 char *tmp;
7013 levels = atoi(args->s);
7014 if (levels == 0)
7015 levels = 1;
7017 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7018 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7019 return 1;
7020 tmp += strlen(XT_PROTO_DELIM);
7022 /* if an uri starts with a slash, leave it alone (for file:///) */
7023 if (tmp[0] == '/')
7024 tmp++;
7026 while (levels--) {
7027 char *p;
7029 p = strrchr(tmp, '/');
7030 if (p != NULL)
7031 *p = '\0';
7032 else
7033 break;
7036 load_uri(t, uri);
7037 g_free(uri);
7039 return (0);
7043 gototab(struct tab *t, struct karg *args)
7045 int tab;
7046 struct karg arg = {0, NULL, -1};
7048 tab = atoi(args->s);
7050 arg.i = XT_TAB_NEXT;
7051 arg.precount = tab;
7053 movetab(t, &arg);
7055 return (0);
7059 zoom_amount(struct tab *t, struct karg *arg)
7061 struct karg narg = {0, NULL, -1};
7063 narg.i = atoi(arg->s);
7064 resizetab(t, &narg);
7066 return 0;
7070 flip_colon(struct tab *t, struct karg *arg)
7072 struct karg narg = {0, NULL, -1};
7073 char *p;
7075 if (t == NULL || arg == NULL)
7076 return (1);
7078 p = strstr(arg->s, ":");
7079 if (p == NULL)
7080 return (1);
7081 *p = '\0';
7083 narg.i = ':';
7084 narg.s = arg->s;
7085 command(t, &narg);
7087 return (0);
7090 /* buffer commands receive the regex that triggered them in arg.s */
7091 char bcmd[XT_BUFCMD_SZ];
7092 struct buffercmd {
7093 char *regex;
7094 int precount;
7095 #define XT_PRE_NO (0)
7096 #define XT_PRE_YES (1)
7097 #define XT_PRE_MAYBE (2)
7098 char *cmd;
7099 int (*func)(struct tab *, struct karg *);
7100 int arg;
7101 regex_t cregex;
7102 } buffercmds[] = {
7103 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7104 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7105 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7106 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7107 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7108 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7109 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7110 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7111 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7112 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7113 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7114 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7115 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7116 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7117 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7118 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7119 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7120 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7123 void
7124 buffercmd_init(void)
7126 int i;
7128 for (i = 0; i < LENGTH(buffercmds); i++)
7129 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7130 REG_EXTENDED);
7133 void
7134 buffercmd_abort(struct tab *t)
7136 int i;
7138 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7139 for (i = 0; i < LENGTH(bcmd); i++)
7140 bcmd[i] = '\0';
7142 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7143 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7146 void
7147 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7149 struct karg arg = {0, NULL, -1};
7151 arg.i = cmd->arg;
7152 arg.s = g_strdup(bcmd);
7154 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7155 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7156 cmd->func(t, &arg);
7158 if (arg.s)
7159 g_free(arg.s);
7161 buffercmd_abort(t);
7164 gboolean
7165 buffercmd_addkey(struct tab *t, guint keyval)
7167 int i, c, match ;
7168 char s[XT_BUFCMD_SZ];
7170 if (keyval == GDK_Escape) {
7171 buffercmd_abort(t);
7172 return (XT_CB_HANDLED);
7175 /* key with modifier or non-ascii character */
7176 if (!isascii(keyval))
7177 return (XT_CB_PASSTHROUGH);
7179 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7180 "to buffer \"%s\"\n", keyval, bcmd);
7182 for (i = 0; i < LENGTH(bcmd); i++)
7183 if (bcmd[i] == '\0') {
7184 bcmd[i] = keyval;
7185 break;
7188 /* buffer full, ignore input */
7189 if (i >= LENGTH(bcmd) -1) {
7190 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7191 buffercmd_abort(t);
7192 return (XT_CB_HANDLED);
7195 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7197 /* find exact match */
7198 for (i = 0; i < LENGTH(buffercmds); i++)
7199 if (regexec(&buffercmds[i].cregex, bcmd,
7200 (size_t) 0, NULL, 0) == 0) {
7201 buffercmd_execute(t, &buffercmds[i]);
7202 goto done;
7205 /* find non exact matches to see if we need to abort ot not */
7206 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7207 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7208 c = -1;
7209 s[0] = '\0';
7210 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7211 if (isdigit(bcmd[0])) {
7212 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7213 continue;
7214 } else {
7215 c = 0;
7216 if (sscanf(bcmd, "%s", s) == 0)
7217 continue;
7219 } else if (buffercmds[i].precount == XT_PRE_YES) {
7220 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7221 continue;
7222 } else {
7223 if (sscanf(bcmd, "%s", s) == 0)
7224 continue;
7226 if (c == -1 && buffercmds[i].precount)
7227 continue;
7228 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7229 match++;
7231 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7232 i, match, buffercmds[i].cmd, c, s);
7234 if (match == 0) {
7235 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7236 buffercmd_abort(t);
7239 done:
7240 return (XT_CB_HANDLED);
7243 gboolean
7244 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7246 struct key_binding *k;
7248 /* handle keybindings if buffercmd is empty.
7249 if not empty, allow commands like C-n */
7250 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7251 TAILQ_FOREACH(k, &kbl, entry)
7252 if (e->keyval == k->key
7253 && (entry ? k->use_in_entry : 1)) {
7254 if (k->mask == 0) {
7255 if ((e->state & (CTRL | MOD1)) == 0)
7256 return (cmd_execute(t, k->cmd));
7257 } else if ((e->state & k->mask) == k->mask) {
7258 return (cmd_execute(t, k->cmd));
7262 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7263 return buffercmd_addkey(t, e->keyval);
7265 return (XT_CB_PASSTHROUGH);
7269 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7271 char s[2], buf[128];
7272 const char *errstr = NULL;
7274 /* don't use w directly; use t->whatever instead */
7276 if (t == NULL) {
7277 show_oops(NULL, "wv_keypress_after_cb");
7278 return (XT_CB_PASSTHROUGH);
7281 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7282 e->keyval, e->state, t);
7284 if (t->hints_on) {
7285 /* ESC */
7286 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7287 disable_hints(t);
7288 return (XT_CB_HANDLED);
7291 /* RETURN */
7292 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7293 if (errstr) {
7294 /* we have a string */
7295 } else {
7296 /* we have a number */
7297 snprintf(buf, sizeof buf,
7298 "vimprobable_fire(%s)", t->hint_num);
7299 run_script(t, buf);
7301 disable_hints(t);
7304 /* BACKSPACE */
7305 /* XXX unfuck this */
7306 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7307 if (t->hint_mode == XT_HINT_NUMERICAL) {
7308 /* last input was numerical */
7309 int l;
7310 l = strlen(t->hint_num);
7311 if (l > 0) {
7312 l--;
7313 if (l == 0) {
7314 disable_hints(t);
7315 enable_hints(t);
7316 } else {
7317 t->hint_num[l] = '\0';
7318 goto num;
7321 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7322 /* last input was alphanumerical */
7323 int l;
7324 l = strlen(t->hint_buf);
7325 if (l > 0) {
7326 l--;
7327 if (l == 0) {
7328 disable_hints(t);
7329 enable_hints(t);
7330 } else {
7331 t->hint_buf[l] = '\0';
7332 goto anum;
7335 } else {
7336 /* bogus */
7337 disable_hints(t);
7341 /* numerical input */
7342 if (CLEAN(e->state) == 0 &&
7343 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7344 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7345 snprintf(s, sizeof s, "%c", e->keyval);
7346 strlcat(t->hint_num, s, sizeof t->hint_num);
7347 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7348 t->hint_num);
7349 num:
7350 if (errstr) {
7351 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7352 "invalid link number\n");
7353 disable_hints(t);
7354 } else {
7355 snprintf(buf, sizeof buf,
7356 "vimprobable_update_hints(%s)",
7357 t->hint_num);
7358 t->hint_mode = XT_HINT_NUMERICAL;
7359 run_script(t, buf);
7362 /* empty the counter buffer */
7363 bzero(t->hint_buf, sizeof t->hint_buf);
7364 return (XT_CB_HANDLED);
7367 /* alphanumerical input */
7368 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7369 e->keyval <= GDK_z) ||
7370 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7371 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7372 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7373 e->keyval <= GDK_9) ||
7374 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7375 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7376 snprintf(s, sizeof s, "%c", e->keyval);
7377 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7378 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7379 " %s\n", t->hint_buf);
7380 anum:
7381 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7382 run_script(t, buf);
7384 snprintf(buf, sizeof buf,
7385 "vimprobable_show_hints('%s')", t->hint_buf);
7386 t->hint_mode = XT_HINT_ALPHANUM;
7387 run_script(t, buf);
7389 /* empty the counter buffer */
7390 bzero(t->hint_num, sizeof t->hint_num);
7391 return (XT_CB_HANDLED);
7394 return (XT_CB_HANDLED);
7395 } else {
7396 /* prefix input*/
7397 snprintf(s, sizeof s, "%c", e->keyval);
7398 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7399 cmd_prefix = 10 * cmd_prefix + atoi(s);
7402 return (handle_keypress(t, e, 0));
7406 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7408 hide_oops(t);
7410 /* Hide buffers, if they are visible, with escape. */
7411 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7412 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7413 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7414 hide_buffers(t);
7415 return (XT_CB_HANDLED);
7418 return (XT_CB_PASSTHROUGH);
7421 gboolean
7422 search_continue(struct tab *t)
7424 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7425 gboolean rv = FALSE;
7427 if (c[0] == ':')
7428 goto done;
7429 if (strlen(c) == 1) {
7430 webkit_web_view_unmark_text_matches(t->wv);
7431 goto done;
7434 if (c[0] == '/')
7435 t->search_forward = TRUE;
7436 else if (c[0] == '?')
7437 t->search_forward = FALSE;
7438 else
7439 goto done;
7441 rv = TRUE;
7442 done:
7443 return (rv);
7446 gboolean
7447 search_cb(struct tab *t)
7449 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7450 GdkColor color;
7452 if (search_continue(t) == FALSE)
7453 goto done;
7455 /* search */
7456 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7457 TRUE) == FALSE) {
7458 /* not found, mark red */
7459 gdk_color_parse(XT_COLOR_RED, &color);
7460 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7461 /* unmark and remove selection */
7462 webkit_web_view_unmark_text_matches(t->wv);
7463 /* my kingdom for a way to unselect text in webview */
7464 } else {
7465 /* found, highlight all */
7466 webkit_web_view_unmark_text_matches(t->wv);
7467 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7468 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7469 gdk_color_parse(XT_COLOR_WHITE, &color);
7470 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7472 done:
7473 t->search_id = 0;
7474 return (FALSE);
7478 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7480 const gchar *c = gtk_entry_get_text(w);
7482 if (t == NULL) {
7483 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7484 return (XT_CB_PASSTHROUGH);
7487 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7488 e->keyval, e->state, t);
7490 if (search_continue(t) == FALSE)
7491 goto done;
7493 /* if search length is > 4 then no longer play timeout games */
7494 if (strlen(c) > 4) {
7495 if (t->search_id) {
7496 g_source_remove(t->search_id);
7497 t->search_id = 0;
7499 search_cb(t);
7500 goto done;
7503 /* reestablish a new timer if the user types fast */
7504 if (t->search_id)
7505 g_source_remove(t->search_id);
7506 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7508 done:
7509 return (XT_CB_PASSTHROUGH);
7512 gboolean
7513 match_uri(const gchar *uri, const gchar *key) {
7514 gchar *voffset;
7515 size_t len;
7516 gboolean match = FALSE;
7518 len = strlen(key);
7520 if (!strncmp(key, uri, len))
7521 match = TRUE;
7522 else {
7523 voffset = strstr(uri, "/") + 2;
7524 if (!strncmp(key, voffset, len))
7525 match = TRUE;
7526 else if (g_str_has_prefix(voffset, "www.")) {
7527 voffset = voffset + strlen("www.");
7528 if (!strncmp(key, voffset, len))
7529 match = TRUE;
7533 return (match);
7536 void
7537 cmd_getlist(int id, char *key)
7539 int i, dep, c = 0;
7540 struct history *h;
7542 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7543 RB_FOREACH_REVERSE(h, history_list, &hl)
7544 if (match_uri(h->uri, key)) {
7545 cmd_status.list[c] = (char *)h->uri;
7546 if (++c > 255)
7547 break;
7550 cmd_status.len = c;
7551 return;
7554 dep = (id == -1) ? 0 : cmds[id].level + 1;
7556 for (i = id + 1; i < LENGTH(cmds); i++) {
7557 if (cmds[i].level < dep)
7558 break;
7559 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7560 strlen(key)))
7561 cmd_status.list[c++] = cmds[i].cmd;
7565 cmd_status.len = c;
7568 char *
7569 cmd_getnext(int dir)
7571 cmd_status.index += dir;
7573 if (cmd_status.index < 0)
7574 cmd_status.index = cmd_status.len - 1;
7575 else if (cmd_status.index >= cmd_status.len)
7576 cmd_status.index = 0;
7578 return cmd_status.list[cmd_status.index];
7582 cmd_tokenize(char *s, char *tokens[])
7584 int i = 0;
7585 char *tok, *last;
7586 size_t len = strlen(s);
7587 bool blank;
7589 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7590 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7591 tok = strtok_r(NULL, " ", &last), i++)
7592 tokens[i] = tok;
7594 if (blank && i < 3)
7595 tokens[i++] = "";
7597 return (i);
7600 void
7601 cmd_complete(struct tab *t, char *str, int dir)
7603 GtkEntry *w = GTK_ENTRY(t->cmd);
7604 int i, j, levels, c = 0, dep = 0, parent = -1;
7605 int matchcount = 0;
7606 char *tok, *match, *s = g_strdup(str);
7607 char *tokens[3];
7608 char res[XT_MAX_URL_LENGTH + 32] = ":";
7609 char *sc = s;
7611 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7613 /* copy prefix*/
7614 for (i = 0; isdigit(s[i]); i++)
7615 res[i + 1] = s[i];
7617 for (; isspace(s[i]); i++)
7618 res[i + 1] = s[i];
7620 s += i;
7622 levels = cmd_tokenize(s, tokens);
7624 for (i = 0; i < levels - 1; i++) {
7625 tok = tokens[i];
7626 matchcount = 0;
7627 for (j = c; j < LENGTH(cmds); j++) {
7628 if (cmds[j].level < dep)
7629 break;
7630 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7631 strlen(tok))) {
7632 matchcount++;
7633 c = j + 1;
7634 if (strlen(tok) == strlen(cmds[j].cmd)) {
7635 matchcount = 1;
7636 break;
7641 if (matchcount == 1) {
7642 strlcat(res, tok, sizeof res);
7643 strlcat(res, " ", sizeof res);
7644 dep++;
7645 } else {
7646 g_free(sc);
7647 return;
7650 parent = c - 1;
7653 if (cmd_status.index == -1)
7654 cmd_getlist(parent, tokens[i]);
7656 if (cmd_status.len > 0) {
7657 match = cmd_getnext(dir);
7658 strlcat(res, match, sizeof res);
7659 gtk_entry_set_text(w, res);
7660 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7663 g_free(sc);
7666 gboolean
7667 cmd_execute(struct tab *t, char *str)
7669 struct cmd *cmd = NULL;
7670 char *tok, *last, *s = g_strdup(str), *sc;
7671 char prefixstr[4];
7672 int j, len, c = 0, dep = 0, matchcount = 0;
7673 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7674 struct karg arg = {0, NULL, -1};
7676 sc = s;
7678 /* copy prefix*/
7679 for (j = 0; j<3 && isdigit(s[j]); j++)
7680 prefixstr[j]=s[j];
7682 prefixstr[j]='\0';
7684 s += j;
7685 while (isspace(s[0]))
7686 s++;
7688 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7689 prefix = atoi(prefixstr);
7690 else
7691 s = sc;
7693 for (tok = strtok_r(s, " ", &last); tok;
7694 tok = strtok_r(NULL, " ", &last)) {
7695 matchcount = 0;
7696 for (j = c; j < LENGTH(cmds); j++) {
7697 if (cmds[j].level < dep)
7698 break;
7699 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7700 strlen(tok);
7701 if (cmds[j].level == dep &&
7702 !strncmp(tok, cmds[j].cmd, len)) {
7703 matchcount++;
7704 c = j + 1;
7705 cmd = &cmds[j];
7706 if (len == strlen(cmds[j].cmd)) {
7707 matchcount = 1;
7708 break;
7712 if (matchcount == 1) {
7713 if (cmd->type > 0)
7714 goto execute_cmd;
7715 dep++;
7716 } else {
7717 show_oops(t, "Invalid command: %s", str);
7718 goto done;
7721 execute_cmd:
7722 arg.i = cmd->arg;
7724 if (prefix != -1)
7725 arg.precount = prefix;
7726 else if (cmd_prefix > 0)
7727 arg.precount = cmd_prefix;
7729 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7730 show_oops(t, "No prefix allowed: %s", str);
7731 goto done;
7733 if (cmd->type > 1)
7734 arg.s = last ? g_strdup(last) : g_strdup("");
7735 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7736 arg.precount = atoi(arg.s);
7737 if (arg.precount <= 0) {
7738 if (arg.s[0] == '0')
7739 show_oops(t, "Zero count");
7740 else
7741 show_oops(t, "Trailing characters");
7742 goto done;
7746 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7747 __func__, arg.precount, arg.s);
7749 cmd->func(t, &arg);
7751 rv = XT_CB_HANDLED;
7752 done:
7753 if (j > 0)
7754 cmd_prefix = 0;
7755 g_free(sc);
7756 if (arg.s)
7757 g_free(arg.s);
7759 return (rv);
7763 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7765 if (t == NULL) {
7766 show_oops(NULL, "entry_key_cb invalid parameters");
7767 return (XT_CB_PASSTHROUGH);
7770 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7771 e->keyval, e->state, t);
7773 hide_oops(t);
7775 if (e->keyval == GDK_Escape) {
7776 /* don't use focus_webview(t) because we want to type :cmds */
7777 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7780 return (handle_keypress(t, e, 1));
7784 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7786 int rv = XT_CB_HANDLED;
7787 const gchar *c = gtk_entry_get_text(w);
7789 if (t == NULL) {
7790 show_oops(NULL, "cmd_keypress_cb parameters");
7791 return (XT_CB_PASSTHROUGH);
7794 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7795 e->keyval, e->state, t);
7797 /* sanity */
7798 if (c == NULL)
7799 e->keyval = GDK_Escape;
7800 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7801 e->keyval = GDK_Escape;
7803 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7804 e->keyval != GDK_ISO_Left_Tab)
7805 cmd_status.index = -1;
7807 switch (e->keyval) {
7808 case GDK_Tab:
7809 if (c[0] == ':')
7810 cmd_complete(t, (char *)&c[1], 1);
7811 goto done;
7812 case GDK_ISO_Left_Tab:
7813 if (c[0] == ':')
7814 cmd_complete(t, (char *)&c[1], -1);
7816 goto done;
7817 case GDK_BackSpace:
7818 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7819 break;
7820 /* FALLTHROUGH */
7821 case GDK_Escape:
7822 hide_cmd(t);
7823 focus_webview(t);
7825 /* cancel search */
7826 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7827 webkit_web_view_unmark_text_matches(t->wv);
7828 goto done;
7831 rv = XT_CB_PASSTHROUGH;
7832 done:
7833 return (rv);
7837 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7839 if (t == NULL) {
7840 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7841 return (XT_CB_PASSTHROUGH);
7843 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7845 hide_cmd(t);
7846 hide_oops(t);
7848 if (show_url == 0 || t->focus_wv)
7849 focus_webview(t);
7850 else
7851 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7853 return (XT_CB_PASSTHROUGH);
7856 void
7857 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7859 char *s;
7860 const gchar *c = gtk_entry_get_text(entry);
7862 if (t == NULL) {
7863 show_oops(NULL, "cmd_activate_cb invalid parameters");
7864 return;
7867 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7869 hide_cmd(t);
7871 /* sanity */
7872 if (c == NULL)
7873 goto done;
7874 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7875 goto done;
7876 if (strlen(c) < 2)
7877 goto done;
7878 s = (char *)&c[1];
7880 if (c[0] == '/' || c[0] == '?') {
7881 /* see if there is a timer pending */
7882 if (t->search_id) {
7883 g_source_remove(t->search_id);
7884 t->search_id = 0;
7885 search_cb(t);
7888 if (t->search_text) {
7889 g_free(t->search_text);
7890 t->search_text = NULL;
7893 t->search_text = g_strdup(s);
7894 if (global_search)
7895 g_free(global_search);
7896 global_search = g_strdup(s);
7897 t->search_forward = c[0] == '/';
7899 goto done;
7902 cmd_execute(t, s);
7904 done:
7905 return;
7908 void
7909 backward_cb(GtkWidget *w, struct tab *t)
7911 struct karg a;
7913 if (t == NULL) {
7914 show_oops(NULL, "backward_cb invalid parameters");
7915 return;
7918 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7920 a.i = XT_NAV_BACK;
7921 navaction(t, &a);
7924 void
7925 forward_cb(GtkWidget *w, struct tab *t)
7927 struct karg a;
7929 if (t == NULL) {
7930 show_oops(NULL, "forward_cb invalid parameters");
7931 return;
7934 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7936 a.i = XT_NAV_FORWARD;
7937 navaction(t, &a);
7940 void
7941 home_cb(GtkWidget *w, struct tab *t)
7943 if (t == NULL) {
7944 show_oops(NULL, "home_cb invalid parameters");
7945 return;
7948 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7950 load_uri(t, home);
7953 void
7954 stop_cb(GtkWidget *w, struct tab *t)
7956 WebKitWebFrame *frame;
7958 if (t == NULL) {
7959 show_oops(NULL, "stop_cb invalid parameters");
7960 return;
7963 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7965 frame = webkit_web_view_get_main_frame(t->wv);
7966 if (frame == NULL) {
7967 show_oops(t, "stop_cb: no frame");
7968 return;
7971 webkit_web_frame_stop_loading(frame);
7972 abort_favicon_download(t);
7975 void
7976 setup_webkit(struct tab *t)
7978 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7979 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7980 FALSE, (char *)NULL);
7981 else
7982 warnx("webkit does not have \"enable-dns-prefetching\" property");
7983 g_object_set(G_OBJECT(t->settings),
7984 "user-agent", t->user_agent, (char *)NULL);
7985 g_object_set(G_OBJECT(t->settings),
7986 "enable-scripts", enable_scripts, (char *)NULL);
7987 g_object_set(G_OBJECT(t->settings),
7988 "enable-plugins", enable_plugins, (char *)NULL);
7989 g_object_set(G_OBJECT(t->settings),
7990 "javascript-can-open-windows-automatically", enable_scripts,
7991 (char *)NULL);
7992 g_object_set(G_OBJECT(t->settings),
7993 "enable-html5-database", FALSE, (char *)NULL);
7994 g_object_set(G_OBJECT(t->settings),
7995 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7996 g_object_set(G_OBJECT(t->settings),
7997 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7998 g_object_set(G_OBJECT(t->settings),
7999 "spell_checking_languages", spell_check_languages, (char *)NULL);
8000 g_object_set(G_OBJECT(t->wv),
8001 "full-content-zoom", TRUE, (char *)NULL);
8003 webkit_web_view_set_settings(t->wv, t->settings);
8006 gboolean
8007 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8009 struct tab *ti, *t = NULL;
8010 gdouble view_size, value, max;
8011 gchar *position;
8013 TAILQ_FOREACH(ti, &tabs, entry)
8014 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8015 t = ti;
8016 break;
8019 if (t == NULL)
8020 return FALSE;
8022 if (adjustment == NULL)
8023 adjustment = gtk_scrolled_window_get_vadjustment(
8024 GTK_SCROLLED_WINDOW(t->browser_win));
8026 view_size = gtk_adjustment_get_page_size(adjustment);
8027 value = gtk_adjustment_get_value(adjustment);
8028 max = gtk_adjustment_get_upper(adjustment) - view_size;
8030 if (max == 0)
8031 position = g_strdup("All");
8032 else if (value == max)
8033 position = g_strdup("Bot");
8034 else if (value == 0)
8035 position = g_strdup("Top");
8036 else
8037 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8039 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8040 g_free(position);
8042 return (TRUE);
8045 GtkWidget *
8046 create_browser(struct tab *t)
8048 GtkWidget *w;
8049 gchar *strval;
8050 GtkAdjustment *adjustment;
8052 if (t == NULL) {
8053 show_oops(NULL, "create_browser invalid parameters");
8054 return (NULL);
8057 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8058 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8059 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8060 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8062 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8063 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8064 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8066 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8067 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8069 /* set defaults */
8070 t->settings = webkit_web_settings_new();
8072 if (user_agent == NULL) {
8073 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8074 (char *)NULL);
8075 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8076 g_free(strval);
8077 } else
8078 t->user_agent = g_strdup(user_agent);
8080 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8082 adjustment =
8083 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8084 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8085 G_CALLBACK(update_statusbar_position), NULL);
8087 setup_webkit(t);
8089 return (w);
8092 GtkWidget *
8093 create_window(void)
8095 GtkWidget *w;
8097 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8098 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8099 gtk_widget_set_name(w, "xxxterm");
8100 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8101 g_signal_connect(G_OBJECT(w), "delete_event",
8102 G_CALLBACK (gtk_main_quit), NULL);
8104 return (w);
8107 GtkWidget *
8108 create_kiosk_toolbar(struct tab *t)
8110 GtkWidget *toolbar = NULL, *b;
8112 b = gtk_hbox_new(FALSE, 0);
8113 toolbar = b;
8114 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8116 /* backward button */
8117 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8118 gtk_widget_set_sensitive(t->backward, FALSE);
8119 g_signal_connect(G_OBJECT(t->backward), "clicked",
8120 G_CALLBACK(backward_cb), t);
8121 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8123 /* forward button */
8124 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8125 gtk_widget_set_sensitive(t->forward, FALSE);
8126 g_signal_connect(G_OBJECT(t->forward), "clicked",
8127 G_CALLBACK(forward_cb), t);
8128 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8130 /* home button */
8131 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8132 gtk_widget_set_sensitive(t->gohome, true);
8133 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8134 G_CALLBACK(home_cb), t);
8135 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8137 /* create widgets but don't use them */
8138 t->uri_entry = gtk_entry_new();
8139 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8140 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8141 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8143 return (toolbar);
8146 GtkWidget *
8147 create_toolbar(struct tab *t)
8149 GtkWidget *toolbar = NULL, *b, *eb1;
8151 b = gtk_hbox_new(FALSE, 0);
8152 toolbar = b;
8153 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8155 /* backward button */
8156 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8157 gtk_widget_set_sensitive(t->backward, FALSE);
8158 g_signal_connect(G_OBJECT(t->backward), "clicked",
8159 G_CALLBACK(backward_cb), t);
8160 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8162 /* forward button */
8163 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8164 gtk_widget_set_sensitive(t->forward, FALSE);
8165 g_signal_connect(G_OBJECT(t->forward), "clicked",
8166 G_CALLBACK(forward_cb), t);
8167 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8168 FALSE, 0);
8170 /* stop button */
8171 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8172 gtk_widget_set_sensitive(t->stop, FALSE);
8173 g_signal_connect(G_OBJECT(t->stop), "clicked",
8174 G_CALLBACK(stop_cb), t);
8175 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8176 FALSE, 0);
8178 /* JS button */
8179 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8180 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8181 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8182 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8183 G_CALLBACK(js_toggle_cb), t);
8184 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8186 t->uri_entry = gtk_entry_new();
8187 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8188 G_CALLBACK(activate_uri_entry_cb), t);
8189 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8190 G_CALLBACK(entry_key_cb), t);
8191 completion_add(t);
8192 eb1 = gtk_hbox_new(FALSE, 0);
8193 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8194 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8195 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8197 /* search entry */
8198 if (search_string) {
8199 GtkWidget *eb2;
8200 t->search_entry = gtk_entry_new();
8201 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8202 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8203 G_CALLBACK(activate_search_entry_cb), t);
8204 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8205 G_CALLBACK(entry_key_cb), t);
8206 gtk_widget_set_size_request(t->search_entry, -1, -1);
8207 eb2 = gtk_hbox_new(FALSE, 0);
8208 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8209 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8211 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8214 return (toolbar);
8217 GtkWidget *
8218 create_buffers(struct tab *t)
8220 GtkCellRenderer *renderer;
8221 GtkWidget *view;
8223 view = gtk_tree_view_new();
8225 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8227 renderer = gtk_cell_renderer_text_new();
8228 gtk_tree_view_insert_column_with_attributes
8229 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8231 renderer = gtk_cell_renderer_text_new();
8232 gtk_tree_view_insert_column_with_attributes
8233 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8234 NULL);
8236 gtk_tree_view_set_model
8237 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8239 return view;
8242 void
8243 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8244 GtkTreeViewColumn *col, struct tab *t)
8246 GtkTreeIter iter;
8247 guint id;
8249 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8251 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8252 path)) {
8253 gtk_tree_model_get
8254 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8255 set_current_tab(id - 1);
8258 hide_buffers(t);
8261 /* after tab reordering/creation/removal */
8262 void
8263 recalc_tabs(void)
8265 struct tab *t;
8266 int maxid = 0;
8268 TAILQ_FOREACH(t, &tabs, entry) {
8269 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8270 if (t->tab_id > maxid)
8271 maxid = t->tab_id;
8273 gtk_widget_show(t->tab_elems.sep);
8276 TAILQ_FOREACH(t, &tabs, entry) {
8277 if (t->tab_id == maxid) {
8278 gtk_widget_hide(t->tab_elems.sep);
8279 break;
8284 /* after active tab change */
8285 void
8286 recolor_compact_tabs(void)
8288 struct tab *t;
8289 int curid = 0;
8290 GdkColor color;
8292 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8293 TAILQ_FOREACH(t, &tabs, entry)
8294 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8295 &color);
8297 curid = gtk_notebook_get_current_page(notebook);
8298 TAILQ_FOREACH(t, &tabs, entry)
8299 if (t->tab_id == curid) {
8300 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8301 gtk_widget_modify_fg(t->tab_elems.label,
8302 GTK_STATE_NORMAL, &color);
8303 break;
8307 void
8308 set_current_tab(int page_num)
8310 buffercmd_abort(get_current_tab());
8311 gtk_notebook_set_current_page(notebook, page_num);
8312 recolor_compact_tabs();
8316 undo_close_tab_save(struct tab *t)
8318 int m, n;
8319 const gchar *uri;
8320 struct undo *u1, *u2;
8321 GList *items;
8322 WebKitWebHistoryItem *item;
8324 if ((uri = get_uri(t)) == NULL)
8325 return (1);
8327 u1 = g_malloc0(sizeof(struct undo));
8328 u1->uri = g_strdup(uri);
8330 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8332 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8333 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8334 u1->back = n;
8336 /* forward history */
8337 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8339 while (items) {
8340 item = items->data;
8341 u1->history = g_list_prepend(u1->history,
8342 webkit_web_history_item_copy(item));
8343 items = g_list_next(items);
8346 /* current item */
8347 if (m) {
8348 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8349 u1->history = g_list_prepend(u1->history,
8350 webkit_web_history_item_copy(item));
8353 /* back history */
8354 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8356 while (items) {
8357 item = items->data;
8358 u1->history = g_list_prepend(u1->history,
8359 webkit_web_history_item_copy(item));
8360 items = g_list_next(items);
8363 TAILQ_INSERT_HEAD(&undos, u1, entry);
8365 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8366 u2 = TAILQ_LAST(&undos, undo_tailq);
8367 TAILQ_REMOVE(&undos, u2, entry);
8368 g_free(u2->uri);
8369 g_list_free(u2->history);
8370 g_free(u2);
8371 } else
8372 undo_count++;
8374 return (0);
8377 void
8378 delete_tab(struct tab *t)
8380 struct karg a;
8382 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8384 if (t == NULL)
8385 return;
8387 buffercmd_abort(t);
8388 TAILQ_REMOVE(&tabs, t, entry);
8390 /* Halt all webkit activity. */
8391 abort_favicon_download(t);
8392 webkit_web_view_stop_loading(t->wv);
8394 /* Save the tab, so we can undo the close. */
8395 undo_close_tab_save(t);
8397 if (browser_mode == XT_BM_KIOSK) {
8398 gtk_widget_destroy(t->uri_entry);
8399 gtk_widget_destroy(t->stop);
8400 gtk_widget_destroy(t->js_toggle);
8403 gtk_widget_destroy(t->tab_elems.eventbox);
8404 gtk_widget_destroy(t->vbox);
8406 /* just in case */
8407 if (t->search_id)
8408 g_source_remove(t->search_id);
8410 g_free(t->user_agent);
8411 g_free(t->stylesheet);
8412 g_free(t->tmp_uri);
8413 g_free(t);
8415 if (TAILQ_EMPTY(&tabs)) {
8416 if (browser_mode == XT_BM_KIOSK)
8417 create_new_tab(home, NULL, 1, -1);
8418 else
8419 create_new_tab(NULL, NULL, 1, -1);
8422 /* recreate session */
8423 if (session_autosave) {
8424 a.s = NULL;
8425 save_tabs(t, &a);
8428 recalc_tabs();
8429 recolor_compact_tabs();
8432 void
8433 update_statusbar_zoom(struct tab *t)
8435 gfloat zoom;
8436 char s[16] = { '\0' };
8438 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8439 if ((zoom <= 0.99 || zoom >= 1.01))
8440 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8441 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8444 void
8445 setzoom_webkit(struct tab *t, int adjust)
8447 #define XT_ZOOMPERCENT 0.04
8449 gfloat zoom;
8451 if (t == NULL) {
8452 show_oops(NULL, "setzoom_webkit invalid parameters");
8453 return;
8456 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8457 if (adjust == XT_ZOOM_IN)
8458 zoom += XT_ZOOMPERCENT;
8459 else if (adjust == XT_ZOOM_OUT)
8460 zoom -= XT_ZOOMPERCENT;
8461 else if (adjust > 0)
8462 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8463 else {
8464 show_oops(t, "setzoom_webkit invalid zoom value");
8465 return;
8468 if (zoom < XT_ZOOMPERCENT)
8469 zoom = XT_ZOOMPERCENT;
8470 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8471 update_statusbar_zoom(t);
8474 gboolean
8475 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8477 struct tab *t = (struct tab *) data;
8479 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8481 switch (event->button) {
8482 case 1:
8483 set_current_tab(t->tab_id);
8484 break;
8485 case 2:
8486 delete_tab(t);
8487 break;
8490 return TRUE;
8493 void
8494 append_tab(struct tab *t)
8496 if (t == NULL)
8497 return;
8499 TAILQ_INSERT_TAIL(&tabs, t, entry);
8500 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8503 GtkWidget *
8504 create_sbe(int width)
8506 GtkWidget *sbe;
8508 sbe = gtk_entry_new();
8509 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8510 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8511 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8512 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8513 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8514 gtk_widget_set_size_request(sbe, width, -1);
8516 return sbe;
8519 struct tab *
8520 create_new_tab(char *title, struct undo *u, int focus, int position)
8522 struct tab *t;
8523 int load = 1, id;
8524 GtkWidget *b, *bb;
8525 WebKitWebHistoryItem *item;
8526 GList *items;
8527 GdkColor color;
8528 char *p;
8529 int sbe_p = 0, sbe_b = 0,
8530 sbe_z = 0;
8532 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8534 if (tabless && !TAILQ_EMPTY(&tabs)) {
8535 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8536 return (NULL);
8539 t = g_malloc0(sizeof *t);
8541 if (title == NULL) {
8542 title = "(untitled)";
8543 load = 0;
8546 t->vbox = gtk_vbox_new(FALSE, 0);
8548 /* label + button for tab */
8549 b = gtk_hbox_new(FALSE, 0);
8550 t->tab_content = b;
8552 #if GTK_CHECK_VERSION(2, 20, 0)
8553 t->spinner = gtk_spinner_new();
8554 #endif
8555 t->label = gtk_label_new(title);
8556 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8557 gtk_widget_set_size_request(t->label, 100, 0);
8558 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8559 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8560 gtk_widget_set_size_request(b, 130, 0);
8562 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8563 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8564 #if GTK_CHECK_VERSION(2, 20, 0)
8565 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8566 #endif
8568 /* toolbar */
8569 if (browser_mode == XT_BM_KIOSK) {
8570 t->toolbar = create_kiosk_toolbar(t);
8571 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8573 } else {
8574 t->toolbar = create_toolbar(t);
8575 if (fancy_bar)
8576 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8577 FALSE, 0);
8580 /* marks */
8581 marks_clear(t);
8583 /* browser */
8584 t->browser_win = create_browser(t);
8585 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8587 /* oops message for user feedback */
8588 t->oops = gtk_entry_new();
8589 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8590 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8591 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8592 gdk_color_parse(XT_COLOR_RED, &color);
8593 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8594 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8595 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8597 /* command entry */
8598 t->cmd = gtk_entry_new();
8599 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8600 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8601 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8602 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8604 /* status bar */
8605 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8607 t->sbe.statusbar = gtk_entry_new();
8608 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8609 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8610 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8611 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8613 /* create these widgets only if specified in statusbar_elems */
8615 t->sbe.position = create_sbe(40);
8616 t->sbe.zoom = create_sbe(40);
8617 t->sbe.buffercmd = create_sbe(60);
8619 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8621 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8622 TRUE, FALSE);
8624 /* gtk widgets cannot be added to a box twice. sbe_* variables
8625 make sure of this */
8626 for (p = statusbar_elems; *p != '\0'; p++) {
8627 switch (*p) {
8628 case '|':
8630 GtkWidget *sep = gtk_vseparator_new();
8632 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8633 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8634 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8635 FALSE, FALSE, FALSE);
8636 break;
8638 case 'P':
8639 if (sbe_p) {
8640 warnx("flag \"%c\" specified more than "
8641 "once in statusbar_elems\n", *p);
8642 break;
8644 sbe_p = 1;
8645 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8646 t->sbe.position, FALSE, FALSE, FALSE);
8647 break;
8648 case 'B':
8649 if (sbe_b) {
8650 warnx("flag \"%c\" specified more than "
8651 "once in statusbar_elems\n", *p);
8652 break;
8654 sbe_b = 1;
8655 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8656 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8657 break;
8658 case 'Z':
8659 if (sbe_z) {
8660 warnx("flag \"%c\" specified more than "
8661 "once in statusbar_elems\n", *p);
8662 break;
8664 sbe_z = 1;
8665 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8666 t->sbe.zoom, FALSE, FALSE, FALSE);
8667 break;
8668 default:
8669 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8670 break;
8674 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8676 /* buffer list */
8677 t->buffers = create_buffers(t);
8678 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8680 /* xtp meaning is normal by default */
8681 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8683 /* set empty favicon */
8684 xt_icon_from_name(t, "text-html");
8686 /* and show it all */
8687 gtk_widget_show_all(b);
8688 gtk_widget_show_all(t->vbox);
8690 /* compact tab bar */
8691 t->tab_elems.label = gtk_label_new(title);
8692 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8693 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8694 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8695 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8697 t->tab_elems.eventbox = gtk_event_box_new();
8698 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8699 t->tab_elems.sep = gtk_vseparator_new();
8701 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8702 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8703 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8704 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8705 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8706 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8708 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8709 TRUE, 0);
8710 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8711 FALSE, 0);
8712 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8713 t->tab_elems.box);
8715 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8716 TRUE, 0);
8717 gtk_widget_show_all(t->tab_elems.eventbox);
8719 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8720 append_tab(t);
8721 else {
8722 id = position >= 0 ? position :
8723 gtk_notebook_get_current_page(notebook) + 1;
8724 if (id > gtk_notebook_get_n_pages(notebook))
8725 append_tab(t);
8726 else {
8727 TAILQ_INSERT_TAIL(&tabs, t, entry);
8728 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8729 gtk_box_reorder_child(GTK_BOX(tab_bar),
8730 t->tab_elems.eventbox, id);
8731 recalc_tabs();
8735 #if GTK_CHECK_VERSION(2, 20, 0)
8736 /* turn spinner off if we are a new tab without uri */
8737 if (!load) {
8738 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8739 gtk_widget_hide(t->spinner);
8741 #endif
8742 /* make notebook tabs reorderable */
8743 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8745 /* compact tabs clickable */
8746 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8747 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8749 g_object_connect(G_OBJECT(t->cmd),
8750 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8751 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8752 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8753 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8754 (char *)NULL);
8756 /* reuse wv_button_cb to hide oops */
8757 g_object_connect(G_OBJECT(t->oops),
8758 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8759 (char *)NULL);
8761 g_signal_connect(t->buffers,
8762 "row-activated", G_CALLBACK(row_activated_cb), t);
8763 g_object_connect(G_OBJECT(t->buffers),
8764 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8766 g_object_connect(G_OBJECT(t->wv),
8767 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8768 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8769 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8770 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8771 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8772 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8773 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8774 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8775 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8776 "signal::event", G_CALLBACK(webview_event_cb), t,
8777 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8778 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8779 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8780 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8781 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8782 (char *)NULL);
8783 g_signal_connect(t->wv,
8784 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8786 * XXX this puts invalid url in uri_entry and that is undesirable
8788 #if 0
8789 g_signal_connect(t->wv,
8790 "load-error", G_CALLBACK(notify_load_error_cb), t);
8791 #endif
8792 g_signal_connect(t->wv,
8793 "notify::title", G_CALLBACK(notify_title_cb), t);
8795 /* hijack the unused keys as if we were the browser */
8796 g_object_connect(G_OBJECT(t->toolbar),
8797 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8798 (char *)NULL);
8800 g_signal_connect(G_OBJECT(bb), "button_press_event",
8801 G_CALLBACK(tab_close_cb), t);
8803 /* hide stuff */
8804 hide_cmd(t);
8805 hide_oops(t);
8806 hide_buffers(t);
8807 url_set_visibility();
8808 statusbar_set_visibility();
8810 if (focus) {
8811 set_current_tab(t->tab_id);
8812 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8813 t->tab_id);
8816 if (load) {
8817 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8818 load_uri(t, title);
8819 } else {
8820 if (show_url == 1)
8821 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8822 else
8823 focus_webview(t);
8826 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8827 /* restore the tab's history */
8828 if (u && u->history) {
8829 items = u->history;
8830 while (items) {
8831 item = items->data;
8832 webkit_web_back_forward_list_add_item(t->bfl, item);
8833 items = g_list_next(items);
8836 item = g_list_nth_data(u->history, u->back);
8837 if (item)
8838 webkit_web_view_go_to_back_forward_item(t->wv, item);
8840 g_list_free(items);
8841 g_list_free(u->history);
8842 } else
8843 webkit_web_back_forward_list_clear(t->bfl);
8845 recolor_compact_tabs();
8846 setzoom_webkit(t, XT_ZOOM_NORMAL);
8847 return (t);
8850 void
8851 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8852 gpointer *udata)
8854 struct tab *t;
8855 const gchar *uri;
8857 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8859 if (gtk_notebook_get_current_page(notebook) == -1)
8860 recalc_tabs();
8862 TAILQ_FOREACH(t, &tabs, entry) {
8863 if (t->tab_id == pn) {
8864 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8865 "%d\n", pn);
8867 uri = get_title(t, TRUE);
8868 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8870 hide_cmd(t);
8871 hide_oops(t);
8873 if (t->focus_wv) {
8874 /* can't use focus_webview here */
8875 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8881 void
8882 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8883 gpointer *udata)
8885 struct tab *t = NULL, *tt;
8887 recalc_tabs();
8889 TAILQ_FOREACH(tt, &tabs, entry)
8890 if (tt->tab_id == pn) {
8891 t = tt;
8892 break;
8895 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8897 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8898 t->tab_id);
8901 void
8902 menuitem_response(struct tab *t)
8904 gtk_notebook_set_current_page(notebook, t->tab_id);
8907 gboolean
8908 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8910 GtkWidget *menu, *menu_items;
8911 GdkEventButton *bevent;
8912 const gchar *uri;
8913 struct tab *ti;
8915 if (event->type == GDK_BUTTON_PRESS) {
8916 bevent = (GdkEventButton *) event;
8917 menu = gtk_menu_new();
8919 TAILQ_FOREACH(ti, &tabs, entry) {
8920 if ((uri = get_uri(ti)) == NULL)
8921 /* XXX make sure there is something to print */
8922 /* XXX add gui pages in here to look purdy */
8923 uri = "(untitled)";
8924 menu_items = gtk_menu_item_new_with_label(uri);
8925 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8926 gtk_widget_show(menu_items);
8928 g_signal_connect_swapped((menu_items),
8929 "activate", G_CALLBACK(menuitem_response),
8930 (gpointer)ti);
8933 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8934 bevent->button, bevent->time);
8936 /* unref object so it'll free itself when popped down */
8937 #if !GTK_CHECK_VERSION(3, 0, 0)
8938 /* XXX does not need unref with gtk+3? */
8939 g_object_ref_sink(menu);
8940 g_object_unref(menu);
8941 #endif
8943 return (TRUE /* eat event */);
8946 return (FALSE /* propagate */);
8950 icon_size_map(int icon_size)
8952 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8953 icon_size > GTK_ICON_SIZE_DIALOG)
8954 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8956 return (icon_size);
8959 GtkWidget *
8960 create_button(char *name, char *stockid, int size)
8962 GtkWidget *button, *image;
8963 gchar *rcstring;
8964 int gtk_icon_size;
8966 rcstring = g_strdup_printf(
8967 "style \"%s-style\"\n"
8968 "{\n"
8969 " GtkWidget::focus-padding = 0\n"
8970 " GtkWidget::focus-line-width = 0\n"
8971 " xthickness = 0\n"
8972 " ythickness = 0\n"
8973 "}\n"
8974 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8975 gtk_rc_parse_string(rcstring);
8976 g_free(rcstring);
8977 button = gtk_button_new();
8978 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8979 gtk_icon_size = icon_size_map(size ? size : icon_size);
8981 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8982 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8983 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8984 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8985 gtk_widget_set_name(button, name);
8986 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8988 return (button);
8991 void
8992 button_set_stockid(GtkWidget *button, char *stockid)
8994 GtkWidget *image;
8996 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8997 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8998 gtk_button_set_image(GTK_BUTTON(button), image);
9001 void
9002 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9004 gchar *p = NULL;
9005 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9006 gint len;
9008 if (xterm_workaround == 0)
9009 return;
9012 * xterm doesn't play nice with clipboards because it clears the
9013 * primary when clicked. We rely on primary being set to properly
9014 * handle middle mouse button clicks (paste). So when someone clears
9015 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9016 * other application behavior (as in DON'T clear primary).
9019 p = gtk_clipboard_wait_for_text(primary);
9020 if (p == NULL) {
9021 if (gdk_property_get(gdk_get_default_root_window(),
9022 atom,
9023 gdk_atom_intern("STRING", FALSE),
9025 1024 * 1024 /* picked out of my butt */,
9026 FALSE,
9027 NULL,
9028 NULL,
9029 &len,
9030 (guchar **)&p)) {
9031 /* yes sir, we need to NUL the string */
9032 p[len] = '\0';
9033 gtk_clipboard_set_text(primary, p, -1);
9037 if (p)
9038 g_free(p);
9041 void
9042 create_canvas(void)
9044 GtkWidget *vbox;
9045 GList *l = NULL;
9046 GdkPixbuf *pb;
9047 char file[PATH_MAX];
9048 int i;
9050 vbox = gtk_vbox_new(FALSE, 0);
9051 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9052 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9053 #if !GTK_CHECK_VERSION(3, 0, 0)
9054 /* XXX seems to be needed with gtk+2 */
9055 gtk_notebook_set_tab_hborder(notebook, 0);
9056 gtk_notebook_set_tab_vborder(notebook, 0);
9057 #endif
9058 gtk_notebook_set_scrollable(notebook, TRUE);
9059 gtk_notebook_set_show_border(notebook, FALSE);
9060 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9062 abtn = gtk_button_new();
9063 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9064 gtk_widget_set_size_request(arrow, -1, -1);
9065 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9066 gtk_widget_set_size_request(abtn, -1, 20);
9068 #if GTK_CHECK_VERSION(2, 20, 0)
9069 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9070 #endif
9071 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9073 /* compact tab bar */
9074 tab_bar = gtk_hbox_new(TRUE, 0);
9076 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9077 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9078 gtk_widget_set_size_request(vbox, -1, -1);
9080 g_object_connect(G_OBJECT(notebook),
9081 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9082 (char *)NULL);
9083 g_object_connect(G_OBJECT(notebook),
9084 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9085 NULL, (char *)NULL);
9086 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9087 G_CALLBACK(arrow_cb), NULL);
9089 main_window = create_window();
9090 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9092 /* icons */
9093 for (i = 0; i < LENGTH(icons); i++) {
9094 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9095 pb = gdk_pixbuf_new_from_file(file, NULL);
9096 l = g_list_append(l, pb);
9098 gtk_window_set_default_icon_list(l);
9100 /* clipboard work around */
9101 if (xterm_workaround)
9102 g_signal_connect(
9103 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9104 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9106 gtk_widget_show_all(abtn);
9107 gtk_widget_show_all(main_window);
9108 notebook_tab_set_visibility();
9111 void
9112 set_hook(void **hook, char *name)
9114 if (hook == NULL)
9115 errx(1, "set_hook");
9117 if (*hook == NULL) {
9118 *hook = dlsym(RTLD_NEXT, name);
9119 if (*hook == NULL)
9120 errx(1, "can't hook %s", name);
9124 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9125 gboolean
9126 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9128 g_return_val_if_fail(cookie1, FALSE);
9129 g_return_val_if_fail(cookie2, FALSE);
9131 return (!strcmp (cookie1->name, cookie2->name) &&
9132 !strcmp (cookie1->value, cookie2->value) &&
9133 !strcmp (cookie1->path, cookie2->path) &&
9134 !strcmp (cookie1->domain, cookie2->domain));
9137 void
9138 transfer_cookies(void)
9140 GSList *cf;
9141 SoupCookie *sc, *pc;
9143 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9145 for (;cf; cf = cf->next) {
9146 pc = cf->data;
9147 sc = soup_cookie_copy(pc);
9148 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9151 soup_cookies_free(cf);
9154 void
9155 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9157 GSList *cf;
9158 SoupCookie *ci;
9160 print_cookie("soup_cookie_jar_delete_cookie", c);
9162 if (cookies_enabled == 0)
9163 return;
9165 if (jar == NULL || c == NULL)
9166 return;
9168 /* find and remove from persistent jar */
9169 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9171 for (;cf; cf = cf->next) {
9172 ci = cf->data;
9173 if (soup_cookie_equal(ci, c)) {
9174 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9175 break;
9179 soup_cookies_free(cf);
9181 /* delete from session jar */
9182 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9185 void
9186 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9188 struct domain *d = NULL;
9189 SoupCookie *c;
9190 FILE *r_cookie_f;
9192 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9193 jar, p_cookiejar, s_cookiejar);
9195 if (cookies_enabled == 0)
9196 return;
9198 /* see if we are up and running */
9199 if (p_cookiejar == NULL) {
9200 _soup_cookie_jar_add_cookie(jar, cookie);
9201 return;
9203 /* disallow p_cookiejar adds, shouldn't happen */
9204 if (jar == p_cookiejar)
9205 return;
9207 /* sanity */
9208 if (jar == NULL || cookie == NULL)
9209 return;
9211 if (enable_cookie_whitelist &&
9212 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9213 blocked_cookies++;
9214 DNPRINTF(XT_D_COOKIE,
9215 "soup_cookie_jar_add_cookie: reject %s\n",
9216 cookie->domain);
9217 if (save_rejected_cookies) {
9218 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9219 show_oops(NULL, "can't open reject cookie file");
9220 return;
9222 fseek(r_cookie_f, 0, SEEK_END);
9223 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9224 cookie->http_only ? "#HttpOnly_" : "",
9225 cookie->domain,
9226 *cookie->domain == '.' ? "TRUE" : "FALSE",
9227 cookie->path,
9228 cookie->secure ? "TRUE" : "FALSE",
9229 cookie->expires ?
9230 (gulong)soup_date_to_time_t(cookie->expires) :
9232 cookie->name,
9233 cookie->value);
9234 fflush(r_cookie_f);
9235 fclose(r_cookie_f);
9237 if (!allow_volatile_cookies)
9238 return;
9241 if (cookie->expires == NULL && session_timeout) {
9242 soup_cookie_set_expires(cookie,
9243 soup_date_new_from_now(session_timeout));
9244 print_cookie("modified add cookie", cookie);
9247 /* see if we are white listed for persistence */
9248 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9249 /* add to persistent jar */
9250 c = soup_cookie_copy(cookie);
9251 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9252 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9255 /* add to session jar */
9256 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9257 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9260 void
9261 setup_cookies(void)
9263 char file[PATH_MAX];
9265 set_hook((void *)&_soup_cookie_jar_add_cookie,
9266 "soup_cookie_jar_add_cookie");
9267 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9268 "soup_cookie_jar_delete_cookie");
9270 if (cookies_enabled == 0)
9271 return;
9274 * the following code is intricate due to overriding several libsoup
9275 * functions.
9276 * do not alter order of these operations.
9279 /* rejected cookies */
9280 if (save_rejected_cookies)
9281 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9282 XT_REJECT_FILE);
9284 /* persistent cookies */
9285 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9286 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9288 /* session cookies */
9289 s_cookiejar = soup_cookie_jar_new();
9290 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9291 cookie_policy, (void *)NULL);
9292 transfer_cookies();
9294 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9297 void
9298 setup_proxy(char *uri)
9300 if (proxy_uri) {
9301 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9302 soup_uri_free(proxy_uri);
9303 proxy_uri = NULL;
9305 if (http_proxy) {
9306 if (http_proxy != uri) {
9307 g_free(http_proxy);
9308 http_proxy = NULL;
9312 if (uri) {
9313 http_proxy = g_strdup(uri);
9314 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9315 proxy_uri = soup_uri_new(http_proxy);
9316 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
9321 send_cmd_to_socket(char *cmd)
9323 int s, len, rv = 1;
9324 struct sockaddr_un sa;
9326 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9327 warnx("%s: socket", __func__);
9328 return (rv);
9331 sa.sun_family = AF_UNIX;
9332 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9333 work_dir, XT_SOCKET_FILE);
9334 len = SUN_LEN(&sa);
9336 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9337 warnx("%s: connect", __func__);
9338 goto done;
9341 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9342 warnx("%s: send", __func__);
9343 goto done;
9346 rv = 0;
9347 done:
9348 close(s);
9349 return (rv);
9352 gboolean
9353 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9355 int s, n;
9356 char str[XT_MAX_URL_LENGTH];
9357 socklen_t t = sizeof(struct sockaddr_un);
9358 struct sockaddr_un sa;
9359 struct passwd *p;
9360 uid_t uid;
9361 gid_t gid;
9362 struct tab *tt;
9363 gint fd = g_io_channel_unix_get_fd(source);
9365 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9366 warn("accept");
9367 return (FALSE);
9370 if (getpeereid(s, &uid, &gid) == -1) {
9371 warn("getpeereid");
9372 return (FALSE);
9374 if (uid != getuid() || gid != getgid()) {
9375 warnx("unauthorized user");
9376 return (FALSE);
9379 p = getpwuid(uid);
9380 if (p == NULL) {
9381 warnx("not a valid user");
9382 return (FALSE);
9385 n = recv(s, str, sizeof(str), 0);
9386 if (n <= 0)
9387 return (TRUE);
9389 tt = TAILQ_LAST(&tabs, tab_list);
9390 cmd_execute(tt, str);
9391 return (TRUE);
9395 is_running(void)
9397 int s, len, rv = 1;
9398 struct sockaddr_un sa;
9400 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9401 warn("is_running: socket");
9402 return (-1);
9405 sa.sun_family = AF_UNIX;
9406 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9407 work_dir, XT_SOCKET_FILE);
9408 len = SUN_LEN(&sa);
9410 /* connect to see if there is a listener */
9411 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9412 rv = 0; /* not running */
9413 else
9414 rv = 1; /* already running */
9416 close(s);
9418 return (rv);
9422 build_socket(void)
9424 int s, len;
9425 struct sockaddr_un sa;
9427 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9428 warn("build_socket: socket");
9429 return (-1);
9432 sa.sun_family = AF_UNIX;
9433 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9434 work_dir, XT_SOCKET_FILE);
9435 len = SUN_LEN(&sa);
9437 /* connect to see if there is a listener */
9438 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9439 /* no listener so we will */
9440 unlink(sa.sun_path);
9442 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9443 warn("build_socket: bind");
9444 goto done;
9447 if (listen(s, 1) == -1) {
9448 warn("build_socket: listen");
9449 goto done;
9452 return (s);
9455 done:
9456 close(s);
9457 return (-1);
9460 gboolean
9461 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9462 GtkTreeIter *iter, struct tab *t)
9464 gchar *value;
9466 gtk_tree_model_get(model, iter, 0, &value, -1);
9467 load_uri(t, value);
9468 g_free(value);
9470 return (FALSE);
9473 gboolean
9474 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9475 GtkTreeIter *iter, struct tab *t)
9477 gchar *value;
9479 gtk_tree_model_get(model, iter, 0, &value, -1);
9480 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9481 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9482 g_free(value);
9484 return (TRUE);
9487 void
9488 completion_add_uri(const gchar *uri)
9490 GtkTreeIter iter;
9492 /* add uri to list_store */
9493 gtk_list_store_append(completion_model, &iter);
9494 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9497 gboolean
9498 completion_match(GtkEntryCompletion *completion, const gchar *key,
9499 GtkTreeIter *iter, gpointer user_data)
9501 gchar *value;
9502 gboolean match = FALSE;
9504 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9505 -1);
9507 if (value == NULL)
9508 return FALSE;
9510 match = match_uri(value, key);
9512 g_free(value);
9513 return (match);
9516 void
9517 completion_add(struct tab *t)
9519 /* enable completion for tab */
9520 t->completion = gtk_entry_completion_new();
9521 gtk_entry_completion_set_text_column(t->completion, 0);
9522 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9523 gtk_entry_completion_set_model(t->completion,
9524 GTK_TREE_MODEL(completion_model));
9525 gtk_entry_completion_set_match_func(t->completion, completion_match,
9526 NULL, NULL);
9527 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9528 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9529 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9530 G_CALLBACK(completion_select_cb), t);
9531 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9532 G_CALLBACK(completion_hover_cb), t);
9535 void
9536 xxx_dir(char *dir)
9538 struct stat sb;
9540 if (stat(dir, &sb)) {
9541 if (mkdir(dir, S_IRWXU) == -1)
9542 err(1, "mkdir %s", dir);
9543 if (stat(dir, &sb))
9544 err(1, "stat %s", dir);
9546 if (S_ISDIR(sb.st_mode) == 0)
9547 errx(1, "%s not a dir", dir);
9548 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9549 warnx("fixing invalid permissions on %s", dir);
9550 if (chmod(dir, S_IRWXU) == -1)
9551 err(1, "chmod %s", dir);
9555 void
9556 usage(void)
9558 fprintf(stderr,
9559 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9560 exit(0);
9565 main(int argc, char *argv[])
9567 struct stat sb;
9568 int c, s, optn = 0, opte = 0, focus = 1;
9569 char conf[PATH_MAX] = { '\0' };
9570 char file[PATH_MAX];
9571 char *env_proxy = NULL;
9572 char *cmd = NULL;
9573 FILE *f = NULL;
9574 struct karg a;
9575 struct sigaction sact;
9576 GIOChannel *channel;
9577 struct rlimit rlp;
9579 start_argv = argv;
9581 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9583 RB_INIT(&hl);
9584 RB_INIT(&js_wl);
9585 RB_INIT(&downloads);
9587 TAILQ_INIT(&tabs);
9588 TAILQ_INIT(&mtl);
9589 TAILQ_INIT(&aliases);
9590 TAILQ_INIT(&undos);
9591 TAILQ_INIT(&kbl);
9592 TAILQ_INIT(&spl);
9594 /* fiddle with ulimits */
9595 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9596 warn("getrlimit");
9597 else {
9598 /* just use them all */
9599 rlp.rlim_cur = rlp.rlim_max;
9600 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9601 warn("setrlimit");
9602 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9603 warn("getrlimit");
9604 else if (rlp.rlim_cur <= 256)
9605 startpage_add("%s requires at least 256 file "
9606 "descriptors, currently it has up to %d available",
9607 __progname, rlp.rlim_cur);
9610 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9611 switch (c) {
9612 case 'S':
9613 show_url = 0;
9614 break;
9615 case 'T':
9616 show_tabs = 0;
9617 break;
9618 case 'V':
9619 errx(0 , "Version: %s", version);
9620 break;
9621 case 'f':
9622 strlcpy(conf, optarg, sizeof(conf));
9623 break;
9624 case 's':
9625 strlcpy(named_session, optarg, sizeof(named_session));
9626 break;
9627 case 't':
9628 tabless = 1;
9629 break;
9630 case 'n':
9631 optn = 1;
9632 break;
9633 case 'e':
9634 opte = 1;
9635 break;
9636 default:
9637 usage();
9638 /* NOTREACHED */
9641 argc -= optind;
9642 argv += optind;
9644 init_keybindings();
9646 gnutls_global_init();
9648 /* generate session keys for xtp pages */
9649 generate_xtp_session_key(&dl_session_key);
9650 generate_xtp_session_key(&hl_session_key);
9651 generate_xtp_session_key(&cl_session_key);
9652 generate_xtp_session_key(&fl_session_key);
9654 /* prepare gtk */
9655 if (!g_thread_supported()) {
9656 g_thread_init(NULL);
9657 gdk_threads_init();
9658 gdk_threads_enter();
9660 gtk_init(&argc, &argv);
9662 /* signals */
9663 bzero(&sact, sizeof(sact));
9664 sigemptyset(&sact.sa_mask);
9665 sact.sa_handler = sigchild;
9666 sact.sa_flags = SA_NOCLDSTOP;
9667 sigaction(SIGCHLD, &sact, NULL);
9669 /* set download dir */
9670 pwd = getpwuid(getuid());
9671 if (pwd == NULL)
9672 errx(1, "invalid user %d", getuid());
9673 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9675 /* compile buffer command regexes */
9676 buffercmd_init();
9678 /* set default string settings */
9679 home = g_strdup("https://www.cyphertite.com");
9680 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9681 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9682 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9683 cmd_font_name = g_strdup("monospace normal 9");
9684 oops_font_name = g_strdup("monospace normal 9");
9685 statusbar_font_name = g_strdup("monospace normal 9");
9686 tabbar_font_name = g_strdup("monospace normal 9");
9687 statusbar_elems = g_strdup("BP");
9689 /* read config file */
9690 if (strlen(conf) == 0)
9691 snprintf(conf, sizeof conf, "%s/.%s",
9692 pwd->pw_dir, XT_CONF_FILE);
9693 config_parse(conf, 0);
9695 /* init fonts */
9696 cmd_font = pango_font_description_from_string(cmd_font_name);
9697 oops_font = pango_font_description_from_string(oops_font_name);
9698 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9699 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9701 /* working directory */
9702 if (strlen(work_dir) == 0)
9703 snprintf(work_dir, sizeof work_dir, "%s/%s",
9704 pwd->pw_dir, XT_DIR);
9705 xxx_dir(work_dir);
9707 /* icon cache dir */
9708 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9709 xxx_dir(cache_dir);
9711 /* certs dir */
9712 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9713 xxx_dir(certs_dir);
9715 /* sessions dir */
9716 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9717 work_dir, XT_SESSIONS_DIR);
9718 xxx_dir(sessions_dir);
9720 /* runtime settings that can override config file */
9721 if (runtime_settings[0] != '\0')
9722 config_parse(runtime_settings, 1);
9724 /* download dir */
9725 if (!strcmp(download_dir, pwd->pw_dir))
9726 strlcat(download_dir, "/downloads", sizeof download_dir);
9727 xxx_dir(download_dir);
9729 /* favorites file */
9730 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9731 if (stat(file, &sb)) {
9732 warnx("favorites file doesn't exist, creating it");
9733 if ((f = fopen(file, "w")) == NULL)
9734 err(1, "favorites");
9735 fclose(f);
9738 /* quickmarks file */
9739 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9740 if (stat(file, &sb)) {
9741 warnx("quickmarks file doesn't exist, creating it");
9742 if ((f = fopen(file, "w")) == NULL)
9743 err(1, "quickmarks");
9744 fclose(f);
9747 /* cookies */
9748 session = webkit_get_default_session();
9749 setup_cookies();
9751 /* certs */
9752 if (ssl_ca_file) {
9753 if (stat(ssl_ca_file, &sb)) {
9754 warnx("no CA file: %s", ssl_ca_file);
9755 g_free(ssl_ca_file);
9756 ssl_ca_file = NULL;
9757 } else
9758 g_object_set(session,
9759 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9760 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9761 (void *)NULL);
9764 /* proxy */
9765 env_proxy = getenv("http_proxy");
9766 if (env_proxy)
9767 setup_proxy(env_proxy);
9768 else
9769 setup_proxy(http_proxy);
9771 if (opte) {
9772 send_cmd_to_socket(argv[0]);
9773 exit(0);
9776 /* set some connection parameters */
9777 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9778 g_object_set(session, "max-conns-per-host", max_host_connections,
9779 (char *)NULL);
9781 /* see if there is already an xxxterm running */
9782 if (single_instance && is_running()) {
9783 optn = 1;
9784 warnx("already running");
9787 if (optn) {
9788 while (argc) {
9789 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9790 send_cmd_to_socket(cmd);
9791 if (cmd)
9792 g_free(cmd);
9794 argc--;
9795 argv++;
9797 exit(0);
9800 /* uri completion */
9801 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9803 /* buffers */
9804 buffers_store = gtk_list_store_new
9805 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9807 qmarks_load();
9809 /* go graphical */
9810 create_canvas();
9811 notebook_tab_set_visibility();
9813 if (save_global_history)
9814 restore_global_history();
9816 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9817 restore_saved_tabs();
9818 else {
9819 a.s = named_session;
9820 a.i = XT_SES_DONOTHING;
9821 open_tabs(NULL, &a);
9824 /* see if we have an exception */
9825 if (!TAILQ_EMPTY(&spl)) {
9826 create_new_tab("about:startpage", NULL, focus, -1);
9827 focus = 0;
9830 while (argc) {
9831 create_new_tab(argv[0], NULL, focus, -1);
9832 focus = 0;
9834 argc--;
9835 argv++;
9838 if (TAILQ_EMPTY(&tabs))
9839 create_new_tab(home, NULL, 1, -1);
9841 if (enable_socket)
9842 if ((s = build_socket()) != -1) {
9843 channel = g_io_channel_unix_new(s);
9844 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9847 gtk_main();
9849 if (!g_thread_supported()) {
9850 gdk_threads_leave();
9853 gnutls_global_deinit();
9855 return (0);