use an actual working heuristic to determine if something is a valid url;
[xxxterm.git] / xxxterm.c
blob423bba3933544e1c8fa8e79aa2b46db509b0e65a
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 int set_http_proxy(char *);
627 struct special {
628 int (*set)(struct settings *, char *);
629 char *(*get)(struct settings *);
630 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
633 struct special s_browser_mode = {
634 set_browser_mode,
635 get_browser_mode,
636 NULL
639 struct special s_cookie = {
640 set_cookie_policy,
641 get_cookie_policy,
642 NULL
645 struct special s_alias = {
646 add_alias,
647 NULL,
648 walk_alias
651 struct special s_mime = {
652 add_mime_type,
653 NULL,
654 walk_mime_type
657 struct special s_js = {
658 add_js_wl,
659 NULL,
660 walk_js_wl
663 struct special s_kb = {
664 add_kb,
665 NULL,
666 walk_kb
669 struct special s_cookie_wl = {
670 add_cookie_wl,
671 NULL,
672 walk_cookie_wl
675 struct special s_default_script = {
676 set_default_script,
677 get_default_script,
678 NULL
681 struct special s_download_dir = {
682 set_download_dir,
683 get_download_dir,
684 NULL
687 struct special s_work_dir = {
688 set_work_dir,
689 get_work_dir,
690 NULL
693 struct special s_tab_style = {
694 set_tab_style,
695 get_tab_style,
696 NULL
699 struct settings {
700 char *name;
701 int type;
702 #define XT_S_INVALID (0)
703 #define XT_S_INT (1)
704 #define XT_S_STR (2)
705 #define XT_S_FLOAT (3)
706 uint32_t flags;
707 #define XT_SF_RESTART (1<<0)
708 #define XT_SF_RUNTIME (1<<1)
709 int *ival;
710 char **sval;
711 struct special *s;
712 gfloat *fval;
713 int (*activate)(char *);
714 } rs[] = {
715 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
716 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
717 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
718 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
719 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
720 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
721 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
722 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
723 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
724 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
725 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
726 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
727 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
728 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
729 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
730 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
731 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
732 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
733 { "home", XT_S_STR, 0, NULL, &home, NULL },
734 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
735 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
736 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
737 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
738 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
739 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
740 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
741 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
742 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
743 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
744 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
745 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
746 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
747 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
748 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
749 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
750 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
751 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
752 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
753 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
754 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
755 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
756 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
757 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
758 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
759 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
761 /* font settings */
762 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
763 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
764 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
765 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
767 /* runtime settings */
768 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
769 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
770 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
771 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
772 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
775 int about(struct tab *, struct karg *);
776 int blank(struct tab *, struct karg *);
777 int ca_cmd(struct tab *, struct karg *);
778 int cookie_show_wl(struct tab *, struct karg *);
779 int js_show_wl(struct tab *, struct karg *);
780 int help(struct tab *, struct karg *);
781 int set(struct tab *, struct karg *);
782 int stats(struct tab *, struct karg *);
783 int marco(struct tab *, struct karg *);
784 int startpage(struct tab *, struct karg *);
785 const char * marco_message(int *);
786 int xtp_page_cl(struct tab *, struct karg *);
787 int xtp_page_dl(struct tab *, struct karg *);
788 int xtp_page_fl(struct tab *, struct karg *);
789 int xtp_page_hl(struct tab *, struct karg *);
790 void xt_icon_from_file(struct tab *, char *);
791 const gchar *get_uri(struct tab *);
792 const gchar *get_title(struct tab *, bool);
794 #define XT_URI_ABOUT ("about:")
795 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
796 #define XT_URI_ABOUT_ABOUT ("about")
797 #define XT_URI_ABOUT_BLANK ("blank")
798 #define XT_URI_ABOUT_CERTS ("certs")
799 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
800 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
801 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
802 #define XT_URI_ABOUT_FAVORITES ("favorites")
803 #define XT_URI_ABOUT_HELP ("help")
804 #define XT_URI_ABOUT_HISTORY ("history")
805 #define XT_URI_ABOUT_JSWL ("jswl")
806 #define XT_URI_ABOUT_SET ("set")
807 #define XT_URI_ABOUT_STATS ("stats")
808 #define XT_URI_ABOUT_MARCO ("marco")
809 #define XT_URI_ABOUT_STARTPAGE ("startpage")
811 struct about_type {
812 char *name;
813 int (*func)(struct tab *, struct karg *);
814 } about_list[] = {
815 { XT_URI_ABOUT_ABOUT, about },
816 { XT_URI_ABOUT_BLANK, blank },
817 { XT_URI_ABOUT_CERTS, ca_cmd },
818 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
819 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
820 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
821 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
822 { XT_URI_ABOUT_HELP, help },
823 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
824 { XT_URI_ABOUT_JSWL, js_show_wl },
825 { XT_URI_ABOUT_SET, set },
826 { XT_URI_ABOUT_STATS, stats },
827 { XT_URI_ABOUT_MARCO, marco },
828 { XT_URI_ABOUT_STARTPAGE, startpage },
831 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
832 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
833 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
834 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
835 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
836 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
837 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
839 /* globals */
840 extern char *__progname;
841 char **start_argv;
842 struct passwd *pwd;
843 GtkWidget *main_window;
844 GtkNotebook *notebook;
845 GtkWidget *tab_bar;
846 GtkWidget *arrow, *abtn;
847 struct tab_list tabs;
848 struct history_list hl;
849 struct download_list downloads;
850 struct domain_list c_wl;
851 struct domain_list js_wl;
852 struct undo_tailq undos;
853 struct keybinding_list kbl;
854 struct sp_list spl;
855 int undo_count;
856 int updating_dl_tabs = 0;
857 int updating_hl_tabs = 0;
858 int updating_cl_tabs = 0;
859 int updating_fl_tabs = 0;
860 char *global_search;
861 uint64_t blocked_cookies = 0;
862 char named_session[PATH_MAX];
863 int icon_size_map(int);
865 GtkListStore *completion_model;
866 void completion_add(struct tab *);
867 void completion_add_uri(const gchar *);
868 GtkListStore *buffers_store;
869 void xxx_dir(char *);
871 /* marks and quickmarks array storage.
872 * first a-z, then A-Z, then 0-9 */
873 char
874 indextomark(int i)
876 if (i < 0)
877 return 0;
879 if (i >= 0 && i <= 'z' - 'a')
880 return 'a' + i;
882 i -= 'z' - 'a' + 1;
883 if (i >= 0 && i <= 'Z' - 'A')
884 return 'A' + i;
886 i -= 'Z' - 'A' + 1;
887 if (i >= 10)
888 return 0;
890 return i + '0';
894 marktoindex(char m)
896 int ret = 0;
898 if (m >= 'a' && m <= 'z')
899 return ret + m - 'a';
901 ret += 'z' - 'a' + 1;
902 if (m >= 'A' && m <= 'Z')
903 return ret + m - 'A';
905 ret += 'Z' - 'A' + 1;
906 if (m >= '0' && m <= '9')
907 return ret + m - '0';
909 return -1;
913 void
914 sigchild(int sig)
916 int saved_errno, status;
917 pid_t pid;
919 saved_errno = errno;
921 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
922 if (pid == -1) {
923 if (errno == EINTR)
924 continue;
925 if (errno != ECHILD) {
927 clog_warn("sigchild: waitpid:");
930 break;
933 if (WIFEXITED(status)) {
934 if (WEXITSTATUS(status) != 0) {
936 clog_warnx("sigchild: child exit status: %d",
937 WEXITSTATUS(status));
940 } else {
942 clog_warnx("sigchild: child is terminated abnormally");
947 errno = saved_errno;
951 is_g_object_setting(GObject *o, char *str)
953 guint n_props = 0, i;
954 GParamSpec **proplist;
956 if (! G_IS_OBJECT(o))
957 return (0);
959 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
960 &n_props);
962 for (i=0; i < n_props; i++) {
963 if (! strcmp(proplist[i]->name, str))
964 return (1);
966 return (0);
969 gchar *
970 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
972 gchar *r;
974 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
975 "<head>\n"
976 "<title>%s</title>\n"
977 "%s"
978 "%s"
979 "</head>\n"
980 "<body>\n"
981 "<h1>%s</h1>\n"
982 "%s\n</body>\n"
983 "</html>",
984 title,
985 addstyles ? XT_PAGE_STYLE : "",
986 head,
987 title,
988 body);
990 return r;
994 * Display a web page from a HTML string in memory, rather than from a URL
996 void
997 load_webkit_string(struct tab *t, const char *str, gchar *title)
999 char file[PATH_MAX];
1000 int i;
1002 /* we set this to indicate we want to manually do navaction */
1003 if (t->bfl)
1004 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1006 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1007 if (title) {
1008 /* set t->xtp_meaning */
1009 for (i = 0; i < LENGTH(about_list); i++)
1010 if (!strcmp(title, about_list[i].name)) {
1011 t->xtp_meaning = i;
1012 break;
1015 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1016 #if GTK_CHECK_VERSION(2, 20, 0)
1017 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1018 gtk_widget_hide(t->spinner);
1019 #endif
1020 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1021 xt_icon_from_file(t, file);
1025 struct tab *
1026 get_current_tab(void)
1028 struct tab *t;
1030 TAILQ_FOREACH(t, &tabs, entry) {
1031 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1032 return (t);
1035 warnx("%s: no current tab", __func__);
1037 return (NULL);
1040 void
1041 set_status(struct tab *t, gchar *s, int status)
1043 gchar *type = NULL;
1045 if (s == NULL)
1046 return;
1048 switch (status) {
1049 case XT_STATUS_LOADING:
1050 type = g_strdup_printf("Loading: %s", s);
1051 s = type;
1052 break;
1053 case XT_STATUS_LINK:
1054 type = g_strdup_printf("Link: %s", s);
1055 if (!t->status)
1056 t->status = g_strdup(gtk_entry_get_text(
1057 GTK_ENTRY(t->sbe.statusbar)));
1058 s = type;
1059 break;
1060 case XT_STATUS_URI:
1061 type = g_strdup_printf("%s", s);
1062 if (!t->status) {
1063 t->status = g_strdup(type);
1065 s = type;
1066 if (!t->status)
1067 t->status = g_strdup(s);
1068 break;
1069 case XT_STATUS_NOTHING:
1070 /* FALL THROUGH */
1071 default:
1072 break;
1074 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1075 if (type)
1076 g_free(type);
1079 void
1080 hide_cmd(struct tab *t)
1082 gtk_widget_hide(t->cmd);
1085 void
1086 show_cmd(struct tab *t)
1088 gtk_widget_hide(t->oops);
1089 gtk_widget_show(t->cmd);
1092 void
1093 hide_buffers(struct tab *t)
1095 gtk_widget_hide(t->buffers);
1096 gtk_list_store_clear(buffers_store);
1099 enum {
1100 COL_ID = 0,
1101 COL_TITLE,
1102 NUM_COLS
1106 sort_tabs_by_page_num(struct tab ***stabs)
1108 int num_tabs = 0;
1109 struct tab *t;
1111 num_tabs = gtk_notebook_get_n_pages(notebook);
1113 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1115 TAILQ_FOREACH(t, &tabs, entry)
1116 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1118 return (num_tabs);
1121 void
1122 buffers_make_list(void)
1124 int i, num_tabs;
1125 const gchar *title = NULL;
1126 GtkTreeIter iter;
1127 struct tab **stabs = NULL;
1129 num_tabs = sort_tabs_by_page_num(&stabs);
1131 for (i = 0; i < num_tabs; i++)
1132 if (stabs[i]) {
1133 gtk_list_store_append(buffers_store, &iter);
1134 title = get_title(stabs[i], FALSE);
1135 gtk_list_store_set(buffers_store, &iter,
1136 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1137 * rather than 0. */
1138 COL_TITLE, title,
1139 -1);
1142 g_free(stabs);
1145 void
1146 show_buffers(struct tab *t)
1148 buffers_make_list();
1149 gtk_widget_show(t->buffers);
1150 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1153 void
1154 toggle_buffers(struct tab *t)
1156 if (gtk_widget_get_visible(t->buffers))
1157 hide_buffers(t);
1158 else
1159 show_buffers(t);
1163 buffers(struct tab *t, struct karg *args)
1165 show_buffers(t);
1167 return (0);
1170 void
1171 hide_oops(struct tab *t)
1173 gtk_widget_hide(t->oops);
1176 void
1177 show_oops(struct tab *at, const char *fmt, ...)
1179 va_list ap;
1180 char *msg = NULL;
1181 struct tab *t = NULL;
1183 if (fmt == NULL)
1184 return;
1186 if (at == NULL) {
1187 if ((t = get_current_tab()) == NULL)
1188 return;
1189 } else
1190 t = at;
1192 va_start(ap, fmt);
1193 if (vasprintf(&msg, fmt, ap) == -1)
1194 errx(1, "show_oops failed");
1195 va_end(ap);
1197 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1198 gtk_widget_hide(t->cmd);
1199 gtk_widget_show(t->oops);
1201 if (msg)
1202 free(msg);
1205 char *
1206 get_as_string(struct settings *s)
1208 char *r = NULL;
1210 if (s == NULL)
1211 return (NULL);
1213 if (s->s) {
1214 if (s->s->get)
1215 r = s->s->get(s);
1216 else
1217 warnx("get_as_string skip %s\n", s->name);
1218 } else if (s->type == XT_S_INT)
1219 r = g_strdup_printf("%d", *s->ival);
1220 else if (s->type == XT_S_STR)
1221 r = g_strdup(*s->sval);
1222 else if (s->type == XT_S_FLOAT)
1223 r = g_strdup_printf("%f", *s->fval);
1224 else
1225 r = g_strdup_printf("INVALID TYPE");
1227 return (r);
1230 void
1231 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1233 int i;
1234 char *s;
1236 for (i = 0; i < LENGTH(rs); i++) {
1237 if (rs[i].s && rs[i].s->walk)
1238 rs[i].s->walk(&rs[i], cb, cb_args);
1239 else {
1240 s = get_as_string(&rs[i]);
1241 cb(&rs[i], s, cb_args);
1242 g_free(s);
1248 set_browser_mode(struct settings *s, char *val)
1250 if (!strcmp(val, "whitelist")) {
1251 browser_mode = XT_BM_WHITELIST;
1252 allow_volatile_cookies = 0;
1253 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1254 cookies_enabled = 1;
1255 enable_cookie_whitelist = 1;
1256 read_only_cookies = 0;
1257 save_rejected_cookies = 0;
1258 session_timeout = 3600;
1259 enable_scripts = 0;
1260 enable_js_whitelist = 1;
1261 enable_localstorage = 0;
1262 } else if (!strcmp(val, "normal")) {
1263 browser_mode = XT_BM_NORMAL;
1264 allow_volatile_cookies = 0;
1265 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1266 cookies_enabled = 1;
1267 enable_cookie_whitelist = 0;
1268 read_only_cookies = 0;
1269 save_rejected_cookies = 0;
1270 session_timeout = 3600;
1271 enable_scripts = 1;
1272 enable_js_whitelist = 0;
1273 enable_localstorage = 1;
1274 } else if (!strcmp(val, "kiosk")) {
1275 browser_mode = XT_BM_KIOSK;
1276 allow_volatile_cookies = 0;
1277 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1278 cookies_enabled = 1;
1279 enable_cookie_whitelist = 0;
1280 read_only_cookies = 0;
1281 save_rejected_cookies = 0;
1282 session_timeout = 3600;
1283 enable_scripts = 1;
1284 enable_js_whitelist = 0;
1285 enable_localstorage = 1;
1286 show_tabs = 0;
1287 tabless = 1;
1288 } else
1289 return (1);
1291 return (0);
1294 char *
1295 get_browser_mode(struct settings *s)
1297 char *r = NULL;
1299 if (browser_mode == XT_BM_WHITELIST)
1300 r = g_strdup("whitelist");
1301 else if (browser_mode == XT_BM_NORMAL)
1302 r = g_strdup("normal");
1303 else if (browser_mode == XT_BM_KIOSK)
1304 r = g_strdup("kiosk");
1305 else
1306 return (NULL);
1308 return (r);
1312 set_cookie_policy(struct settings *s, char *val)
1314 if (!strcmp(val, "no3rdparty"))
1315 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1316 else if (!strcmp(val, "accept"))
1317 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1318 else if (!strcmp(val, "reject"))
1319 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1320 else
1321 return (1);
1323 return (0);
1326 char *
1327 get_cookie_policy(struct settings *s)
1329 char *r = NULL;
1331 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1332 r = g_strdup("no3rdparty");
1333 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1334 r = g_strdup("accept");
1335 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1336 r = g_strdup("reject");
1337 else
1338 return (NULL);
1340 return (r);
1343 char *
1344 get_default_script(struct settings *s)
1346 if (default_script[0] == '\0')
1347 return (0);
1348 return (g_strdup(default_script));
1352 set_default_script(struct settings *s, char *val)
1354 if (val[0] == '~')
1355 snprintf(default_script, sizeof default_script, "%s/%s",
1356 pwd->pw_dir, &val[1]);
1357 else
1358 strlcpy(default_script, val, sizeof default_script);
1360 return (0);
1363 char *
1364 get_download_dir(struct settings *s)
1366 if (download_dir[0] == '\0')
1367 return (0);
1368 return (g_strdup(download_dir));
1372 set_download_dir(struct settings *s, char *val)
1374 if (val[0] == '~')
1375 snprintf(download_dir, sizeof download_dir, "%s/%s",
1376 pwd->pw_dir, &val[1]);
1377 else
1378 strlcpy(download_dir, val, sizeof download_dir);
1380 return (0);
1384 * Session IDs.
1385 * We use these to prevent people putting xxxt:// URLs on
1386 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1388 #define XT_XTP_SES_KEY_SZ 8
1389 #define XT_XTP_SES_KEY_HEX_FMT \
1390 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1391 char *dl_session_key; /* downloads */
1392 char *hl_session_key; /* history list */
1393 char *cl_session_key; /* cookie list */
1394 char *fl_session_key; /* favorites list */
1396 char work_dir[PATH_MAX];
1397 char certs_dir[PATH_MAX];
1398 char cache_dir[PATH_MAX];
1399 char sessions_dir[PATH_MAX];
1400 char cookie_file[PATH_MAX];
1401 SoupURI *proxy_uri = NULL;
1402 SoupSession *session;
1403 SoupCookieJar *s_cookiejar;
1404 SoupCookieJar *p_cookiejar;
1405 char rc_fname[PATH_MAX];
1407 struct mime_type_list mtl;
1408 struct alias_list aliases;
1410 /* protos */
1411 struct tab *create_new_tab(char *, struct undo *, int, int);
1412 void delete_tab(struct tab *);
1413 void setzoom_webkit(struct tab *, int);
1414 int run_script(struct tab *, char *);
1415 int download_rb_cmp(struct download *, struct download *);
1416 gboolean cmd_execute(struct tab *t, char *str);
1419 history_rb_cmp(struct history *h1, struct history *h2)
1421 return (strcmp(h1->uri, h2->uri));
1423 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1426 domain_rb_cmp(struct domain *d1, struct domain *d2)
1428 return (strcmp(d1->d, d2->d));
1430 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1432 char *
1433 get_work_dir(struct settings *s)
1435 if (work_dir[0] == '\0')
1436 return (0);
1437 return (g_strdup(work_dir));
1441 set_work_dir(struct settings *s, char *val)
1443 if (val[0] == '~')
1444 snprintf(work_dir, sizeof work_dir, "%s/%s",
1445 pwd->pw_dir, &val[1]);
1446 else
1447 strlcpy(work_dir, val, sizeof work_dir);
1449 return (0);
1452 char *
1453 get_tab_style(struct settings *s)
1455 if (tab_style == XT_TABS_NORMAL)
1456 return (g_strdup("normal"));
1457 else
1458 return (g_strdup("compact"));
1462 set_tab_style(struct settings *s, char *val)
1464 if (!strcmp(val, "normal"))
1465 tab_style = XT_TABS_NORMAL;
1466 else if (!strcmp(val, "compact"))
1467 tab_style = XT_TABS_COMPACT;
1468 else
1469 return (1);
1471 return (0);
1475 * generate a session key to secure xtp commands.
1476 * pass in a ptr to the key in question and it will
1477 * be modified in place.
1479 void
1480 generate_xtp_session_key(char **key)
1482 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1484 /* free old key */
1485 if (*key)
1486 g_free(*key);
1488 /* make a new one */
1489 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1490 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1491 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1492 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1494 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1498 * validate a xtp session key.
1499 * return 1 if OK
1502 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1504 if (strcmp(trusted, untrusted) != 0) {
1505 show_oops(t, "%s: xtp session key mismatch possible spoof",
1506 __func__);
1507 return (0);
1510 return (1);
1514 download_rb_cmp(struct download *e1, struct download *e2)
1516 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1518 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1520 struct valid_url_types {
1521 char *type;
1522 } vut[] = {
1523 { "http://" },
1524 { "https://" },
1525 { "ftp://" },
1526 { "file://" },
1527 { XT_XTP_STR },
1531 valid_url_type(char *url)
1533 int i;
1535 for (i = 0; i < LENGTH(vut); i++)
1536 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1537 return (0);
1539 return (1);
1542 void
1543 print_cookie(char *msg, SoupCookie *c)
1545 if (c == NULL)
1546 return;
1548 if (msg)
1549 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1550 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1551 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1552 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1553 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1554 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1555 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1556 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1557 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1558 DNPRINTF(XT_D_COOKIE, "====================================\n");
1561 void
1562 walk_alias(struct settings *s,
1563 void (*cb)(struct settings *, char *, void *), void *cb_args)
1565 struct alias *a;
1566 char *str;
1568 if (s == NULL || cb == NULL) {
1569 show_oops(NULL, "walk_alias invalid parameters");
1570 return;
1573 TAILQ_FOREACH(a, &aliases, entry) {
1574 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1575 cb(s, str, cb_args);
1576 g_free(str);
1580 char *
1581 match_alias(char *url_in)
1583 struct alias *a;
1584 char *arg;
1585 char *url_out = NULL, *search, *enc_arg;
1587 search = g_strdup(url_in);
1588 arg = search;
1589 if (strsep(&arg, " \t") == NULL) {
1590 show_oops(NULL, "match_alias: NULL URL");
1591 goto done;
1594 TAILQ_FOREACH(a, &aliases, entry) {
1595 if (!strcmp(search, a->a_name))
1596 break;
1599 if (a != NULL) {
1600 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1601 a->a_name);
1602 if (arg != NULL) {
1603 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1604 url_out = g_strdup_printf(a->a_uri, enc_arg);
1605 g_free(enc_arg);
1606 } else
1607 url_out = g_strdup_printf(a->a_uri, "");
1609 done:
1610 g_free(search);
1611 return (url_out);
1614 char *
1615 guess_url_type(char *url_in)
1617 struct stat sb;
1618 char *url_out = NULL, *enc_search = NULL;
1619 SoupURI *uri = NULL;
1621 url_out = match_alias(url_in);
1622 if (url_out != NULL)
1623 return (url_out);
1625 if (guess_search) {
1626 uri = soup_uri_new(url_in);
1627 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
1628 /* invalid URI so search instead */
1629 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1630 url_out = g_strdup_printf(search_string, enc_search);
1631 g_free(enc_search);
1632 goto done;
1636 /* XXX not sure about this heuristic */
1637 if (stat(url_in, &sb) == 0)
1638 url_out = g_strdup_printf("file://%s", url_in);
1639 else
1640 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1641 done:
1642 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1643 if (uri)
1644 soup_uri_free(uri);
1646 return (url_out);
1649 void
1650 load_uri(struct tab *t, gchar *uri)
1652 struct karg args;
1653 gchar *newuri = NULL;
1654 int i;
1656 if (uri == NULL)
1657 return;
1659 /* Strip leading spaces. */
1660 while (*uri && isspace(*uri))
1661 uri++;
1663 if (strlen(uri) == 0) {
1664 blank(t, NULL);
1665 return;
1668 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1670 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1671 for (i = 0; i < LENGTH(about_list); i++)
1672 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1673 bzero(&args, sizeof args);
1674 about_list[i].func(t, &args);
1675 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1676 FALSE);
1677 return;
1679 show_oops(t, "invalid about page");
1680 return;
1683 if (valid_url_type(uri)) {
1684 newuri = guess_url_type(uri);
1685 uri = newuri;
1688 set_status(t, (char *)uri, XT_STATUS_LOADING);
1689 marks_clear(t);
1690 webkit_web_view_load_uri(t->wv, uri);
1692 if (newuri)
1693 g_free(newuri);
1696 const gchar *
1697 get_uri(struct tab *t)
1699 const gchar *uri = NULL;
1701 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1702 return t->tmp_uri;
1703 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1704 uri = webkit_web_view_get_uri(t->wv);
1705 } else {
1706 /* use tmp_uri to make sure it is g_freed */
1707 if (t->tmp_uri)
1708 g_free(t->tmp_uri);
1709 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1710 about_list[t->xtp_meaning].name);
1711 uri = t->tmp_uri;
1713 return uri;
1716 const gchar *
1717 get_title(struct tab *t, bool window)
1719 const gchar *set = NULL, *title = NULL;
1720 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1722 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1723 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1724 goto notitle;
1726 title = webkit_web_view_get_title(t->wv);
1727 if ((set = title ? title : get_uri(t)))
1728 return set;
1730 notitle:
1731 set = window ? XT_NAME : "(untitled)";
1733 return set;
1737 add_alias(struct settings *s, char *line)
1739 char *l, *alias;
1740 struct alias *a = NULL;
1742 if (s == NULL || line == NULL) {
1743 show_oops(NULL, "add_alias invalid parameters");
1744 return (1);
1747 l = line;
1748 a = g_malloc(sizeof(*a));
1750 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1751 show_oops(NULL, "add_alias: incomplete alias definition");
1752 goto bad;
1754 if (strlen(alias) == 0 || strlen(l) == 0) {
1755 show_oops(NULL, "add_alias: invalid alias definition");
1756 goto bad;
1759 a->a_name = g_strdup(alias);
1760 a->a_uri = g_strdup(l);
1762 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1764 TAILQ_INSERT_TAIL(&aliases, a, entry);
1766 return (0);
1767 bad:
1768 if (a)
1769 g_free(a);
1770 return (1);
1774 add_mime_type(struct settings *s, char *line)
1776 char *mime_type;
1777 char *l;
1778 struct mime_type *m = NULL;
1779 int downloadfirst = 0;
1781 /* XXX this could be smarter */
1783 if (line == NULL || strlen(line) == 0) {
1784 show_oops(NULL, "add_mime_type invalid parameters");
1785 return (1);
1788 l = line;
1789 if (*l == '@') {
1790 downloadfirst = 1;
1791 l++;
1793 m = g_malloc(sizeof(*m));
1795 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1796 show_oops(NULL, "add_mime_type: invalid mime_type");
1797 goto bad;
1799 if (mime_type[strlen(mime_type) - 1] == '*') {
1800 mime_type[strlen(mime_type) - 1] = '\0';
1801 m->mt_default = 1;
1802 } else
1803 m->mt_default = 0;
1805 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1806 show_oops(NULL, "add_mime_type: invalid mime_type");
1807 goto bad;
1810 m->mt_type = g_strdup(mime_type);
1811 m->mt_action = g_strdup(l);
1812 m->mt_download = downloadfirst;
1814 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1815 m->mt_type, m->mt_action, m->mt_default);
1817 TAILQ_INSERT_TAIL(&mtl, m, entry);
1819 return (0);
1820 bad:
1821 if (m)
1822 g_free(m);
1823 return (1);
1826 struct mime_type *
1827 find_mime_type(char *mime_type)
1829 struct mime_type *m, *def = NULL, *rv = NULL;
1831 TAILQ_FOREACH(m, &mtl, entry) {
1832 if (m->mt_default &&
1833 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1834 def = m;
1836 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1837 rv = m;
1838 break;
1842 if (rv == NULL)
1843 rv = def;
1845 return (rv);
1848 void
1849 walk_mime_type(struct settings *s,
1850 void (*cb)(struct settings *, char *, void *), void *cb_args)
1852 struct mime_type *m;
1853 char *str;
1855 if (s == NULL || cb == NULL) {
1856 show_oops(NULL, "walk_mime_type invalid parameters");
1857 return;
1860 TAILQ_FOREACH(m, &mtl, entry) {
1861 str = g_strdup_printf("%s%s --> %s",
1862 m->mt_type,
1863 m->mt_default ? "*" : "",
1864 m->mt_action);
1865 cb(s, str, cb_args);
1866 g_free(str);
1870 void
1871 wl_add(char *str, struct domain_list *wl, int handy)
1873 struct domain *d;
1874 int add_dot = 0;
1876 if (str == NULL || wl == NULL || strlen(str) < 2)
1877 return;
1879 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1881 /* treat *.moo.com the same as .moo.com */
1882 if (str[0] == '*' && str[1] == '.')
1883 str = &str[1];
1884 else if (str[0] == '.')
1885 str = &str[0];
1886 else
1887 add_dot = 1;
1889 d = g_malloc(sizeof *d);
1890 if (add_dot)
1891 d->d = g_strdup_printf(".%s", str);
1892 else
1893 d->d = g_strdup(str);
1894 d->handy = handy;
1896 if (RB_INSERT(domain_list, wl, d))
1897 goto unwind;
1899 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1900 return;
1901 unwind:
1902 if (d) {
1903 if (d->d)
1904 g_free(d->d);
1905 g_free(d);
1910 add_cookie_wl(struct settings *s, char *entry)
1912 wl_add(entry, &c_wl, 1);
1913 return (0);
1916 void
1917 walk_cookie_wl(struct settings *s,
1918 void (*cb)(struct settings *, char *, void *), void *cb_args)
1920 struct domain *d;
1922 if (s == NULL || cb == NULL) {
1923 show_oops(NULL, "walk_cookie_wl invalid parameters");
1924 return;
1927 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1928 cb(s, d->d, cb_args);
1931 void
1932 walk_js_wl(struct settings *s,
1933 void (*cb)(struct settings *, char *, void *), void *cb_args)
1935 struct domain *d;
1937 if (s == NULL || cb == NULL) {
1938 show_oops(NULL, "walk_js_wl invalid parameters");
1939 return;
1942 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1943 cb(s, d->d, cb_args);
1947 add_js_wl(struct settings *s, char *entry)
1949 wl_add(entry, &js_wl, 1 /* persistent */);
1950 return (0);
1953 struct domain *
1954 wl_find(const gchar *search, struct domain_list *wl)
1956 int i;
1957 struct domain *d = NULL, dfind;
1958 gchar *s = NULL;
1960 if (search == NULL || wl == NULL)
1961 return (NULL);
1962 if (strlen(search) < 2)
1963 return (NULL);
1965 if (search[0] != '.')
1966 s = g_strdup_printf(".%s", search);
1967 else
1968 s = g_strdup(search);
1970 for (i = strlen(s) - 1; i >= 0; i--) {
1971 if (s[i] == '.') {
1972 dfind.d = &s[i];
1973 d = RB_FIND(domain_list, wl, &dfind);
1974 if (d)
1975 goto done;
1979 done:
1980 if (s)
1981 g_free(s);
1983 return (d);
1986 struct domain *
1987 wl_find_uri(const gchar *s, struct domain_list *wl)
1989 int i;
1990 char *ss;
1991 struct domain *r;
1993 if (s == NULL || wl == NULL)
1994 return (NULL);
1996 if (!strncmp(s, "http://", strlen("http://")))
1997 s = &s[strlen("http://")];
1998 else if (!strncmp(s, "https://", strlen("https://")))
1999 s = &s[strlen("https://")];
2001 if (strlen(s) < 2)
2002 return (NULL);
2004 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2005 /* chop string at first slash */
2006 if (s[i] == '/' || s[i] == '\0') {
2007 ss = g_strdup(s);
2008 ss[i] = '\0';
2009 r = wl_find(ss, wl);
2010 g_free(ss);
2011 return (r);
2014 return (NULL);
2018 settings_add(char *var, char *val)
2020 int i, rv, *p;
2021 gfloat *f;
2022 char **s;
2024 /* get settings */
2025 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2026 if (strcmp(var, rs[i].name))
2027 continue;
2029 if (rs[i].s) {
2030 if (rs[i].s->set(&rs[i], val))
2031 errx(1, "invalid value for %s: %s", var, val);
2032 rv = 1;
2033 break;
2034 } else
2035 switch (rs[i].type) {
2036 case XT_S_INT:
2037 p = rs[i].ival;
2038 *p = atoi(val);
2039 rv = 1;
2040 break;
2041 case XT_S_STR:
2042 s = rs[i].sval;
2043 if (s == NULL)
2044 errx(1, "invalid sval for %s",
2045 rs[i].name);
2046 if (*s)
2047 g_free(*s);
2048 *s = g_strdup(val);
2049 rv = 1;
2050 break;
2051 case XT_S_FLOAT:
2052 f = rs[i].fval;
2053 *f = atof(val);
2054 rv = 1;
2055 break;
2056 case XT_S_INVALID:
2057 default:
2058 errx(1, "invalid type for %s", var);
2060 break;
2062 return (rv);
2065 #define WS "\n= \t"
2066 void
2067 config_parse(char *filename, int runtime)
2069 FILE *config, *f;
2070 char *line, *cp, *var, *val;
2071 size_t len, lineno = 0;
2072 int handled;
2073 char file[PATH_MAX];
2074 struct stat sb;
2076 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2078 if (filename == NULL)
2079 return;
2081 if (runtime && runtime_settings[0] != '\0') {
2082 snprintf(file, sizeof file, "%s/%s",
2083 work_dir, runtime_settings);
2084 if (stat(file, &sb)) {
2085 warnx("runtime file doesn't exist, creating it");
2086 if ((f = fopen(file, "w")) == NULL)
2087 err(1, "runtime");
2088 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2089 fclose(f);
2091 } else
2092 strlcpy(file, filename, sizeof file);
2094 if ((config = fopen(file, "r")) == NULL) {
2095 warn("config_parse: cannot open %s", filename);
2096 return;
2099 for (;;) {
2100 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2101 if (feof(config) || ferror(config))
2102 break;
2104 cp = line;
2105 cp += (long)strspn(cp, WS);
2106 if (cp[0] == '\0') {
2107 /* empty line */
2108 free(line);
2109 continue;
2112 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2113 startpage_add("invalid configuration file entry: %s",
2114 line);
2116 cp += (long)strspn(cp, WS);
2118 if ((val = strsep(&cp, "\0")) == NULL)
2119 break;
2121 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2122 handled = settings_add(var, val);
2123 if (handled == 0)
2124 startpage_add("invalid configuration file entry: %s=%s",
2125 var, val);
2127 free(line);
2130 fclose(config);
2133 char *
2134 js_ref_to_string(JSContextRef context, JSValueRef ref)
2136 char *s = NULL;
2137 size_t l;
2138 JSStringRef jsref;
2140 jsref = JSValueToStringCopy(context, ref, NULL);
2141 if (jsref == NULL)
2142 return (NULL);
2144 l = JSStringGetMaximumUTF8CStringSize(jsref);
2145 s = g_malloc(l);
2146 if (s)
2147 JSStringGetUTF8CString(jsref, s, l);
2148 JSStringRelease(jsref);
2150 return (s);
2153 void
2154 disable_hints(struct tab *t)
2156 bzero(t->hint_buf, sizeof t->hint_buf);
2157 bzero(t->hint_num, sizeof t->hint_num);
2158 run_script(t, "vimprobable_clear()");
2159 t->hints_on = 0;
2160 t->hint_mode = XT_HINT_NONE;
2163 void
2164 enable_hints(struct tab *t)
2166 bzero(t->hint_buf, sizeof t->hint_buf);
2167 run_script(t, "vimprobable_show_hints()");
2168 t->hints_on = 1;
2169 t->hint_mode = XT_HINT_NONE;
2172 #define XT_JS_OPEN ("open;")
2173 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2174 #define XT_JS_FIRE ("fire;")
2175 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2176 #define XT_JS_FOUND ("found;")
2177 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2180 run_script(struct tab *t, char *s)
2182 JSGlobalContextRef ctx;
2183 WebKitWebFrame *frame;
2184 JSStringRef str;
2185 JSValueRef val, exception;
2186 char *es, buf[128];
2188 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2189 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2191 frame = webkit_web_view_get_main_frame(t->wv);
2192 ctx = webkit_web_frame_get_global_context(frame);
2194 str = JSStringCreateWithUTF8CString(s);
2195 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2196 NULL, 0, &exception);
2197 JSStringRelease(str);
2199 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2200 if (val == NULL) {
2201 es = js_ref_to_string(ctx, exception);
2202 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2203 g_free(es);
2204 return (1);
2205 } else {
2206 es = js_ref_to_string(ctx, val);
2207 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2209 /* handle return value right here */
2210 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2211 disable_hints(t);
2212 marks_clear(t);
2213 load_uri(t, &es[XT_JS_OPEN_LEN]);
2216 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2217 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2218 &es[XT_JS_FIRE_LEN]);
2219 run_script(t, buf);
2220 disable_hints(t);
2223 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2224 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2225 disable_hints(t);
2228 g_free(es);
2231 return (0);
2235 hint(struct tab *t, struct karg *args)
2238 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2240 if (t->hints_on == 0)
2241 enable_hints(t);
2242 else
2243 disable_hints(t);
2245 return (0);
2248 void
2249 apply_style(struct tab *t)
2251 g_object_set(G_OBJECT(t->settings),
2252 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2256 userstyle(struct tab *t, struct karg *args)
2258 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2260 if (t->styled) {
2261 t->styled = 0;
2262 g_object_set(G_OBJECT(t->settings),
2263 "user-stylesheet-uri", NULL, (char *)NULL);
2264 } else {
2265 t->styled = 1;
2266 apply_style(t);
2268 return (0);
2272 * Doesn't work fully, due to the following bug:
2273 * https://bugs.webkit.org/show_bug.cgi?id=51747
2276 restore_global_history(void)
2278 char file[PATH_MAX];
2279 FILE *f;
2280 struct history *h;
2281 gchar *uri;
2282 gchar *title;
2283 const char delim[3] = {'\\', '\\', '\0'};
2285 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2287 if ((f = fopen(file, "r")) == NULL) {
2288 warnx("%s: fopen", __func__);
2289 return (1);
2292 for (;;) {
2293 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2294 if (feof(f) || ferror(f))
2295 break;
2297 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2298 if (feof(f) || ferror(f)) {
2299 free(uri);
2300 warnx("%s: broken history file\n", __func__);
2301 return (1);
2304 if (uri && strlen(uri) && title && strlen(title)) {
2305 webkit_web_history_item_new_with_data(uri, title);
2306 h = g_malloc(sizeof(struct history));
2307 h->uri = g_strdup(uri);
2308 h->title = g_strdup(title);
2309 RB_INSERT(history_list, &hl, h);
2310 completion_add_uri(h->uri);
2311 } else {
2312 warnx("%s: failed to restore history\n", __func__);
2313 free(uri);
2314 free(title);
2315 return (1);
2318 free(uri);
2319 free(title);
2320 uri = NULL;
2321 title = NULL;
2324 return (0);
2328 save_global_history_to_disk(struct tab *t)
2330 char file[PATH_MAX];
2331 FILE *f;
2332 struct history *h;
2334 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2336 if ((f = fopen(file, "w")) == NULL) {
2337 show_oops(t, "%s: global history file: %s",
2338 __func__, strerror(errno));
2339 return (1);
2342 RB_FOREACH_REVERSE(h, history_list, &hl) {
2343 if (h->uri && h->title)
2344 fprintf(f, "%s\n%s\n", h->uri, h->title);
2347 fclose(f);
2349 return (0);
2353 quit(struct tab *t, struct karg *args)
2355 if (save_global_history)
2356 save_global_history_to_disk(t);
2358 gtk_main_quit();
2360 return (1);
2364 open_tabs(struct tab *t, struct karg *a)
2366 char file[PATH_MAX];
2367 FILE *f = NULL;
2368 char *uri = NULL;
2369 int rv = 1;
2370 struct tab *ti, *tt;
2372 if (a == NULL)
2373 goto done;
2375 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2376 if ((f = fopen(file, "r")) == NULL)
2377 goto done;
2379 ti = TAILQ_LAST(&tabs, tab_list);
2381 for (;;) {
2382 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2383 if (feof(f) || ferror(f))
2384 break;
2386 /* retrieve session name */
2387 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2388 strlcpy(named_session,
2389 &uri[strlen(XT_SAVE_SESSION_ID)],
2390 sizeof named_session);
2391 continue;
2394 if (uri && strlen(uri))
2395 create_new_tab(uri, NULL, 1, -1);
2397 free(uri);
2398 uri = NULL;
2401 /* close open tabs */
2402 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2403 for (;;) {
2404 tt = TAILQ_FIRST(&tabs);
2405 if (tt != ti) {
2406 delete_tab(tt);
2407 continue;
2409 delete_tab(tt);
2410 break;
2412 recalc_tabs();
2415 rv = 0;
2416 done:
2417 if (f)
2418 fclose(f);
2420 return (rv);
2424 restore_saved_tabs(void)
2426 char file[PATH_MAX];
2427 int unlink_file = 0;
2428 struct stat sb;
2429 struct karg a;
2430 int rv = 0;
2432 snprintf(file, sizeof file, "%s/%s",
2433 sessions_dir, XT_RESTART_TABS_FILE);
2434 if (stat(file, &sb) == -1)
2435 a.s = XT_SAVED_TABS_FILE;
2436 else {
2437 unlink_file = 1;
2438 a.s = XT_RESTART_TABS_FILE;
2441 a.i = XT_SES_DONOTHING;
2442 rv = open_tabs(NULL, &a);
2444 if (unlink_file)
2445 unlink(file);
2447 return (rv);
2451 save_tabs(struct tab *t, struct karg *a)
2453 char file[PATH_MAX];
2454 FILE *f;
2455 int num_tabs = 0, i;
2456 struct tab **stabs = NULL;
2458 if (a == NULL)
2459 return (1);
2460 if (a->s == NULL)
2461 snprintf(file, sizeof file, "%s/%s",
2462 sessions_dir, named_session);
2463 else
2464 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2466 if ((f = fopen(file, "w")) == NULL) {
2467 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2468 return (1);
2471 /* save session name */
2472 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2474 /* Save tabs, in the order they are arranged in the notebook. */
2475 num_tabs = sort_tabs_by_page_num(&stabs);
2477 for (i = 0; i < num_tabs; i++)
2478 if (stabs[i]) {
2479 if (get_uri(stabs[i]) != NULL)
2480 fprintf(f, "%s\n", get_uri(stabs[i]));
2481 else if (gtk_entry_get_text(GTK_ENTRY(
2482 stabs[i]->uri_entry)))
2483 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2484 stabs[i]->uri_entry)));
2487 g_free(stabs);
2489 /* try and make sure this gets to disk NOW. XXX Backup first? */
2490 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2491 show_oops(t, "May not have managed to save session: %s",
2492 strerror(errno));
2495 fclose(f);
2497 return (0);
2501 save_tabs_and_quit(struct tab *t, struct karg *args)
2503 struct karg a;
2505 a.s = NULL;
2506 save_tabs(t, &a);
2507 quit(t, NULL);
2509 return (1);
2513 run_page_script(struct tab *t, struct karg *args)
2515 const gchar *uri;
2516 char *tmp, script[PATH_MAX];
2518 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2519 if (tmp[0] == '\0') {
2520 show_oops(t, "no script specified");
2521 return (1);
2524 if ((uri = get_uri(t)) == NULL) {
2525 show_oops(t, "tab is empty, not running script");
2526 return (1);
2529 if (tmp[0] == '~')
2530 snprintf(script, sizeof script, "%s/%s",
2531 pwd->pw_dir, &tmp[1]);
2532 else
2533 strlcpy(script, tmp, sizeof script);
2535 switch (fork()) {
2536 case -1:
2537 show_oops(t, "can't fork to run script");
2538 return (1);
2539 /* NOTREACHED */
2540 case 0:
2541 break;
2542 default:
2543 return (0);
2546 /* child */
2547 execlp(script, script, uri, (void *)NULL);
2549 _exit(0);
2551 /* NOTREACHED */
2553 return (0);
2557 yank_uri(struct tab *t, struct karg *args)
2559 const gchar *uri;
2560 GtkClipboard *clipboard;
2562 if ((uri = get_uri(t)) == NULL)
2563 return (1);
2565 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2566 gtk_clipboard_set_text(clipboard, uri, -1);
2568 return (0);
2572 paste_uri(struct tab *t, struct karg *args)
2574 GtkClipboard *clipboard;
2575 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2576 gint len;
2577 gchar *p = NULL, *uri;
2579 /* try primary clipboard first */
2580 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2581 p = gtk_clipboard_wait_for_text(clipboard);
2583 /* if it failed get whatever text is in cut_buffer0 */
2584 if (p == NULL && xterm_workaround)
2585 if (gdk_property_get(gdk_get_default_root_window(),
2586 atom,
2587 gdk_atom_intern("STRING", FALSE),
2589 1024 * 1024 /* picked out of my butt */,
2590 FALSE,
2591 NULL,
2592 NULL,
2593 &len,
2594 (guchar **)&p)) {
2595 /* yes sir, we need to NUL the string */
2596 p[len] = '\0';
2599 if (p) {
2600 uri = p;
2601 while (*uri && isspace(*uri))
2602 uri++;
2603 if (strlen(uri) == 0) {
2604 show_oops(t, "empty paste buffer");
2605 goto done;
2607 if (guess_search == 0 && valid_url_type(uri)) {
2608 /* we can be clever and paste this in search box */
2609 show_oops(t, "not a valid URL");
2610 goto done;
2613 if (args->i == XT_PASTE_CURRENT_TAB)
2614 load_uri(t, uri);
2615 else if (args->i == XT_PASTE_NEW_TAB)
2616 create_new_tab(uri, NULL, 1, -1);
2619 done:
2620 if (p)
2621 g_free(p);
2623 return (0);
2626 gchar *
2627 find_domain(const gchar *s, int toplevel)
2629 SoupURI *uri;
2630 gchar *ret, *p;
2632 if (s == NULL)
2633 return (NULL);
2635 uri = soup_uri_new(s);
2637 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2638 return (NULL);
2641 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2642 if ((p = strrchr(uri->host, '.')) != NULL) {
2643 while(--p >= uri->host && *p != '.');
2644 p++;
2645 } else
2646 p = uri->host;
2647 } else
2648 p = uri->host;
2650 if (uri->port == 80)
2651 ret = g_strdup_printf(".%s", p);
2652 else
2653 ret = g_strdup_printf(".%s:%d", p, uri->port);
2655 soup_uri_free(uri);
2657 return ret;
2661 toggle_cwl(struct tab *t, struct karg *args)
2663 struct domain *d;
2664 const gchar *uri;
2665 char *dom = NULL;
2666 int es;
2668 if (args == NULL)
2669 return (1);
2671 uri = get_uri(t);
2672 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2674 if (uri == NULL || dom == NULL ||
2675 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2676 show_oops(t, "Can't toggle domain in cookie white list");
2677 goto done;
2679 d = wl_find(dom, &c_wl);
2681 if (d == NULL)
2682 es = 0;
2683 else
2684 es = 1;
2686 if (args->i & XT_WL_TOGGLE)
2687 es = !es;
2688 else if ((args->i & XT_WL_ENABLE) && es != 1)
2689 es = 1;
2690 else if ((args->i & XT_WL_DISABLE) && es != 0)
2691 es = 0;
2693 if (es)
2694 /* enable cookies for domain */
2695 wl_add(dom, &c_wl, 0);
2696 else
2697 /* disable cookies for domain */
2698 RB_REMOVE(domain_list, &c_wl, d);
2700 if (args->i & XT_WL_RELOAD)
2701 webkit_web_view_reload(t->wv);
2703 done:
2704 g_free(dom);
2705 return (0);
2709 toggle_js(struct tab *t, struct karg *args)
2711 int es;
2712 const gchar *uri;
2713 struct domain *d;
2714 char *dom = NULL;
2716 if (args == NULL)
2717 return (1);
2719 g_object_get(G_OBJECT(t->settings),
2720 "enable-scripts", &es, (char *)NULL);
2721 if (args->i & XT_WL_TOGGLE)
2722 es = !es;
2723 else if ((args->i & XT_WL_ENABLE) && es != 1)
2724 es = 1;
2725 else if ((args->i & XT_WL_DISABLE) && es != 0)
2726 es = 0;
2727 else
2728 return (1);
2730 uri = get_uri(t);
2731 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2733 if (uri == NULL || dom == NULL ||
2734 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2735 show_oops(t, "Can't toggle domain in JavaScript white list");
2736 goto done;
2739 if (es) {
2740 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2741 wl_add(dom, &js_wl, 0 /* session */);
2742 } else {
2743 d = wl_find(dom, &js_wl);
2744 if (d)
2745 RB_REMOVE(domain_list, &js_wl, d);
2746 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2748 g_object_set(G_OBJECT(t->settings),
2749 "enable-scripts", es, (char *)NULL);
2750 g_object_set(G_OBJECT(t->settings),
2751 "javascript-can-open-windows-automatically", es, (char *)NULL);
2752 webkit_web_view_set_settings(t->wv, t->settings);
2754 if (args->i & XT_WL_RELOAD)
2755 webkit_web_view_reload(t->wv);
2756 done:
2757 if (dom)
2758 g_free(dom);
2759 return (0);
2762 void
2763 js_toggle_cb(GtkWidget *w, struct tab *t)
2765 struct karg a;
2767 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2768 toggle_cwl(t, &a);
2770 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2771 toggle_js(t, &a);
2775 toggle_src(struct tab *t, struct karg *args)
2777 gboolean mode;
2779 if (t == NULL)
2780 return (0);
2782 mode = webkit_web_view_get_view_source_mode(t->wv);
2783 webkit_web_view_set_view_source_mode(t->wv, !mode);
2784 webkit_web_view_reload(t->wv);
2786 return (0);
2789 void
2790 focus_webview(struct tab *t)
2792 if (t == NULL)
2793 return;
2795 /* only grab focus if we are visible */
2796 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2797 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2801 focus(struct tab *t, struct karg *args)
2803 if (t == NULL || args == NULL)
2804 return (1);
2806 if (show_url == 0)
2807 return (0);
2809 if (args->i == XT_FOCUS_URI)
2810 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2811 else if (args->i == XT_FOCUS_SEARCH)
2812 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2814 return (0);
2818 stats(struct tab *t, struct karg *args)
2820 char *page, *body, *s, line[64 * 1024];
2821 uint64_t line_count = 0;
2822 FILE *r_cookie_f;
2824 if (t == NULL)
2825 show_oops(NULL, "stats invalid parameters");
2827 line[0] = '\0';
2828 if (save_rejected_cookies) {
2829 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2830 for (;;) {
2831 s = fgets(line, sizeof line, r_cookie_f);
2832 if (s == NULL || feof(r_cookie_f) ||
2833 ferror(r_cookie_f))
2834 break;
2835 line_count++;
2837 fclose(r_cookie_f);
2838 snprintf(line, sizeof line,
2839 "<br/>Cookies blocked(*) total: %llu", line_count);
2840 } else
2841 show_oops(t, "Can't open blocked cookies file: %s",
2842 strerror(errno));
2845 body = g_strdup_printf(
2846 "Cookies blocked(*) this session: %llu"
2847 "%s"
2848 "<p><small><b>*</b> results vary based on settings</small></p>",
2849 blocked_cookies,
2850 line);
2852 page = get_html_page("Statistics", body, "", 0);
2853 g_free(body);
2855 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2856 g_free(page);
2858 return (0);
2862 marco(struct tab *t, struct karg *args)
2864 char *page, line[64 * 1024];
2865 int len;
2867 if (t == NULL)
2868 show_oops(NULL, "marco invalid parameters");
2870 line[0] = '\0';
2871 snprintf(line, sizeof line, "%s", marco_message(&len));
2873 page = get_html_page("Marco Sez...", line, "", 0);
2875 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2876 g_free(page);
2878 return (0);
2882 blank(struct tab *t, struct karg *args)
2884 if (t == NULL)
2885 show_oops(NULL, "blank invalid parameters");
2887 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2889 return (0);
2893 about(struct tab *t, struct karg *args)
2895 char *page, *body;
2897 if (t == NULL)
2898 show_oops(NULL, "about invalid parameters");
2900 body = g_strdup_printf("<b>Version: %s</b><p>"
2901 "Authors:"
2902 "<ul>"
2903 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2904 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2905 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2906 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2907 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2908 "</ul>"
2909 "Copyrights and licenses can be found on the XXXTerm "
2910 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2911 version
2914 page = get_html_page("About", body, "", 0);
2915 g_free(body);
2917 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2918 g_free(page);
2920 return (0);
2924 help(struct tab *t, struct karg *args)
2926 char *page, *head, *body;
2928 if (t == NULL)
2929 show_oops(NULL, "help invalid parameters");
2931 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2932 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2933 "</head>\n";
2934 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2935 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2936 "cgi-bin/man-cgi?xxxterm</a>";
2938 page = get_html_page(XT_NAME, body, head, FALSE);
2940 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2941 g_free(page);
2943 return (0);
2947 startpage(struct tab *t, struct karg *args)
2949 char *page, *body, *b;
2950 struct sp *s;
2952 if (t == NULL)
2953 show_oops(NULL, "startpage invalid parameters");
2955 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
2957 TAILQ_FOREACH(s, &spl, entry) {
2958 b = body;
2959 body = g_strdup_printf("%s%s<br>", body, s->line);
2960 g_free(b);
2963 page = get_html_page("Startup Exception", body, "", 0);
2964 g_free(body);
2966 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
2967 g_free(page);
2969 return (0);
2972 void
2973 startpage_add(const char *fmt, ...)
2975 va_list ap;
2976 char *msg;
2977 struct sp *s;
2979 if (fmt == NULL)
2980 return;
2982 va_start(ap, fmt);
2983 if (vasprintf(&msg, fmt, ap) == -1)
2984 errx(1, "startpage_add failed");
2985 va_end(ap);
2987 s = g_malloc0(sizeof *s);
2988 s->line = msg;
2990 TAILQ_INSERT_TAIL(&spl, s, entry);
2994 * update all favorite tabs apart from one. Pass NULL if
2995 * you want to update all.
2997 void
2998 update_favorite_tabs(struct tab *apart_from)
3000 struct tab *t;
3001 if (!updating_fl_tabs) {
3002 updating_fl_tabs = 1; /* stop infinite recursion */
3003 TAILQ_FOREACH(t, &tabs, entry)
3004 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3005 && (t != apart_from))
3006 xtp_page_fl(t, NULL);
3007 updating_fl_tabs = 0;
3011 /* show a list of favorites (bookmarks) */
3013 xtp_page_fl(struct tab *t, struct karg *args)
3015 char file[PATH_MAX];
3016 FILE *f;
3017 char *uri = NULL, *title = NULL;
3018 size_t len, lineno = 0;
3019 int i, failed = 0;
3020 char *body, *tmp, *page = NULL;
3021 const char delim[3] = {'\\', '\\', '\0'};
3023 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3025 if (t == NULL)
3026 warn("%s: bad param", __func__);
3028 /* new session key */
3029 if (!updating_fl_tabs)
3030 generate_xtp_session_key(&fl_session_key);
3032 /* open favorites */
3033 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3034 if ((f = fopen(file, "r")) == NULL) {
3035 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3036 return (1);
3039 /* body */
3040 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3041 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3042 "<th style='width: 40px'>Rm</th></tr>\n");
3044 for (i = 1;;) {
3045 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3046 if (feof(f) || ferror(f))
3047 break;
3048 if (strlen(title) == 0 || title[0] == '#') {
3049 free(title);
3050 title = NULL;
3051 continue;
3054 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3055 if (feof(f) || ferror(f)) {
3056 show_oops(t, "favorites file corrupt");
3057 failed = 1;
3058 break;
3061 tmp = body;
3062 body = g_strdup_printf("%s<tr>"
3063 "<td>%d</td>"
3064 "<td><a href='%s'>%s</a></td>"
3065 "<td style='text-align: center'>"
3066 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3067 "</tr>\n",
3068 body, i, uri, title,
3069 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3071 g_free(tmp);
3073 free(uri);
3074 uri = NULL;
3075 free(title);
3076 title = NULL;
3077 i++;
3079 fclose(f);
3081 /* if none, say so */
3082 if (i == 1) {
3083 tmp = body;
3084 body = g_strdup_printf("%s<tr>"
3085 "<td colspan='3' style='text-align: center'>"
3086 "No favorites - To add one use the 'favadd' command."
3087 "</td></tr>", body);
3088 g_free(tmp);
3091 tmp = body;
3092 body = g_strdup_printf("%s</table>", body);
3093 g_free(tmp);
3095 if (uri)
3096 free(uri);
3097 if (title)
3098 free(title);
3100 /* render */
3101 if (!failed) {
3102 page = get_html_page("Favorites", body, "", 1);
3103 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3104 g_free(page);
3107 update_favorite_tabs(t);
3109 if (body)
3110 g_free(body);
3112 return (failed);
3115 void
3116 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3117 size_t cert_count, char *title)
3119 gnutls_datum_t cinfo;
3120 char *tmp, *body;
3121 int i;
3123 body = g_strdup("");
3125 for (i = 0; i < cert_count; i++) {
3126 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3127 &cinfo))
3128 return;
3130 tmp = body;
3131 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3132 body, i, cinfo.data);
3133 gnutls_free(cinfo.data);
3134 g_free(tmp);
3137 tmp = get_html_page(title, body, "", 0);
3138 g_free(body);
3140 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3141 g_free(tmp);
3145 ca_cmd(struct tab *t, struct karg *args)
3147 FILE *f = NULL;
3148 int rv = 1, certs = 0, certs_read;
3149 struct stat sb;
3150 gnutls_datum_t dt;
3151 gnutls_x509_crt_t *c = NULL;
3152 char *certs_buf = NULL, *s;
3154 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3155 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3156 return (1);
3159 if (fstat(fileno(f), &sb) == -1) {
3160 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3161 goto done;
3164 certs_buf = g_malloc(sb.st_size + 1);
3165 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3166 show_oops(t, "Can't read CA file: %s", strerror(errno));
3167 goto done;
3169 certs_buf[sb.st_size] = '\0';
3171 s = certs_buf;
3172 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3173 certs++;
3174 s += strlen("BEGIN CERTIFICATE");
3177 bzero(&dt, sizeof dt);
3178 dt.data = (unsigned char *)certs_buf;
3179 dt.size = sb.st_size;
3180 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3181 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3182 GNUTLS_X509_FMT_PEM, 0);
3183 if (certs_read <= 0) {
3184 show_oops(t, "No cert(s) available");
3185 goto done;
3187 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3188 done:
3189 if (c)
3190 g_free(c);
3191 if (certs_buf)
3192 g_free(certs_buf);
3193 if (f)
3194 fclose(f);
3196 return (rv);
3200 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3201 size_t domain_sz)
3203 SoupURI *su = NULL;
3204 struct addrinfo hints, *res = NULL, *ai;
3205 int rv = -1, s = -1, on, error;
3206 char port[8];
3208 if (uri && !g_str_has_prefix(uri, "https://")) {
3209 show_oops(t, "invalid URI");
3210 goto done;
3213 su = soup_uri_new(uri);
3214 if (su == NULL) {
3215 show_oops(t, "invalid soup URI");
3216 goto done;
3218 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3219 show_oops(t, "invalid HTTPS URI");
3220 goto done;
3223 snprintf(port, sizeof port, "%d", su->port);
3224 bzero(&hints, sizeof(struct addrinfo));
3225 hints.ai_flags = AI_CANONNAME;
3226 hints.ai_family = AF_UNSPEC;
3227 hints.ai_socktype = SOCK_STREAM;
3229 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3230 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3231 goto done;
3234 for (ai = res; ai; ai = ai->ai_next) {
3235 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3236 continue;
3238 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3239 if (s == -1) {
3240 show_oops(t, "socket failed: %s", strerror(errno));
3241 goto done;
3243 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3244 sizeof(on)) == -1) {
3245 show_oops(t, "setsockopt failed: %s", strerror(errno));
3246 goto done;
3248 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3249 show_oops(t, "connect failed: %s", strerror(errno));
3250 goto done;
3253 break;
3256 if (domain)
3257 strlcpy(domain, su->host, domain_sz);
3258 rv = s;
3259 done:
3260 if (su)
3261 soup_uri_free(su);
3262 if (res)
3263 freeaddrinfo(res);
3264 if (rv == -1 && s != -1)
3265 close(s);
3267 return (rv);
3271 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3273 if (gsession)
3274 gnutls_deinit(gsession);
3275 if (xcred)
3276 gnutls_certificate_free_credentials(xcred);
3278 return (0);
3282 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3283 gnutls_certificate_credentials_t *xc)
3285 gnutls_certificate_credentials_t xcred;
3286 gnutls_session_t gsession;
3287 int rv = 1;
3289 if (gs == NULL || xc == NULL)
3290 goto done;
3292 *gs = NULL;
3293 *xc = NULL;
3295 gnutls_certificate_allocate_credentials(&xcred);
3296 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3297 GNUTLS_X509_FMT_PEM);
3299 gnutls_init(&gsession, GNUTLS_CLIENT);
3300 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3301 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3302 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3303 if ((rv = gnutls_handshake(gsession)) < 0) {
3304 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3306 gnutls_error_is_fatal(rv),
3307 gnutls_strerror_name(rv));
3308 stop_tls(gsession, xcred);
3309 goto done;
3312 gnutls_credentials_type_t cred;
3313 cred = gnutls_auth_get_type(gsession);
3314 if (cred != GNUTLS_CRD_CERTIFICATE) {
3315 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3316 stop_tls(gsession, xcred);
3317 goto done;
3320 *gs = gsession;
3321 *xc = xcred;
3322 rv = 0;
3323 done:
3324 return (rv);
3328 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3329 size_t *cert_count)
3331 unsigned int len;
3332 const gnutls_datum_t *cl;
3333 gnutls_x509_crt_t *all_certs;
3334 int i, rv = 1;
3336 if (certs == NULL || cert_count == NULL)
3337 goto done;
3338 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3339 goto done;
3340 cl = gnutls_certificate_get_peers(gsession, &len);
3341 if (len == 0)
3342 goto done;
3344 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3345 for (i = 0; i < len; i++) {
3346 gnutls_x509_crt_init(&all_certs[i]);
3347 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3348 GNUTLS_X509_FMT_PEM < 0)) {
3349 g_free(all_certs);
3350 goto done;
3354 *certs = all_certs;
3355 *cert_count = len;
3356 rv = 0;
3357 done:
3358 return (rv);
3361 void
3362 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3364 int i;
3366 for (i = 0; i < cert_count; i++)
3367 gnutls_x509_crt_deinit(certs[i]);
3368 g_free(certs);
3371 void
3372 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3374 GdkColor c_text, c_base;
3376 gdk_color_parse(text, &c_text);
3377 gdk_color_parse(base, &c_base);
3379 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3380 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3381 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3382 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3384 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3385 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3386 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3387 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3390 void
3391 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3392 size_t cert_count, char *domain)
3394 size_t cert_buf_sz;
3395 char cert_buf[64 * 1024], file[PATH_MAX];
3396 int i;
3397 FILE *f;
3398 GdkColor color;
3400 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3401 return;
3403 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3404 if ((f = fopen(file, "w")) == NULL) {
3405 show_oops(t, "Can't create cert file %s %s",
3406 file, strerror(errno));
3407 return;
3410 for (i = 0; i < cert_count; i++) {
3411 cert_buf_sz = sizeof cert_buf;
3412 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3413 cert_buf, &cert_buf_sz)) {
3414 show_oops(t, "gnutls_x509_crt_export failed");
3415 goto done;
3417 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3418 show_oops(t, "Can't write certs: %s", strerror(errno));
3419 goto done;
3423 /* not the best spot but oh well */
3424 gdk_color_parse("lightblue", &color);
3425 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3426 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3427 done:
3428 fclose(f);
3431 enum cert_trust {
3432 CERT_LOCAL,
3433 CERT_TRUSTED,
3434 CERT_UNTRUSTED,
3435 CERT_BAD
3438 enum cert_trust
3439 load_compare_cert(struct tab *t, struct karg *args)
3441 const gchar *uri;
3442 char domain[8182], file[PATH_MAX];
3443 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3444 int s = -1, i, error;
3445 FILE *f = NULL;
3446 size_t cert_buf_sz, cert_count;
3447 enum cert_trust rv = CERT_UNTRUSTED;
3448 char serr[80];
3449 gnutls_session_t gsession;
3450 gnutls_x509_crt_t *certs;
3451 gnutls_certificate_credentials_t xcred;
3453 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3455 if (t == NULL)
3456 return (rv);
3458 if ((uri = get_uri(t)) == NULL)
3459 return (rv);
3460 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3462 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3463 return (rv);
3464 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3466 /* go ssl/tls */
3467 if (start_tls(t, s, &gsession, &xcred))
3468 goto done;
3469 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3471 /* verify certs in case cert file doesn't exist */
3472 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3473 GNUTLS_E_SUCCESS) {
3474 show_oops(t, "Invalid certificates");
3475 goto done;
3478 /* get certs */
3479 if (get_connection_certs(gsession, &certs, &cert_count)) {
3480 show_oops(t, "Can't get connection certificates");
3481 goto done;
3484 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3485 if ((f = fopen(file, "r")) == NULL) {
3486 if (!error)
3487 rv = CERT_TRUSTED;
3488 goto freeit;
3491 for (i = 0; i < cert_count; i++) {
3492 cert_buf_sz = sizeof cert_buf;
3493 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3494 cert_buf, &cert_buf_sz)) {
3495 goto freeit;
3497 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3498 rv = CERT_BAD; /* critical */
3499 goto freeit;
3501 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3502 rv = CERT_BAD; /* critical */
3503 goto freeit;
3505 rv = CERT_LOCAL;
3508 freeit:
3509 if (f)
3510 fclose(f);
3511 free_connection_certs(certs, cert_count);
3512 done:
3513 /* we close the socket first for speed */
3514 if (s != -1)
3515 close(s);
3517 /* only complain if we didn't save it locally */
3518 if (error && rv != CERT_LOCAL) {
3519 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3520 if (error & GNUTLS_CERT_INVALID)
3521 strlcat(serr, "invalid, ", sizeof serr);
3522 if (error & GNUTLS_CERT_REVOKED)
3523 strlcat(serr, "revoked, ", sizeof serr);
3524 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3525 strlcat(serr, "signer not found, ", sizeof serr);
3526 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3527 strlcat(serr, "not signed by CA, ", sizeof serr);
3528 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3529 strlcat(serr, "insecure algorithm, ", sizeof serr);
3530 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3531 strlcat(serr, "not activated, ", sizeof serr);
3532 if (error & GNUTLS_CERT_EXPIRED)
3533 strlcat(serr, "expired, ", sizeof serr);
3534 for (i = strlen(serr) - 1; i > 0; i--)
3535 if (serr[i] == ',') {
3536 serr[i] = '\0';
3537 break;
3539 show_oops(t, serr);
3542 stop_tls(gsession, xcred);
3544 return (rv);
3548 cert_cmd(struct tab *t, struct karg *args)
3550 const gchar *uri;
3551 char domain[8182];
3552 int s = -1;
3553 size_t cert_count;
3554 gnutls_session_t gsession;
3555 gnutls_x509_crt_t *certs;
3556 gnutls_certificate_credentials_t xcred;
3558 if (t == NULL)
3559 return (1);
3561 if (ssl_ca_file == NULL) {
3562 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3563 return (1);
3566 if ((uri = get_uri(t)) == NULL) {
3567 show_oops(t, "Invalid URI");
3568 return (1);
3571 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3572 show_oops(t, "Invalid certificate URI: %s", uri);
3573 return (1);
3576 /* go ssl/tls */
3577 if (start_tls(t, s, &gsession, &xcred))
3578 goto done;
3580 /* get certs */
3581 if (get_connection_certs(gsession, &certs, &cert_count)) {
3582 show_oops(t, "get_connection_certs failed");
3583 goto done;
3586 if (args->i & XT_SHOW)
3587 show_certs(t, certs, cert_count, "Certificate Chain");
3588 else if (args->i & XT_SAVE)
3589 save_certs(t, certs, cert_count, domain);
3591 free_connection_certs(certs, cert_count);
3592 done:
3593 /* we close the socket first for speed */
3594 if (s != -1)
3595 close(s);
3596 stop_tls(gsession, xcred);
3598 return (0);
3602 remove_cookie(int index)
3604 int i, rv = 1;
3605 GSList *cf;
3606 SoupCookie *c;
3608 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3610 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3612 for (i = 1; cf; cf = cf->next, i++) {
3613 if (i != index)
3614 continue;
3615 c = cf->data;
3616 print_cookie("remove cookie", c);
3617 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3618 rv = 0;
3619 break;
3622 soup_cookies_free(cf);
3624 return (rv);
3628 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3630 struct domain *d;
3631 char *tmp, *body;
3633 body = g_strdup("");
3635 /* p list */
3636 if (args->i & XT_WL_PERSISTENT) {
3637 tmp = body;
3638 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3639 g_free(tmp);
3640 RB_FOREACH(d, domain_list, wl) {
3641 if (d->handy == 0)
3642 continue;
3643 tmp = body;
3644 body = g_strdup_printf("%s%s<br/>", body, d->d);
3645 g_free(tmp);
3649 /* s list */
3650 if (args->i & XT_WL_SESSION) {
3651 tmp = body;
3652 body = g_strdup_printf("%s<h2>Session</h2>", body);
3653 g_free(tmp);
3654 RB_FOREACH(d, domain_list, wl) {
3655 if (d->handy == 1)
3656 continue;
3657 tmp = body;
3658 body = g_strdup_printf("%s%s<br/>", body, d->d);
3659 g_free(tmp);
3663 tmp = get_html_page(title, body, "", 0);
3664 g_free(body);
3665 if (wl == &js_wl)
3666 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3667 else
3668 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3669 g_free(tmp);
3670 return (0);
3674 wl_save(struct tab *t, struct karg *args, int js)
3676 char file[PATH_MAX];
3677 FILE *f;
3678 char *line = NULL, *lt = NULL, *dom = NULL;
3679 size_t linelen;
3680 const gchar *uri;
3681 struct karg a;
3682 struct domain *d;
3683 GSList *cf;
3684 SoupCookie *ci, *c;
3686 if (t == NULL || args == NULL)
3687 return (1);
3689 if (runtime_settings[0] == '\0')
3690 return (1);
3692 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3693 if ((f = fopen(file, "r+")) == NULL)
3694 return (1);
3696 uri = get_uri(t);
3697 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3698 if (uri == NULL || dom == NULL ||
3699 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3700 show_oops(t, "Can't add domain to %s white list",
3701 js ? "JavaScript" : "cookie");
3702 goto done;
3705 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3707 while (!feof(f)) {
3708 line = fparseln(f, &linelen, NULL, NULL, 0);
3709 if (line == NULL)
3710 continue;
3711 if (!strcmp(line, lt))
3712 goto done;
3713 free(line);
3714 line = NULL;
3717 fprintf(f, "%s\n", lt);
3719 a.i = XT_WL_ENABLE;
3720 a.i |= args->i;
3721 if (js) {
3722 d = wl_find(dom, &js_wl);
3723 if (!d) {
3724 settings_add("js_wl", dom);
3725 d = wl_find(dom, &js_wl);
3727 toggle_js(t, &a);
3728 } else {
3729 d = wl_find(dom, &c_wl);
3730 if (!d) {
3731 settings_add("cookie_wl", dom);
3732 d = wl_find(dom, &c_wl);
3734 toggle_cwl(t, &a);
3736 /* find and add to persistent jar */
3737 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3738 for (;cf; cf = cf->next) {
3739 ci = cf->data;
3740 if (!strcmp(dom, ci->domain) ||
3741 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3742 c = soup_cookie_copy(ci);
3743 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3746 soup_cookies_free(cf);
3748 if (d)
3749 d->handy = 1;
3751 done:
3752 if (line)
3753 free(line);
3754 if (dom)
3755 g_free(dom);
3756 if (lt)
3757 g_free(lt);
3758 fclose(f);
3760 return (0);
3764 js_show_wl(struct tab *t, struct karg *args)
3766 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3767 wl_show(t, args, "JavaScript White List", &js_wl);
3769 return (0);
3773 cookie_show_wl(struct tab *t, struct karg *args)
3775 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3776 wl_show(t, args, "Cookie White List", &c_wl);
3778 return (0);
3782 cookie_cmd(struct tab *t, struct karg *args)
3784 if (args->i & XT_SHOW)
3785 wl_show(t, args, "Cookie White List", &c_wl);
3786 else if (args->i & XT_WL_TOGGLE) {
3787 args->i |= XT_WL_RELOAD;
3788 toggle_cwl(t, args);
3789 } else if (args->i & XT_SAVE) {
3790 args->i |= XT_WL_RELOAD;
3791 wl_save(t, args, 0);
3792 } else if (args->i & XT_DELETE)
3793 show_oops(t, "'cookie delete' currently unimplemented");
3795 return (0);
3799 js_cmd(struct tab *t, struct karg *args)
3801 if (args->i & XT_SHOW)
3802 wl_show(t, args, "JavaScript White List", &js_wl);
3803 else if (args->i & XT_SAVE) {
3804 args->i |= XT_WL_RELOAD;
3805 wl_save(t, args, 1);
3806 } else if (args->i & XT_WL_TOGGLE) {
3807 args->i |= XT_WL_RELOAD;
3808 toggle_js(t, args);
3809 } else if (args->i & XT_DELETE)
3810 show_oops(t, "'js delete' currently unimplemented");
3812 return (0);
3816 toplevel_cmd(struct tab *t, struct karg *args)
3818 js_toggle_cb(t->js_toggle, t);
3820 return (0);
3824 add_favorite(struct tab *t, struct karg *args)
3826 char file[PATH_MAX];
3827 FILE *f;
3828 char *line = NULL;
3829 size_t urilen, linelen;
3830 const gchar *uri, *title;
3832 if (t == NULL)
3833 return (1);
3835 /* don't allow adding of xtp pages to favorites */
3836 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3837 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3838 return (1);
3841 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3842 if ((f = fopen(file, "r+")) == NULL) {
3843 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3844 return (1);
3847 title = get_title(t, FALSE);
3848 uri = get_uri(t);
3850 if (title == NULL || uri == NULL) {
3851 show_oops(t, "can't add page to favorites");
3852 goto done;
3855 urilen = strlen(uri);
3857 for (;;) {
3858 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3859 if (feof(f) || ferror(f))
3860 break;
3862 if (linelen == urilen && !strcmp(line, uri))
3863 goto done;
3865 free(line);
3866 line = NULL;
3869 fprintf(f, "\n%s\n%s", title, uri);
3870 done:
3871 if (line)
3872 free(line);
3873 fclose(f);
3875 update_favorite_tabs(NULL);
3877 return (0);
3881 navaction(struct tab *t, struct karg *args)
3883 WebKitWebHistoryItem *item;
3885 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3886 t->tab_id, args->i);
3888 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3890 if (t->item) {
3891 if (args->i == XT_NAV_BACK)
3892 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3893 else
3894 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3895 if (item == NULL)
3896 return (XT_CB_PASSTHROUGH);
3897 webkit_web_view_go_to_back_forward_item(t->wv, item);
3898 t->item = NULL;
3899 return (XT_CB_PASSTHROUGH);
3902 switch (args->i) {
3903 case XT_NAV_BACK:
3904 marks_clear(t);
3905 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3906 if (item)
3907 webkit_web_view_go_to_back_forward_item(t->wv, item);
3908 break;
3909 case XT_NAV_FORWARD:
3910 marks_clear(t);
3911 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3912 if (item)
3913 webkit_web_view_go_to_back_forward_item(t->wv, item);
3914 break;
3915 case XT_NAV_RELOAD:
3916 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3917 if (item)
3918 webkit_web_view_go_to_back_forward_item(t->wv, item);
3919 break;
3921 return (XT_CB_PASSTHROUGH);
3925 move(struct tab *t, struct karg *args)
3927 GtkAdjustment *adjust;
3928 double pi, si, pos, ps, upper, lower, max;
3929 double percent;
3931 switch (args->i) {
3932 case XT_MOVE_DOWN:
3933 case XT_MOVE_UP:
3934 case XT_MOVE_BOTTOM:
3935 case XT_MOVE_TOP:
3936 case XT_MOVE_PAGEDOWN:
3937 case XT_MOVE_PAGEUP:
3938 case XT_MOVE_HALFDOWN:
3939 case XT_MOVE_HALFUP:
3940 case XT_MOVE_PERCENT:
3941 adjust = t->adjust_v;
3942 break;
3943 default:
3944 adjust = t->adjust_h;
3945 break;
3948 pos = gtk_adjustment_get_value(adjust);
3949 ps = gtk_adjustment_get_page_size(adjust);
3950 upper = gtk_adjustment_get_upper(adjust);
3951 lower = gtk_adjustment_get_lower(adjust);
3952 si = gtk_adjustment_get_step_increment(adjust);
3953 pi = gtk_adjustment_get_page_increment(adjust);
3954 max = upper - ps;
3956 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3957 "max %f si %f pi %f\n",
3958 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3959 pos, ps, upper, lower, max, si, pi);
3961 switch (args->i) {
3962 case XT_MOVE_DOWN:
3963 case XT_MOVE_RIGHT:
3964 pos += si;
3965 gtk_adjustment_set_value(adjust, MIN(pos, max));
3966 break;
3967 case XT_MOVE_UP:
3968 case XT_MOVE_LEFT:
3969 pos -= si;
3970 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3971 break;
3972 case XT_MOVE_BOTTOM:
3973 case XT_MOVE_FARRIGHT:
3974 gtk_adjustment_set_value(adjust, max);
3975 break;
3976 case XT_MOVE_TOP:
3977 case XT_MOVE_FARLEFT:
3978 gtk_adjustment_set_value(adjust, lower);
3979 break;
3980 case XT_MOVE_PAGEDOWN:
3981 pos += pi;
3982 gtk_adjustment_set_value(adjust, MIN(pos, max));
3983 break;
3984 case XT_MOVE_PAGEUP:
3985 pos -= pi;
3986 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3987 break;
3988 case XT_MOVE_HALFDOWN:
3989 pos += pi / 2;
3990 gtk_adjustment_set_value(adjust, MIN(pos, max));
3991 break;
3992 case XT_MOVE_HALFUP:
3993 pos -= pi / 2;
3994 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3995 break;
3996 case XT_MOVE_PERCENT:
3997 percent = atoi(args->s) / 100.0;
3998 pos = max * percent;
3999 if (pos < 0.0 || pos > max)
4000 break;
4001 gtk_adjustment_set_value(adjust, pos);
4002 break;
4003 default:
4004 return (XT_CB_PASSTHROUGH);
4007 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4009 return (XT_CB_HANDLED);
4012 void
4013 url_set_visibility(void)
4015 struct tab *t;
4017 TAILQ_FOREACH(t, &tabs, entry)
4018 if (show_url == 0) {
4019 gtk_widget_hide(t->toolbar);
4020 focus_webview(t);
4021 } else
4022 gtk_widget_show(t->toolbar);
4025 void
4026 notebook_tab_set_visibility(void)
4028 if (show_tabs == 0) {
4029 gtk_widget_hide(tab_bar);
4030 gtk_notebook_set_show_tabs(notebook, FALSE);
4031 } else {
4032 if (tab_style == XT_TABS_NORMAL) {
4033 gtk_widget_hide(tab_bar);
4034 gtk_notebook_set_show_tabs(notebook, TRUE);
4035 } else if (tab_style == XT_TABS_COMPACT) {
4036 gtk_widget_show(tab_bar);
4037 gtk_notebook_set_show_tabs(notebook, FALSE);
4042 void
4043 statusbar_set_visibility(void)
4045 struct tab *t;
4047 TAILQ_FOREACH(t, &tabs, entry)
4048 if (show_statusbar == 0) {
4049 gtk_widget_hide(t->statusbar_box);
4050 focus_webview(t);
4051 } else
4052 gtk_widget_show(t->statusbar_box);
4055 void
4056 url_set(struct tab *t, int enable_url_entry)
4058 GdkPixbuf *pixbuf;
4059 int progress;
4061 show_url = enable_url_entry;
4063 if (enable_url_entry) {
4064 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4065 GTK_ENTRY_ICON_PRIMARY, NULL);
4066 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4067 } else {
4068 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4069 GTK_ENTRY_ICON_PRIMARY);
4070 progress =
4071 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4072 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4073 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4074 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4075 progress);
4080 fullscreen(struct tab *t, struct karg *args)
4082 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4084 if (t == NULL)
4085 return (XT_CB_PASSTHROUGH);
4087 if (show_url == 0) {
4088 url_set(t, 1);
4089 show_tabs = 1;
4090 } else {
4091 url_set(t, 0);
4092 show_tabs = 0;
4095 url_set_visibility();
4096 notebook_tab_set_visibility();
4098 return (XT_CB_HANDLED);
4102 statustoggle(struct tab *t, struct karg *args)
4104 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4106 if (show_statusbar == 1) {
4107 show_statusbar = 0;
4108 statusbar_set_visibility();
4109 } else if (show_statusbar == 0) {
4110 show_statusbar = 1;
4111 statusbar_set_visibility();
4113 return (XT_CB_HANDLED);
4117 urlaction(struct tab *t, struct karg *args)
4119 int rv = XT_CB_HANDLED;
4121 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4123 if (t == NULL)
4124 return (XT_CB_PASSTHROUGH);
4126 switch (args->i) {
4127 case XT_URL_SHOW:
4128 if (show_url == 0) {
4129 url_set(t, 1);
4130 url_set_visibility();
4132 break;
4133 case XT_URL_HIDE:
4134 if (show_url == 1) {
4135 url_set(t, 0);
4136 url_set_visibility();
4138 break;
4140 return (rv);
4144 tabaction(struct tab *t, struct karg *args)
4146 int rv = XT_CB_HANDLED;
4147 char *url = args->s;
4148 struct undo *u;
4149 struct tab *tt;
4151 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4153 if (t == NULL)
4154 return (XT_CB_PASSTHROUGH);
4156 switch (args->i) {
4157 case XT_TAB_NEW:
4158 if (strlen(url) > 0)
4159 create_new_tab(url, NULL, 1, args->precount);
4160 else
4161 create_new_tab(NULL, NULL, 1, args->precount);
4162 break;
4163 case XT_TAB_DELETE:
4164 if (args->precount < 0)
4165 delete_tab(t);
4166 else
4167 TAILQ_FOREACH(tt, &tabs, entry)
4168 if (tt->tab_id == args->precount - 1) {
4169 delete_tab(tt);
4170 break;
4172 break;
4173 case XT_TAB_DELQUIT:
4174 if (gtk_notebook_get_n_pages(notebook) > 1)
4175 delete_tab(t);
4176 else
4177 quit(t, args);
4178 break;
4179 case XT_TAB_OPEN:
4180 if (strlen(url) > 0)
4182 else {
4183 rv = XT_CB_PASSTHROUGH;
4184 goto done;
4186 load_uri(t, url);
4187 break;
4188 case XT_TAB_SHOW:
4189 if (show_tabs == 0) {
4190 show_tabs = 1;
4191 notebook_tab_set_visibility();
4193 break;
4194 case XT_TAB_HIDE:
4195 if (show_tabs == 1) {
4196 show_tabs = 0;
4197 notebook_tab_set_visibility();
4199 break;
4200 case XT_TAB_NEXTSTYLE:
4201 if (tab_style == XT_TABS_NORMAL) {
4202 tab_style = XT_TABS_COMPACT;
4203 recolor_compact_tabs();
4205 else
4206 tab_style = XT_TABS_NORMAL;
4207 notebook_tab_set_visibility();
4208 break;
4209 case XT_TAB_UNDO_CLOSE:
4210 if (undo_count == 0) {
4211 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4212 __func__);
4213 goto done;
4214 } else {
4215 undo_count--;
4216 u = TAILQ_FIRST(&undos);
4217 create_new_tab(u->uri, u, 1, -1);
4219 TAILQ_REMOVE(&undos, u, entry);
4220 g_free(u->uri);
4221 /* u->history is freed in create_new_tab() */
4222 g_free(u);
4224 break;
4225 default:
4226 rv = XT_CB_PASSTHROUGH;
4227 goto done;
4230 done:
4231 if (args->s) {
4232 g_free(args->s);
4233 args->s = NULL;
4236 return (rv);
4240 resizetab(struct tab *t, struct karg *args)
4242 if (t == NULL || args == NULL) {
4243 show_oops(NULL, "resizetab invalid parameters");
4244 return (XT_CB_PASSTHROUGH);
4247 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4248 t->tab_id, args->i);
4250 setzoom_webkit(t, args->i);
4252 return (XT_CB_HANDLED);
4256 movetab(struct tab *t, struct karg *args)
4258 int n, dest;
4260 if (t == NULL || args == NULL) {
4261 show_oops(NULL, "movetab invalid parameters");
4262 return (XT_CB_PASSTHROUGH);
4265 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4266 t->tab_id, args->i);
4268 if (args->i >= XT_TAB_INVALID)
4269 return (XT_CB_PASSTHROUGH);
4271 if (TAILQ_EMPTY(&tabs))
4272 return (XT_CB_PASSTHROUGH);
4274 n = gtk_notebook_get_n_pages(notebook);
4275 dest = gtk_notebook_get_current_page(notebook);
4277 switch (args->i) {
4278 case XT_TAB_NEXT:
4279 if (args->precount < 0)
4280 dest = dest == n - 1 ? 0 : dest + 1;
4281 else
4282 dest = args->precount - 1;
4284 break;
4285 case XT_TAB_PREV:
4286 if (args->precount < 0)
4287 dest -= 1;
4288 else
4289 dest -= args->precount % n;
4291 if (dest < 0)
4292 dest += n;
4294 break;
4295 case XT_TAB_FIRST:
4296 dest = 0;
4297 break;
4298 case XT_TAB_LAST:
4299 dest = n - 1;
4300 break;
4301 default:
4302 return (XT_CB_PASSTHROUGH);
4305 if (dest < 0 || dest >= n)
4306 return (XT_CB_PASSTHROUGH);
4307 if (t->tab_id == dest) {
4308 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4309 return (XT_CB_HANDLED);
4312 set_current_tab(dest);
4314 return (XT_CB_HANDLED);
4317 int cmd_prefix = 0;
4320 command(struct tab *t, struct karg *args)
4322 char *s = NULL, *ss = NULL;
4323 GdkColor color;
4324 const gchar *uri;
4326 if (t == NULL || args == NULL) {
4327 show_oops(NULL, "command invalid parameters");
4328 return (XT_CB_PASSTHROUGH);
4331 switch (args->i) {
4332 case '/':
4333 s = "/";
4334 break;
4335 case '?':
4336 s = "?";
4337 break;
4338 case ':':
4339 if (cmd_prefix == 0)
4340 s = ":";
4341 else {
4342 ss = g_strdup_printf(":%d", cmd_prefix);
4343 s = ss;
4344 cmd_prefix = 0;
4346 break;
4347 case XT_CMD_OPEN:
4348 s = ":open ";
4349 break;
4350 case XT_CMD_TABNEW:
4351 s = ":tabnew ";
4352 break;
4353 case XT_CMD_OPEN_CURRENT:
4354 s = ":open ";
4355 /* FALL THROUGH */
4356 case XT_CMD_TABNEW_CURRENT:
4357 if (!s) /* FALL THROUGH? */
4358 s = ":tabnew ";
4359 if ((uri = get_uri(t)) != NULL) {
4360 ss = g_strdup_printf("%s%s", s, uri);
4361 s = ss;
4363 break;
4364 default:
4365 show_oops(t, "command: invalid opcode %d", args->i);
4366 return (XT_CB_PASSTHROUGH);
4369 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4371 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4372 gdk_color_parse(XT_COLOR_WHITE, &color);
4373 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4374 show_cmd(t);
4375 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4376 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4378 if (ss)
4379 g_free(ss);
4381 return (XT_CB_HANDLED);
4385 * Return a new string with a download row (in html)
4386 * appended. Old string is freed.
4388 char *
4389 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4392 WebKitDownloadStatus stat;
4393 char *status_html = NULL, *cmd_html = NULL, *new_html;
4394 gdouble progress;
4395 char cur_sz[FMT_SCALED_STRSIZE];
4396 char tot_sz[FMT_SCALED_STRSIZE];
4397 char *xtp_prefix;
4399 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4401 /* All actions wil take this form:
4402 * xxxt://class/seskey
4404 xtp_prefix = g_strdup_printf("%s%d/%s/",
4405 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4407 stat = webkit_download_get_status(dl->download);
4409 switch (stat) {
4410 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4411 status_html = g_strdup_printf("Finished");
4412 cmd_html = g_strdup_printf(
4413 "<a href='%s%d/%d'>Remove</a>",
4414 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4415 break;
4416 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4417 /* gather size info */
4418 progress = 100 * webkit_download_get_progress(dl->download);
4420 fmt_scaled(
4421 webkit_download_get_current_size(dl->download), cur_sz);
4422 fmt_scaled(
4423 webkit_download_get_total_size(dl->download), tot_sz);
4425 status_html = g_strdup_printf(
4426 "<div style='width: 100%%' align='center'>"
4427 "<div class='progress-outer'>"
4428 "<div class='progress-inner' style='width: %.2f%%'>"
4429 "</div></div></div>"
4430 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4431 progress, cur_sz, tot_sz, progress);
4433 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4434 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4436 break;
4437 /* LLL */
4438 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4439 status_html = g_strdup_printf("Cancelled");
4440 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4441 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4442 break;
4443 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4444 status_html = g_strdup_printf("Error!");
4445 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4446 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4447 break;
4448 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4449 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4450 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4451 status_html = g_strdup_printf("Starting");
4452 break;
4453 default:
4454 show_oops(t, "%s: unknown download status", __func__);
4457 new_html = g_strdup_printf(
4458 "%s\n<tr><td>%s</td><td>%s</td>"
4459 "<td style='text-align:center'>%s</td></tr>\n",
4460 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4461 status_html, cmd_html);
4462 g_free(html);
4464 if (status_html)
4465 g_free(status_html);
4467 if (cmd_html)
4468 g_free(cmd_html);
4470 g_free(xtp_prefix);
4472 return new_html;
4476 * update all download tabs apart from one. Pass NULL if
4477 * you want to update all.
4479 void
4480 update_download_tabs(struct tab *apart_from)
4482 struct tab *t;
4483 if (!updating_dl_tabs) {
4484 updating_dl_tabs = 1; /* stop infinite recursion */
4485 TAILQ_FOREACH(t, &tabs, entry)
4486 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4487 && (t != apart_from))
4488 xtp_page_dl(t, NULL);
4489 updating_dl_tabs = 0;
4494 * update all cookie tabs apart from one. Pass NULL if
4495 * you want to update all.
4497 void
4498 update_cookie_tabs(struct tab *apart_from)
4500 struct tab *t;
4501 if (!updating_cl_tabs) {
4502 updating_cl_tabs = 1; /* stop infinite recursion */
4503 TAILQ_FOREACH(t, &tabs, entry)
4504 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4505 && (t != apart_from))
4506 xtp_page_cl(t, NULL);
4507 updating_cl_tabs = 0;
4512 * update all history tabs apart from one. Pass NULL if
4513 * you want to update all.
4515 void
4516 update_history_tabs(struct tab *apart_from)
4518 struct tab *t;
4520 if (!updating_hl_tabs) {
4521 updating_hl_tabs = 1; /* stop infinite recursion */
4522 TAILQ_FOREACH(t, &tabs, entry)
4523 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4524 && (t != apart_from))
4525 xtp_page_hl(t, NULL);
4526 updating_hl_tabs = 0;
4530 /* cookie management XTP page */
4532 xtp_page_cl(struct tab *t, struct karg *args)
4534 char *body, *page, *tmp;
4535 int i = 1; /* all ids start 1 */
4536 GSList *sc, *pc, *pc_start;
4537 SoupCookie *c;
4538 char *type, *table_headers, *last_domain;
4540 DNPRINTF(XT_D_CMD, "%s", __func__);
4542 if (t == NULL) {
4543 show_oops(NULL, "%s invalid parameters", __func__);
4544 return (1);
4547 /* Generate a new session key */
4548 if (!updating_cl_tabs)
4549 generate_xtp_session_key(&cl_session_key);
4551 /* table headers */
4552 table_headers = g_strdup_printf("<table><tr>"
4553 "<th>Type</th>"
4554 "<th>Name</th>"
4555 "<th style='width:200px'>Value</th>"
4556 "<th>Path</th>"
4557 "<th>Expires</th>"
4558 "<th>Secure</th>"
4559 "<th>HTTP<br />only</th>"
4560 "<th style='width:40px'>Rm</th></tr>\n");
4562 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4563 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4564 pc_start = pc;
4566 body = NULL;
4567 last_domain = strdup("");
4568 for (; sc; sc = sc->next) {
4569 c = sc->data;
4571 if (strcmp(last_domain, c->domain) != 0) {
4572 /* new domain */
4573 free(last_domain);
4574 last_domain = strdup(c->domain);
4576 if (body != NULL) {
4577 tmp = body;
4578 body = g_strdup_printf("%s</table>"
4579 "<h2>%s</h2>%s\n",
4580 body, c->domain, table_headers);
4581 g_free(tmp);
4582 } else {
4583 /* first domain */
4584 body = g_strdup_printf("<h2>%s</h2>%s\n",
4585 c->domain, table_headers);
4589 type = "Session";
4590 for (pc = pc_start; pc; pc = pc->next)
4591 if (soup_cookie_equal(pc->data, c)) {
4592 type = "Session + Persistent";
4593 break;
4596 tmp = body;
4597 body = g_strdup_printf(
4598 "%s\n<tr>"
4599 "<td>%s</td>"
4600 "<td style='word-wrap:normal'>%s</td>"
4601 "<td>"
4602 " <textarea rows='4'>%s</textarea>"
4603 "</td>"
4604 "<td>%s</td>"
4605 "<td>%s</td>"
4606 "<td>%d</td>"
4607 "<td>%d</td>"
4608 "<td style='text-align:center'>"
4609 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4610 body,
4611 type,
4612 c->name,
4613 c->value,
4614 c->path,
4615 c->expires ?
4616 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4617 c->secure,
4618 c->http_only,
4620 XT_XTP_STR,
4621 XT_XTP_CL,
4622 cl_session_key,
4623 XT_XTP_CL_REMOVE,
4627 g_free(tmp);
4628 i++;
4631 soup_cookies_free(sc);
4632 soup_cookies_free(pc);
4634 /* small message if there are none */
4635 if (i == 1) {
4636 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4637 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4639 tmp = body;
4640 body = g_strdup_printf("%s</table>", body);
4641 g_free(tmp);
4643 page = get_html_page("Cookie Jar", body, "", TRUE);
4644 g_free(body);
4645 g_free(table_headers);
4646 g_free(last_domain);
4648 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4649 update_cookie_tabs(t);
4651 g_free(page);
4653 return (0);
4657 xtp_page_hl(struct tab *t, struct karg *args)
4659 char *body, *page, *tmp;
4660 struct history *h;
4661 int i = 1; /* all ids start 1 */
4663 DNPRINTF(XT_D_CMD, "%s", __func__);
4665 if (t == NULL) {
4666 show_oops(NULL, "%s invalid parameters", __func__);
4667 return (1);
4670 /* Generate a new session key */
4671 if (!updating_hl_tabs)
4672 generate_xtp_session_key(&hl_session_key);
4674 /* body */
4675 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4676 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4678 RB_FOREACH_REVERSE(h, history_list, &hl) {
4679 tmp = body;
4680 body = g_strdup_printf(
4681 "%s\n<tr>"
4682 "<td><a href='%s'>%s</a></td>"
4683 "<td>%s</td>"
4684 "<td style='text-align: center'>"
4685 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4686 body, h->uri, h->uri, h->title,
4687 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4688 XT_XTP_HL_REMOVE, i);
4690 g_free(tmp);
4691 i++;
4694 /* small message if there are none */
4695 if (i == 1) {
4696 tmp = body;
4697 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4698 "colspan='3'>No History</td></tr>\n", body);
4699 g_free(tmp);
4702 tmp = body;
4703 body = g_strdup_printf("%s</table>", body);
4704 g_free(tmp);
4706 page = get_html_page("History", body, "", TRUE);
4707 g_free(body);
4710 * update all history manager tabs as the xtp session
4711 * key has now changed. No need to update the current tab.
4712 * Already did that above.
4714 update_history_tabs(t);
4716 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4717 g_free(page);
4719 return (0);
4723 * Generate a web page detailing the status of any downloads
4726 xtp_page_dl(struct tab *t, struct karg *args)
4728 struct download *dl;
4729 char *body, *page, *tmp;
4730 char *ref;
4731 int n_dl = 1;
4733 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4735 if (t == NULL) {
4736 show_oops(NULL, "%s invalid parameters", __func__);
4737 return (1);
4741 * Generate a new session key for next page instance.
4742 * This only happens for the top level call to xtp_page_dl()
4743 * in which case updating_dl_tabs is 0.
4745 if (!updating_dl_tabs)
4746 generate_xtp_session_key(&dl_session_key);
4748 /* header - with refresh so as to update */
4749 if (refresh_interval >= 1)
4750 ref = g_strdup_printf(
4751 "<meta http-equiv='refresh' content='%u"
4752 ";url=%s%d/%s/%d' />\n",
4753 refresh_interval,
4754 XT_XTP_STR,
4755 XT_XTP_DL,
4756 dl_session_key,
4757 XT_XTP_DL_LIST);
4758 else
4759 ref = g_strdup("");
4761 body = g_strdup_printf("<div align='center'>"
4762 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4763 "</p><table><tr><th style='width: 60%%'>"
4764 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4765 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4767 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4768 body = xtp_page_dl_row(t, body, dl);
4769 n_dl++;
4772 /* message if no downloads in list */
4773 if (n_dl == 1) {
4774 tmp = body;
4775 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4776 " style='text-align: center'>"
4777 "No downloads</td></tr>\n", body);
4778 g_free(tmp);
4781 tmp = body;
4782 body = g_strdup_printf("%s</table></div>", body);
4783 g_free(tmp);
4785 page = get_html_page("Downloads", body, ref, 1);
4786 g_free(ref);
4787 g_free(body);
4790 * update all download manager tabs as the xtp session
4791 * key has now changed. No need to update the current tab.
4792 * Already did that above.
4794 update_download_tabs(t);
4796 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4797 g_free(page);
4799 return (0);
4803 search(struct tab *t, struct karg *args)
4805 gboolean d;
4807 if (t == NULL || args == NULL) {
4808 show_oops(NULL, "search invalid parameters");
4809 return (1);
4811 if (t->search_text == NULL) {
4812 if (global_search == NULL)
4813 return (XT_CB_PASSTHROUGH);
4814 else {
4815 t->search_text = g_strdup(global_search);
4816 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4817 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4821 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4822 t->tab_id, args->i, t->search_forward, t->search_text);
4824 switch (args->i) {
4825 case XT_SEARCH_NEXT:
4826 d = t->search_forward;
4827 break;
4828 case XT_SEARCH_PREV:
4829 d = !t->search_forward;
4830 break;
4831 default:
4832 return (XT_CB_PASSTHROUGH);
4835 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4837 return (XT_CB_HANDLED);
4840 struct settings_args {
4841 char **body;
4842 int i;
4845 void
4846 print_setting(struct settings *s, char *val, void *cb_args)
4848 char *tmp, *color;
4849 struct settings_args *sa = cb_args;
4851 if (sa == NULL)
4852 return;
4854 if (s->flags & XT_SF_RUNTIME)
4855 color = "#22cc22";
4856 else
4857 color = "#cccccc";
4859 tmp = *sa->body;
4860 *sa->body = g_strdup_printf(
4861 "%s\n<tr>"
4862 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4863 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4864 *sa->body,
4865 color,
4866 s->name,
4867 color,
4870 g_free(tmp);
4871 sa->i++;
4875 set_show(struct tab *t, struct karg *args)
4877 char *body, *page, *tmp;
4878 int i = 1;
4879 struct settings_args sa;
4881 bzero(&sa, sizeof sa);
4882 sa.body = &body;
4884 /* body */
4885 body = g_strdup_printf("<div align='center'><table><tr>"
4886 "<th align='left'>Setting</th>"
4887 "<th align='left'>Value</th></tr>\n");
4889 settings_walk(print_setting, &sa);
4890 i = sa.i;
4892 /* small message if there are none */
4893 if (i == 1) {
4894 tmp = body;
4895 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4896 "colspan='2'>No settings</td></tr>\n", body);
4897 g_free(tmp);
4900 tmp = body;
4901 body = g_strdup_printf("%s</table></div>", body);
4902 g_free(tmp);
4904 page = get_html_page("Settings", body, "", 0);
4906 g_free(body);
4908 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4910 g_free(page);
4912 return (XT_CB_PASSTHROUGH);
4916 set(struct tab *t, struct karg *args)
4918 char *p, *val;
4919 int i;
4921 if (args == NULL || args->s == NULL)
4922 return (set_show(t, args));
4924 /* strip spaces */
4925 p = g_strstrip(args->s);
4927 if (strlen(p) == 0)
4928 return (set_show(t, args));
4930 /* we got some sort of string */
4931 val = g_strrstr(p, "=");
4932 if (val) {
4933 *val++ = '\0';
4934 val = g_strchomp(val);
4935 p = g_strchomp(p);
4937 for (i = 0; i < LENGTH(rs); i++) {
4938 if (strcmp(rs[i].name, p))
4939 continue;
4941 if (rs[i].activate) {
4942 if (rs[i].activate(val))
4943 show_oops(t, "%s invalid value %s",
4944 p, val);
4945 else
4946 show_oops(t, ":set %s = %s", p, val);
4947 goto done;
4948 } else {
4949 show_oops(t, "not a runtime option: %s", p);
4950 goto done;
4953 show_oops(t, "unknown option: %s", p);
4954 } else {
4955 p = g_strchomp(p);
4957 for (i = 0; i < LENGTH(rs); i++) {
4958 if (strcmp(rs[i].name, p))
4959 continue;
4961 /* XXX this could use some cleanup */
4962 switch (rs[i].type) {
4963 case XT_S_INT:
4964 if (rs[i].ival)
4965 show_oops(t, "%s = %d",
4966 rs[i].name, *rs[i].ival);
4967 else if (rs[i].s && rs[i].s->get)
4968 show_oops(t, "%s = %s",
4969 rs[i].name,
4970 rs[i].s->get(&rs[i]));
4971 else if (rs[i].s && rs[i].s->get == NULL)
4972 show_oops(t, "%s = ...", rs[i].name);
4973 else
4974 show_oops(t, "%s = ", rs[i].name);
4975 break;
4976 case XT_S_FLOAT:
4977 if (rs[i].fval)
4978 show_oops(t, "%s = %f",
4979 rs[i].name, *rs[i].fval);
4980 else if (rs[i].s && rs[i].s->get)
4981 show_oops(t, "%s = %s",
4982 rs[i].name,
4983 rs[i].s->get(&rs[i]));
4984 else if (rs[i].s && rs[i].s->get == NULL)
4985 show_oops(t, "%s = ...", rs[i].name);
4986 else
4987 show_oops(t, "%s = ", rs[i].name);
4988 break;
4989 case XT_S_STR:
4990 if (rs[i].sval && *rs[i].sval)
4991 show_oops(t, "%s = %s",
4992 rs[i].name, *rs[i].sval);
4993 else if (rs[i].s && rs[i].s->get)
4994 show_oops(t, "%s = %s",
4995 rs[i].name,
4996 rs[i].s->get(&rs[i]));
4997 else if (rs[i].s && rs[i].s->get == NULL)
4998 show_oops(t, "%s = ...", rs[i].name);
4999 else
5000 show_oops(t, "%s = ", rs[i].name);
5001 break;
5002 default:
5003 show_oops(t, "unknown type for %s", rs[i].name);
5004 goto done;
5007 goto done;
5009 show_oops(t, "unknown option: %s", p);
5011 done:
5012 return (XT_CB_PASSTHROUGH);
5016 session_save(struct tab *t, char *filename)
5018 struct karg a;
5019 int rv = 1;
5021 if (strlen(filename) == 0)
5022 goto done;
5024 if (filename[0] == '.' || filename[0] == '/')
5025 goto done;
5027 a.s = filename;
5028 if (save_tabs(t, &a))
5029 goto done;
5030 strlcpy(named_session, filename, sizeof named_session);
5032 rv = 0;
5033 done:
5034 return (rv);
5038 session_open(struct tab *t, char *filename)
5040 struct karg a;
5041 int rv = 1;
5043 if (strlen(filename) == 0)
5044 goto done;
5046 if (filename[0] == '.' || filename[0] == '/')
5047 goto done;
5049 a.s = filename;
5050 a.i = XT_SES_CLOSETABS;
5051 if (open_tabs(t, &a))
5052 goto done;
5054 strlcpy(named_session, filename, sizeof named_session);
5056 rv = 0;
5057 done:
5058 return (rv);
5062 session_delete(struct tab *t, char *filename)
5064 char file[PATH_MAX];
5065 int rv = 1;
5067 if (strlen(filename) == 0)
5068 goto done;
5070 if (filename[0] == '.' || filename[0] == '/')
5071 goto done;
5073 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5074 if (unlink(file))
5075 goto done;
5077 if (!strcmp(filename, named_session))
5078 strlcpy(named_session, XT_SAVED_TABS_FILE,
5079 sizeof named_session);
5081 rv = 0;
5082 done:
5083 return (rv);
5087 session_cmd(struct tab *t, struct karg *args)
5089 char *filename = args->s;
5091 if (t == NULL)
5092 return (1);
5094 if (args->i & XT_SHOW)
5095 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5096 XT_SAVED_TABS_FILE : named_session);
5097 else if (args->i & XT_SAVE) {
5098 if (session_save(t, filename)) {
5099 show_oops(t, "Can't save session: %s",
5100 filename ? filename : "INVALID");
5101 goto done;
5103 } else if (args->i & XT_OPEN) {
5104 if (session_open(t, filename)) {
5105 show_oops(t, "Can't open session: %s",
5106 filename ? filename : "INVALID");
5107 goto done;
5109 } else if (args->i & XT_DELETE) {
5110 if (session_delete(t, filename)) {
5111 show_oops(t, "Can't delete session: %s",
5112 filename ? filename : "INVALID");
5113 goto done;
5116 done:
5117 return (XT_CB_PASSTHROUGH);
5121 * Make a hardcopy of the page
5124 print_page(struct tab *t, struct karg *args)
5126 WebKitWebFrame *frame;
5127 GtkPageSetup *ps;
5128 GtkPrintOperation *op;
5129 GtkPrintOperationAction action;
5130 GtkPrintOperationResult print_res;
5131 GError *g_err = NULL;
5132 int marg_l, marg_r, marg_t, marg_b;
5134 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5136 ps = gtk_page_setup_new();
5137 op = gtk_print_operation_new();
5138 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5139 frame = webkit_web_view_get_main_frame(t->wv);
5141 /* the default margins are too small, so we will bump them */
5142 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5143 XT_PRINT_EXTRA_MARGIN;
5144 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5145 XT_PRINT_EXTRA_MARGIN;
5146 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5147 XT_PRINT_EXTRA_MARGIN;
5148 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5149 XT_PRINT_EXTRA_MARGIN;
5151 /* set margins */
5152 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5153 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5154 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5155 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5157 gtk_print_operation_set_default_page_setup(op, ps);
5159 /* this appears to free 'op' and 'ps' */
5160 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5162 /* check it worked */
5163 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5164 show_oops(NULL, "can't print: %s", g_err->message);
5165 g_error_free (g_err);
5166 return (1);
5169 return (0);
5173 go_home(struct tab *t, struct karg *args)
5175 load_uri(t, home);
5176 return (0);
5180 restart(struct tab *t, struct karg *args)
5182 struct karg a;
5184 a.s = XT_RESTART_TABS_FILE;
5185 save_tabs(t, &a);
5186 execvp(start_argv[0], start_argv);
5187 /* NOTREACHED */
5189 return (0);
5192 #define CTRL GDK_CONTROL_MASK
5193 #define MOD1 GDK_MOD1_MASK
5194 #define SHFT GDK_SHIFT_MASK
5196 /* inherent to GTK not all keys will be caught at all times */
5197 /* XXX sort key bindings */
5198 struct key_binding {
5199 char *cmd;
5200 guint mask;
5201 guint use_in_entry;
5202 guint key;
5203 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5204 } keys[] = {
5205 { "cookiejar", MOD1, 0, GDK_j },
5206 { "downloadmgr", MOD1, 0, GDK_d },
5207 { "history", MOD1, 0, GDK_h },
5208 { "print", CTRL, 0, GDK_p },
5209 { "search", 0, 0, GDK_slash },
5210 { "searchb", 0, 0, GDK_question },
5211 { "statustoggle", CTRL, 0, GDK_n },
5212 { "command", 0, 0, GDK_colon },
5213 { "qa", CTRL, 0, GDK_q },
5214 { "restart", MOD1, 0, GDK_q },
5215 { "js toggle", CTRL, 0, GDK_j },
5216 { "cookie toggle", MOD1, 0, GDK_c },
5217 { "togglesrc", CTRL, 0, GDK_s },
5218 { "yankuri", 0, 0, GDK_y },
5219 { "pasteuricur", 0, 0, GDK_p },
5220 { "pasteurinew", 0, 0, GDK_P },
5221 { "toplevel toggle", 0, 0, GDK_F4 },
5222 { "help", 0, 0, GDK_F1 },
5223 { "run_script", MOD1, 0, GDK_r },
5225 /* search */
5226 { "searchnext", 0, 0, GDK_n },
5227 { "searchprevious", 0, 0, GDK_N },
5229 /* focus */
5230 { "focusaddress", 0, 0, GDK_F6 },
5231 { "focussearch", 0, 0, GDK_F7 },
5233 /* hinting */
5234 { "hinting", 0, 0, GDK_f },
5236 /* custom stylesheet */
5237 { "userstyle", 0, 0, GDK_i },
5239 /* navigation */
5240 { "goback", 0, 0, GDK_BackSpace },
5241 { "goback", MOD1, 0, GDK_Left },
5242 { "goforward", SHFT, 0, GDK_BackSpace },
5243 { "goforward", MOD1, 0, GDK_Right },
5244 { "reload", 0, 0, GDK_F5 },
5245 { "reload", CTRL, 0, GDK_r },
5246 { "reload", CTRL, 0, GDK_l },
5247 { "favorites", MOD1, 1, GDK_f },
5249 /* vertical movement */
5250 { "scrolldown", 0, 0, GDK_j },
5251 { "scrolldown", 0, 0, GDK_Down },
5252 { "scrollup", 0, 0, GDK_Up },
5253 { "scrollup", 0, 0, GDK_k },
5254 { "scrollbottom", 0, 0, GDK_G },
5255 { "scrollbottom", 0, 0, GDK_End },
5256 { "scrolltop", 0, 0, GDK_Home },
5257 { "scrollpagedown", 0, 0, GDK_space },
5258 { "scrollpagedown", CTRL, 0, GDK_f },
5259 { "scrollhalfdown", CTRL, 0, GDK_d },
5260 { "scrollpagedown", 0, 0, GDK_Page_Down },
5261 { "scrollpageup", 0, 0, GDK_Page_Up },
5262 { "scrollpageup", CTRL, 0, GDK_b },
5263 { "scrollhalfup", CTRL, 0, GDK_u },
5264 /* horizontal movement */
5265 { "scrollright", 0, 0, GDK_l },
5266 { "scrollright", 0, 0, GDK_Right },
5267 { "scrollleft", 0, 0, GDK_Left },
5268 { "scrollleft", 0, 0, GDK_h },
5269 { "scrollfarright", 0, 0, GDK_dollar },
5270 { "scrollfarleft", 0, 0, GDK_0 },
5272 /* tabs */
5273 { "tabnew", CTRL, 0, GDK_t },
5274 { "999tabnew", CTRL, 0, GDK_T },
5275 { "tabclose", CTRL, 1, GDK_w },
5276 { "tabundoclose", 0, 0, GDK_U },
5277 { "tabnext 1", CTRL, 0, GDK_1 },
5278 { "tabnext 2", CTRL, 0, GDK_2 },
5279 { "tabnext 3", CTRL, 0, GDK_3 },
5280 { "tabnext 4", CTRL, 0, GDK_4 },
5281 { "tabnext 5", CTRL, 0, GDK_5 },
5282 { "tabnext 6", CTRL, 0, GDK_6 },
5283 { "tabnext 7", CTRL, 0, GDK_7 },
5284 { "tabnext 8", CTRL, 0, GDK_8 },
5285 { "tabnext 9", CTRL, 0, GDK_9 },
5286 { "tabfirst", CTRL, 0, GDK_less },
5287 { "tablast", CTRL, 0, GDK_greater },
5288 { "tabprevious", CTRL, 0, GDK_Left },
5289 { "tabnext", CTRL, 0, GDK_Right },
5290 { "focusout", CTRL, 0, GDK_minus },
5291 { "focusin", CTRL, 0, GDK_plus },
5292 { "focusin", CTRL, 0, GDK_equal },
5293 { "focusreset", CTRL, 0, GDK_0 },
5295 /* command aliases (handy when -S flag is used) */
5296 { "promptopen", 0, 0, GDK_F9 },
5297 { "promptopencurrent", 0, 0, GDK_F10 },
5298 { "prompttabnew", 0, 0, GDK_F11 },
5299 { "prompttabnewcurrent",0, 0, GDK_F12 },
5301 TAILQ_HEAD(keybinding_list, key_binding);
5303 void
5304 walk_kb(struct settings *s,
5305 void (*cb)(struct settings *, char *, void *), void *cb_args)
5307 struct key_binding *k;
5308 char str[1024];
5310 if (s == NULL || cb == NULL) {
5311 show_oops(NULL, "walk_kb invalid parameters");
5312 return;
5315 TAILQ_FOREACH(k, &kbl, entry) {
5316 if (k->cmd == NULL)
5317 continue;
5318 str[0] = '\0';
5320 /* sanity */
5321 if (gdk_keyval_name(k->key) == NULL)
5322 continue;
5324 strlcat(str, k->cmd, sizeof str);
5325 strlcat(str, ",", sizeof str);
5327 if (k->mask & GDK_SHIFT_MASK)
5328 strlcat(str, "S-", sizeof str);
5329 if (k->mask & GDK_CONTROL_MASK)
5330 strlcat(str, "C-", sizeof str);
5331 if (k->mask & GDK_MOD1_MASK)
5332 strlcat(str, "M1-", sizeof str);
5333 if (k->mask & GDK_MOD2_MASK)
5334 strlcat(str, "M2-", sizeof str);
5335 if (k->mask & GDK_MOD3_MASK)
5336 strlcat(str, "M3-", sizeof str);
5337 if (k->mask & GDK_MOD4_MASK)
5338 strlcat(str, "M4-", sizeof str);
5339 if (k->mask & GDK_MOD5_MASK)
5340 strlcat(str, "M5-", sizeof str);
5342 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5343 cb(s, str, cb_args);
5347 void
5348 init_keybindings(void)
5350 int i;
5351 struct key_binding *k;
5353 for (i = 0; i < LENGTH(keys); i++) {
5354 k = g_malloc0(sizeof *k);
5355 k->cmd = keys[i].cmd;
5356 k->mask = keys[i].mask;
5357 k->use_in_entry = keys[i].use_in_entry;
5358 k->key = keys[i].key;
5359 TAILQ_INSERT_HEAD(&kbl, k, entry);
5361 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5362 k->cmd ? k->cmd : "unnamed key");
5366 void
5367 keybinding_clearall(void)
5369 struct key_binding *k, *next;
5371 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5372 next = TAILQ_NEXT(k, entry);
5373 if (k->cmd == NULL)
5374 continue;
5376 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5377 k->cmd ? k->cmd : "unnamed key");
5378 TAILQ_REMOVE(&kbl, k, entry);
5379 g_free(k);
5384 keybinding_add(char *cmd, char *key, int use_in_entry)
5386 struct key_binding *k;
5387 guint keyval, mask = 0;
5388 int i;
5390 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5392 /* Keys which are to be used in entry have been prefixed with an
5393 * exclamation mark. */
5394 if (use_in_entry)
5395 key++;
5397 /* find modifier keys */
5398 if (strstr(key, "S-"))
5399 mask |= GDK_SHIFT_MASK;
5400 if (strstr(key, "C-"))
5401 mask |= GDK_CONTROL_MASK;
5402 if (strstr(key, "M1-"))
5403 mask |= GDK_MOD1_MASK;
5404 if (strstr(key, "M2-"))
5405 mask |= GDK_MOD2_MASK;
5406 if (strstr(key, "M3-"))
5407 mask |= GDK_MOD3_MASK;
5408 if (strstr(key, "M4-"))
5409 mask |= GDK_MOD4_MASK;
5410 if (strstr(key, "M5-"))
5411 mask |= GDK_MOD5_MASK;
5413 /* find keyname */
5414 for (i = strlen(key) - 1; i > 0; i--)
5415 if (key[i] == '-')
5416 key = &key[i + 1];
5418 /* validate keyname */
5419 keyval = gdk_keyval_from_name(key);
5420 if (keyval == GDK_VoidSymbol) {
5421 warnx("invalid keybinding name %s", key);
5422 return (1);
5424 /* must run this test too, gtk+ doesn't handle 10 for example */
5425 if (gdk_keyval_name(keyval) == NULL) {
5426 warnx("invalid keybinding name %s", key);
5427 return (1);
5430 /* Remove eventual dupes. */
5431 TAILQ_FOREACH(k, &kbl, entry)
5432 if (k->key == keyval && k->mask == mask) {
5433 TAILQ_REMOVE(&kbl, k, entry);
5434 g_free(k);
5435 break;
5438 /* add keyname */
5439 k = g_malloc0(sizeof *k);
5440 k->cmd = g_strdup(cmd);
5441 k->mask = mask;
5442 k->use_in_entry = use_in_entry;
5443 k->key = keyval;
5445 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5446 k->cmd,
5447 k->mask,
5448 k->use_in_entry,
5449 k->key);
5450 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5451 k->cmd, gdk_keyval_name(keyval));
5453 TAILQ_INSERT_HEAD(&kbl, k, entry);
5455 return (0);
5459 add_kb(struct settings *s, char *entry)
5461 char *kb, *key;
5463 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5465 /* clearall is special */
5466 if (!strcmp(entry, "clearall")) {
5467 keybinding_clearall();
5468 return (0);
5471 kb = strstr(entry, ",");
5472 if (kb == NULL)
5473 return (1);
5474 *kb = '\0';
5475 key = kb + 1;
5477 return (keybinding_add(entry, key, key[0] == '!'));
5480 struct cmd {
5481 char *cmd;
5482 int level;
5483 int (*func)(struct tab *, struct karg *);
5484 int arg;
5485 int type;
5486 } cmds[] = {
5487 { "command", 0, command, ':', 0 },
5488 { "search", 0, command, '/', 0 },
5489 { "searchb", 0, command, '?', 0 },
5490 { "togglesrc", 0, toggle_src, 0, 0 },
5492 /* yanking and pasting */
5493 { "yankuri", 0, yank_uri, 0, 0 },
5494 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5495 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5496 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5498 /* search */
5499 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5500 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5502 /* focus */
5503 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5504 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5506 /* hinting */
5507 { "hinting", 0, hint, 0, 0 },
5509 /* custom stylesheet */
5510 { "userstyle", 0, userstyle, 0, 0 },
5512 /* navigation */
5513 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5514 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5515 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5517 /* vertical movement */
5518 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5519 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5520 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5521 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5522 { "1", 0, move, XT_MOVE_TOP, 0 },
5523 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5524 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5525 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5526 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5527 /* horizontal movement */
5528 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5529 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5530 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5531 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5533 { "favorites", 0, xtp_page_fl, 0, 0 },
5534 { "fav", 0, xtp_page_fl, 0, 0 },
5535 { "favadd", 0, add_favorite, 0, 0 },
5537 { "qall", 0, quit, 0, 0 },
5538 { "quitall", 0, quit, 0, 0 },
5539 { "w", 0, save_tabs, 0, 0 },
5540 { "wq", 0, save_tabs_and_quit, 0, 0 },
5541 { "help", 0, help, 0, 0 },
5542 { "about", 0, about, 0, 0 },
5543 { "stats", 0, stats, 0, 0 },
5544 { "version", 0, about, 0, 0 },
5546 /* js command */
5547 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5548 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5549 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5550 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5551 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5552 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5553 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5554 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5555 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5556 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5557 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5559 /* cookie command */
5560 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5561 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5562 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5563 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5564 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5565 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5566 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5567 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5568 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5569 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5570 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5572 /* toplevel (domain) command */
5573 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5574 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5576 /* cookie jar */
5577 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5579 /* cert command */
5580 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5581 { "save", 1, cert_cmd, XT_SAVE, 0 },
5582 { "show", 1, cert_cmd, XT_SHOW, 0 },
5584 { "ca", 0, ca_cmd, 0, 0 },
5585 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5586 { "dl", 0, xtp_page_dl, 0, 0 },
5587 { "h", 0, xtp_page_hl, 0, 0 },
5588 { "history", 0, xtp_page_hl, 0, 0 },
5589 { "home", 0, go_home, 0, 0 },
5590 { "restart", 0, restart, 0, 0 },
5591 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5592 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5593 { "statustoggle", 0, statustoggle, 0, 0 },
5594 { "run_script", 0, run_page_script, 0, XT_USERARG },
5596 { "print", 0, print_page, 0, 0 },
5598 /* tabs */
5599 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5600 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5601 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5602 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5603 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5604 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5605 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5606 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5607 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5608 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5609 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5610 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5611 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5612 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5613 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5614 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5615 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5616 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5617 { "buffers", 0, buffers, 0, 0 },
5618 { "ls", 0, buffers, 0, 0 },
5619 { "tabs", 0, buffers, 0, 0 },
5621 /* command aliases (handy when -S flag is used) */
5622 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5623 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5624 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5625 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5627 /* settings */
5628 { "set", 0, set, 0, XT_USERARG },
5630 { "fullscreen", 0, fullscreen, 0, 0 },
5631 { "f", 0, fullscreen, 0, 0 },
5633 /* sessions */
5634 { "session", 0, session_cmd, XT_SHOW, 0 },
5635 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5636 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5637 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5638 { "show", 1, session_cmd, XT_SHOW, 0 },
5641 struct {
5642 int index;
5643 int len;
5644 gchar *list[256];
5645 } cmd_status = {-1, 0};
5647 gboolean
5648 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5651 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5652 btn_down = 0;
5654 return (FALSE);
5657 gboolean
5658 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5660 struct karg a;
5662 hide_oops(t);
5663 hide_buffers(t);
5665 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5666 btn_down = 1;
5667 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5668 /* go backward */
5669 a.i = XT_NAV_BACK;
5670 navaction(t, &a);
5672 return (TRUE);
5673 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5674 /* go forward */
5675 a.i = XT_NAV_FORWARD;
5676 navaction(t, &a);
5678 return (TRUE);
5681 return (FALSE);
5684 gboolean
5685 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5687 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5689 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5690 delete_tab(t);
5692 return (FALSE);
5696 * cancel, remove, etc. downloads
5698 void
5699 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5701 struct download find, *d = NULL;
5703 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5705 /* some commands require a valid download id */
5706 if (cmd != XT_XTP_DL_LIST) {
5707 /* lookup download in question */
5708 find.id = id;
5709 d = RB_FIND(download_list, &downloads, &find);
5711 if (d == NULL) {
5712 show_oops(t, "%s: no such download", __func__);
5713 return;
5717 /* decide what to do */
5718 switch (cmd) {
5719 case XT_XTP_DL_CANCEL:
5720 webkit_download_cancel(d->download);
5721 break;
5722 case XT_XTP_DL_REMOVE:
5723 webkit_download_cancel(d->download); /* just incase */
5724 g_object_unref(d->download);
5725 RB_REMOVE(download_list, &downloads, d);
5726 break;
5727 case XT_XTP_DL_LIST:
5728 /* Nothing */
5729 break;
5730 default:
5731 show_oops(t, "%s: unknown command", __func__);
5732 break;
5734 xtp_page_dl(t, NULL);
5738 * Actions on history, only does one thing for now, but
5739 * we provide the function for future actions
5741 void
5742 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5744 struct history *h, *next;
5745 int i = 1;
5747 switch (cmd) {
5748 case XT_XTP_HL_REMOVE:
5749 /* walk backwards, as listed in reverse */
5750 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5751 next = RB_PREV(history_list, &hl, h);
5752 if (id == i) {
5753 RB_REMOVE(history_list, &hl, h);
5754 g_free((gpointer) h->title);
5755 g_free((gpointer) h->uri);
5756 g_free(h);
5757 break;
5759 i++;
5761 break;
5762 case XT_XTP_HL_LIST:
5763 /* Nothing - just xtp_page_hl() below */
5764 break;
5765 default:
5766 show_oops(t, "%s: unknown command", __func__);
5767 break;
5770 xtp_page_hl(t, NULL);
5773 /* remove a favorite */
5774 void
5775 remove_favorite(struct tab *t, int index)
5777 char file[PATH_MAX], *title, *uri = NULL;
5778 char *new_favs, *tmp;
5779 FILE *f;
5780 int i;
5781 size_t len, lineno;
5783 /* open favorites */
5784 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5786 if ((f = fopen(file, "r")) == NULL) {
5787 show_oops(t, "%s: can't open favorites: %s",
5788 __func__, strerror(errno));
5789 return;
5792 /* build a string which will become the new favroites file */
5793 new_favs = g_strdup("");
5795 for (i = 1;;) {
5796 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5797 if (feof(f) || ferror(f))
5798 break;
5799 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5800 if (len == 0) {
5801 free(title);
5802 title = NULL;
5803 continue;
5806 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5807 if (feof(f) || ferror(f)) {
5808 show_oops(t, "%s: can't parse favorites %s",
5809 __func__, strerror(errno));
5810 goto clean;
5814 /* as long as this isn't the one we are deleting add to file */
5815 if (i != index) {
5816 tmp = new_favs;
5817 new_favs = g_strdup_printf("%s%s\n%s\n",
5818 new_favs, title, uri);
5819 g_free(tmp);
5822 free(uri);
5823 uri = NULL;
5824 free(title);
5825 title = NULL;
5826 i++;
5828 fclose(f);
5830 /* write back new favorites file */
5831 if ((f = fopen(file, "w")) == NULL) {
5832 show_oops(t, "%s: can't open favorites: %s",
5833 __func__, strerror(errno));
5834 goto clean;
5837 fwrite(new_favs, strlen(new_favs), 1, f);
5838 fclose(f);
5840 clean:
5841 if (uri)
5842 free(uri);
5843 if (title)
5844 free(title);
5846 g_free(new_favs);
5849 void
5850 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5852 switch (cmd) {
5853 case XT_XTP_FL_LIST:
5854 /* nothing, just the below call to xtp_page_fl() */
5855 break;
5856 case XT_XTP_FL_REMOVE:
5857 remove_favorite(t, arg);
5858 break;
5859 default:
5860 show_oops(t, "%s: invalid favorites command", __func__);
5861 break;
5864 xtp_page_fl(t, NULL);
5867 void
5868 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5870 switch (cmd) {
5871 case XT_XTP_CL_LIST:
5872 /* nothing, just xtp_page_cl() */
5873 break;
5874 case XT_XTP_CL_REMOVE:
5875 remove_cookie(arg);
5876 break;
5877 default:
5878 show_oops(t, "%s: unknown cookie xtp command", __func__);
5879 break;
5882 xtp_page_cl(t, NULL);
5885 /* link an XTP class to it's session key and handler function */
5886 struct xtp_despatch {
5887 uint8_t xtp_class;
5888 char **session_key;
5889 void (*handle_func)(struct tab *, uint8_t, int);
5892 struct xtp_despatch xtp_despatches[] = {
5893 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5894 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5895 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5896 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5897 { XT_XTP_INVALID, NULL, NULL }
5901 * is the url xtp protocol? (xxxt://)
5902 * if so, parse and despatch correct bahvior
5905 parse_xtp_url(struct tab *t, const char *url)
5907 char *dup = NULL, *p, *last;
5908 uint8_t n_tokens = 0;
5909 char *tokens[4] = {NULL, NULL, NULL, ""};
5910 struct xtp_despatch *dsp, *dsp_match = NULL;
5911 uint8_t req_class;
5912 int ret = FALSE;
5915 * tokens array meaning:
5916 * tokens[0] = class
5917 * tokens[1] = session key
5918 * tokens[2] = action
5919 * tokens[3] = optional argument
5922 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5924 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5925 goto clean;
5927 dup = g_strdup(url + strlen(XT_XTP_STR));
5929 /* split out the url */
5930 for ((p = strtok_r(dup, "/", &last)); p;
5931 (p = strtok_r(NULL, "/", &last))) {
5932 if (n_tokens < 4)
5933 tokens[n_tokens++] = p;
5936 /* should be atleast three fields 'class/seskey/command/arg' */
5937 if (n_tokens < 3)
5938 goto clean;
5940 dsp = xtp_despatches;
5941 req_class = atoi(tokens[0]);
5942 while (dsp->xtp_class) {
5943 if (dsp->xtp_class == req_class) {
5944 dsp_match = dsp;
5945 break;
5947 dsp++;
5950 /* did we find one atall? */
5951 if (dsp_match == NULL) {
5952 show_oops(t, "%s: no matching xtp despatch found", __func__);
5953 goto clean;
5956 /* check session key and call despatch function */
5957 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5958 ret = TRUE; /* all is well, this was a valid xtp request */
5959 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5962 clean:
5963 if (dup)
5964 g_free(dup);
5966 return (ret);
5971 void
5972 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5974 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5976 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5978 if (t == NULL) {
5979 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5980 return;
5983 if (uri == NULL) {
5984 show_oops(t, "activate_uri_entry_cb no uri");
5985 return;
5988 uri += strspn(uri, "\t ");
5990 /* if xxxt:// treat specially */
5991 if (parse_xtp_url(t, uri))
5992 return;
5994 /* otherwise continue to load page normally */
5995 load_uri(t, (gchar *)uri);
5996 focus_webview(t);
5999 void
6000 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6002 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6003 char *newuri = NULL;
6004 gchar *enc_search;
6006 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6008 if (t == NULL) {
6009 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6010 return;
6013 if (search_string == NULL) {
6014 show_oops(t, "no search_string");
6015 return;
6018 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6020 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6021 newuri = g_strdup_printf(search_string, enc_search);
6022 g_free(enc_search);
6024 marks_clear(t);
6025 webkit_web_view_load_uri(t->wv, newuri);
6026 focus_webview(t);
6028 if (newuri)
6029 g_free(newuri);
6032 void
6033 check_and_set_cookie(const gchar *uri, struct tab *t)
6035 struct domain *d = NULL;
6036 int es = 0;
6038 if (uri == NULL || t == NULL)
6039 return;
6041 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6042 es = 0;
6043 else
6044 es = 1;
6046 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6047 es ? "enable" : "disable", uri);
6049 g_object_set(G_OBJECT(t->settings),
6050 "enable-html5-local-storage", es, (char *)NULL);
6051 webkit_web_view_set_settings(t->wv, t->settings);
6054 void
6055 check_and_set_js(const gchar *uri, struct tab *t)
6057 struct domain *d = NULL;
6058 int es = 0;
6060 if (uri == NULL || t == NULL)
6061 return;
6063 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6064 es = 0;
6065 else
6066 es = 1;
6068 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6069 es ? "enable" : "disable", uri);
6071 g_object_set(G_OBJECT(t->settings),
6072 "enable-scripts", es, (char *)NULL);
6073 g_object_set(G_OBJECT(t->settings),
6074 "javascript-can-open-windows-automatically", es, (char *)NULL);
6075 webkit_web_view_set_settings(t->wv, t->settings);
6077 button_set_stockid(t->js_toggle,
6078 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6081 gboolean
6082 color_address_bar(gpointer p)
6084 GdkColor color;
6085 struct tab *tt, *t = p;
6086 gchar *col_str = XT_COLOR_YELLOW;
6088 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6090 /* make sure t still exists */
6091 if (t == NULL)
6092 goto done;
6093 TAILQ_FOREACH(tt, &tabs, entry)
6094 if (t == tt)
6095 break;
6096 if (t != tt)
6097 goto done;
6099 switch (load_compare_cert(t, NULL)) {
6100 case CERT_LOCAL:
6101 col_str = XT_COLOR_BLUE;
6102 break;
6103 case CERT_TRUSTED:
6104 col_str = XT_COLOR_GREEN;
6105 break;
6106 case CERT_UNTRUSTED:
6107 col_str = XT_COLOR_YELLOW;
6108 break;
6109 case CERT_BAD:
6110 col_str = XT_COLOR_RED;
6111 break;
6114 gdk_color_parse(col_str, &color);
6115 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6117 if (!strcmp(col_str, XT_COLOR_WHITE))
6118 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6119 else
6120 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6122 col_str = NULL;
6123 done:
6124 return (FALSE /* kill thread */);
6127 void
6128 show_ca_status(struct tab *t, const char *uri)
6130 GdkColor color;
6131 gchar *col_str = XT_COLOR_WHITE;
6133 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6134 ssl_strict_certs, ssl_ca_file, uri);
6136 if (t == NULL)
6137 return;
6139 if (uri == NULL)
6140 goto done;
6141 if (ssl_ca_file == NULL) {
6142 if (g_str_has_prefix(uri, "http://"))
6143 goto done;
6144 if (g_str_has_prefix(uri, "https://")) {
6145 col_str = XT_COLOR_RED;
6146 goto done;
6148 return;
6150 if (g_str_has_prefix(uri, "http://") ||
6151 !g_str_has_prefix(uri, "https://"))
6152 goto done;
6154 /* thread the coloring of the address bar */
6155 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6156 color_address_bar, t, NULL);
6157 return;
6159 done:
6160 if (col_str) {
6161 gdk_color_parse(col_str, &color);
6162 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6164 if (!strcmp(col_str, XT_COLOR_WHITE))
6165 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6166 else
6167 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6171 void
6172 free_favicon(struct tab *t)
6174 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6175 __func__, t->icon_download, t->icon_request);
6177 if (t->icon_request)
6178 g_object_unref(t->icon_request);
6179 if (t->icon_dest_uri)
6180 g_free(t->icon_dest_uri);
6182 t->icon_request = NULL;
6183 t->icon_dest_uri = NULL;
6186 void
6187 xt_icon_from_name(struct tab *t, gchar *name)
6189 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6190 GTK_ENTRY_ICON_PRIMARY, "text-html");
6191 if (show_url == 0)
6192 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6193 GTK_ENTRY_ICON_PRIMARY, "text-html");
6194 else
6195 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6196 GTK_ENTRY_ICON_PRIMARY, NULL);
6199 void
6200 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6202 GdkPixbuf *pb_scaled;
6204 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6205 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6206 GDK_INTERP_BILINEAR);
6207 else
6208 pb_scaled = pb;
6210 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6211 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6212 if (show_url == 0)
6213 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6214 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6215 else
6216 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6217 GTK_ENTRY_ICON_PRIMARY, NULL);
6219 if (pb_scaled != pb)
6220 g_object_unref(pb_scaled);
6223 void
6224 xt_icon_from_file(struct tab *t, char *file)
6226 GdkPixbuf *pb;
6228 if (g_str_has_prefix(file, "file://"))
6229 file += strlen("file://");
6231 pb = gdk_pixbuf_new_from_file(file, NULL);
6232 if (pb) {
6233 xt_icon_from_pixbuf(t, pb);
6234 g_object_unref(pb);
6235 } else
6236 xt_icon_from_name(t, "text-html");
6239 gboolean
6240 is_valid_icon(char *file)
6242 gboolean valid = 0;
6243 const char *mime_type;
6244 GFileInfo *fi;
6245 GFile *gf;
6247 gf = g_file_new_for_path(file);
6248 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6249 NULL, NULL);
6250 mime_type = g_file_info_get_content_type(fi);
6251 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6252 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6253 g_strcmp0(mime_type, "image/png") == 0 ||
6254 g_strcmp0(mime_type, "image/gif") == 0 ||
6255 g_strcmp0(mime_type, "application/octet-stream") == 0;
6256 g_object_unref(fi);
6257 g_object_unref(gf);
6259 return (valid);
6262 void
6263 set_favicon_from_file(struct tab *t, char *file)
6265 struct stat sb;
6267 if (t == NULL || file == NULL)
6268 return;
6270 if (g_str_has_prefix(file, "file://"))
6271 file += strlen("file://");
6272 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6274 if (!stat(file, &sb)) {
6275 if (sb.st_size == 0 || !is_valid_icon(file)) {
6276 /* corrupt icon so trash it */
6277 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6278 __func__, file);
6279 unlink(file);
6280 /* no need to set icon to default here */
6281 return;
6284 xt_icon_from_file(t, file);
6287 void
6288 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6289 WebKitWebView *wv)
6291 WebKitDownloadStatus status = webkit_download_get_status(download);
6292 struct tab *tt = NULL, *t = NULL;
6295 * find the webview instead of passing in the tab as it could have been
6296 * deleted from underneath us.
6298 TAILQ_FOREACH(tt, &tabs, entry) {
6299 if (tt->wv == wv) {
6300 t = tt;
6301 break;
6304 if (t == NULL)
6305 return;
6307 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6308 __func__, t->tab_id, status);
6310 switch (status) {
6311 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6312 /* -1 */
6313 t->icon_download = NULL;
6314 free_favicon(t);
6315 break;
6316 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6317 /* 0 */
6318 break;
6319 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6320 /* 1 */
6321 break;
6322 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6323 /* 2 */
6324 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6325 __func__, t->tab_id);
6326 t->icon_download = NULL;
6327 free_favicon(t);
6328 break;
6329 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6330 /* 3 */
6332 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6333 __func__, t->icon_dest_uri);
6334 set_favicon_from_file(t, t->icon_dest_uri);
6335 /* these will be freed post callback */
6336 t->icon_request = NULL;
6337 t->icon_download = NULL;
6338 break;
6339 default:
6340 break;
6344 void
6345 abort_favicon_download(struct tab *t)
6347 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6349 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6350 if (t->icon_download) {
6351 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6352 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6353 webkit_download_cancel(t->icon_download);
6354 t->icon_download = NULL;
6355 } else
6356 free_favicon(t);
6357 #endif
6359 xt_icon_from_name(t, "text-html");
6362 void
6363 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6365 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6367 if (uri == NULL || t == NULL)
6368 return;
6370 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6371 /* take icon from WebKitIconDatabase */
6372 GdkPixbuf *pb;
6374 pb = webkit_web_view_get_icon_pixbuf(wv);
6375 if (pb) {
6376 xt_icon_from_pixbuf(t, pb);
6377 g_object_unref(pb);
6378 } else
6379 xt_icon_from_name(t, "text-html");
6380 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6381 /* download icon to cache dir */
6382 gchar *name_hash, file[PATH_MAX];
6383 struct stat sb;
6385 if (t->icon_request) {
6386 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6387 return;
6390 /* check to see if we got the icon in cache */
6391 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6392 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6393 g_free(name_hash);
6395 if (!stat(file, &sb)) {
6396 if (sb.st_size > 0) {
6397 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6398 __func__, file);
6399 set_favicon_from_file(t, file);
6400 return;
6403 /* corrupt icon so trash it */
6404 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6405 __func__, file);
6406 unlink(file);
6409 /* create download for icon */
6410 t->icon_request = webkit_network_request_new(uri);
6411 if (t->icon_request == NULL) {
6412 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6413 __func__, uri);
6414 return;
6417 t->icon_download = webkit_download_new(t->icon_request);
6418 if (t->icon_download == NULL)
6419 return;
6421 /* we have to free icon_dest_uri later */
6422 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6423 webkit_download_set_destination_uri(t->icon_download,
6424 t->icon_dest_uri);
6426 if (webkit_download_get_status(t->icon_download) ==
6427 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6428 g_object_unref(t->icon_request);
6429 g_free(t->icon_dest_uri);
6430 t->icon_request = NULL;
6431 t->icon_dest_uri = NULL;
6432 return;
6435 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6436 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6438 webkit_download_start(t->icon_download);
6439 #endif
6442 void
6443 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6445 const gchar *uri = NULL, *title = NULL;
6446 struct history *h, find;
6447 struct karg a;
6448 GdkColor color;
6450 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6451 webkit_web_view_get_load_status(wview),
6452 get_uri(t) ? get_uri(t) : "NOTHING");
6454 if (t == NULL) {
6455 show_oops(NULL, "notify_load_status_cb invalid parameters");
6456 return;
6459 switch (webkit_web_view_get_load_status(wview)) {
6460 case WEBKIT_LOAD_PROVISIONAL:
6461 /* 0 */
6462 abort_favicon_download(t);
6463 #if GTK_CHECK_VERSION(2, 20, 0)
6464 gtk_widget_show(t->spinner);
6465 gtk_spinner_start(GTK_SPINNER(t->spinner));
6466 #endif
6467 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6469 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6471 /* assume we are a new address */
6472 gdk_color_parse("white", &color);
6473 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6474 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6476 /* take focus if we are visible */
6477 focus_webview(t);
6478 t->focus_wv = 1;
6480 break;
6482 case WEBKIT_LOAD_COMMITTED:
6483 /* 1 */
6484 uri = get_uri(t);
6485 if (uri == NULL)
6486 return;
6487 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6489 if (t->status) {
6490 g_free(t->status);
6491 t->status = NULL;
6493 set_status(t, (char *)uri, XT_STATUS_LOADING);
6495 /* check if js white listing is enabled */
6496 if (enable_cookie_whitelist)
6497 check_and_set_cookie(uri, t);
6498 if (enable_js_whitelist)
6499 check_and_set_js(uri, t);
6501 if (t->styled)
6502 apply_style(t);
6505 /* we know enough to autosave the session */
6506 if (session_autosave) {
6507 a.s = NULL;
6508 save_tabs(t, &a);
6511 show_ca_status(t, uri);
6512 break;
6514 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6515 /* 3 */
6516 break;
6518 case WEBKIT_LOAD_FINISHED:
6519 /* 2 */
6520 uri = get_uri(t);
6521 if (uri == NULL)
6522 return;
6524 if (!strncmp(uri, "http://", strlen("http://")) ||
6525 !strncmp(uri, "https://", strlen("https://")) ||
6526 !strncmp(uri, "file://", strlen("file://"))) {
6527 find.uri = uri;
6528 h = RB_FIND(history_list, &hl, &find);
6529 if (!h) {
6530 title = get_title(t, FALSE);
6531 h = g_malloc(sizeof *h);
6532 h->uri = g_strdup(uri);
6533 h->title = g_strdup(title);
6534 RB_INSERT(history_list, &hl, h);
6535 completion_add_uri(h->uri);
6536 update_history_tabs(NULL);
6540 set_status(t, (char *)uri, XT_STATUS_URI);
6541 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6542 case WEBKIT_LOAD_FAILED:
6543 /* 4 */
6544 #endif
6545 #if GTK_CHECK_VERSION(2, 20, 0)
6546 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6547 gtk_widget_hide(t->spinner);
6548 #endif
6549 default:
6550 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6551 break;
6554 if (t->item)
6555 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6556 else
6557 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6558 webkit_web_view_can_go_back(wview));
6560 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6561 webkit_web_view_can_go_forward(wview));
6564 #if 0
6565 gboolean
6566 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6567 gchar *uri, gpointer web_error,struct tab *t)
6570 * XXX this function is wrong
6571 * it overwrites perfectly good urls with garbage on load errors
6572 * those happen often when popups fail to resolve dns
6574 if (t->tmp_uri)
6575 g_free(t->tmp_uri);
6576 t->tmp_uri = g_strdup(uri);
6577 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6578 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6579 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6580 set_status(t, uri, XT_STATUS_NOTHING);
6582 return (FALSE);
6584 #endif
6586 void
6587 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6589 const gchar *title = NULL, *win_title = NULL;
6591 title = get_title(t, FALSE);
6592 win_title = get_title(t, TRUE);
6593 gtk_label_set_text(GTK_LABEL(t->label), title);
6594 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6595 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6596 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6599 void
6600 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6602 run_script(t, JS_HINTING);
6605 void
6606 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6608 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6609 progress == 100 ? 0 : (double)progress / 100);
6610 if (show_url == 0) {
6611 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6612 progress == 100 ? 0 : (double)progress / 100);
6615 update_statusbar_position(NULL, NULL);
6619 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6620 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6621 WebKitWebPolicyDecision *pd, struct tab *t)
6623 char *uri;
6624 WebKitWebNavigationReason reason;
6625 struct domain *d = NULL;
6627 if (t == NULL) {
6628 show_oops(NULL, "webview_npd_cb invalid parameters");
6629 return (FALSE);
6632 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6633 t->ctrl_click,
6634 webkit_network_request_get_uri(request));
6636 uri = (char *)webkit_network_request_get_uri(request);
6638 /* if this is an xtp url, we don't load anything else */
6639 if (parse_xtp_url(t, uri))
6640 return (TRUE);
6642 if (t->ctrl_click) {
6643 t->ctrl_click = 0;
6644 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6645 webkit_web_policy_decision_ignore(pd);
6646 return (TRUE); /* we made the decission */
6650 * This is a little hairy but it comes down to this:
6651 * when we run in whitelist mode we have to assist the browser in
6652 * opening the URL that it would have opened in a new tab.
6654 reason = webkit_web_navigation_action_get_reason(na);
6655 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6656 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6657 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6658 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6659 load_uri(t, uri);
6660 webkit_web_policy_decision_use(pd);
6661 return (TRUE); /* we made the decision */
6664 return (FALSE);
6667 WebKitWebView *
6668 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6670 struct tab *tt;
6671 struct domain *d = NULL;
6672 const gchar *uri;
6673 WebKitWebView *webview = NULL;
6675 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6676 webkit_web_view_get_uri(wv));
6678 if (tabless) {
6679 /* open in current tab */
6680 webview = t->wv;
6681 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6682 uri = webkit_web_view_get_uri(wv);
6683 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6684 return (NULL);
6686 tt = create_new_tab(NULL, NULL, 1, -1);
6687 webview = tt->wv;
6688 } else if (enable_scripts == 1) {
6689 tt = create_new_tab(NULL, NULL, 1, -1);
6690 webview = tt->wv;
6693 return (webview);
6696 gboolean
6697 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6699 const gchar *uri;
6700 struct domain *d = NULL;
6702 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6704 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6705 uri = webkit_web_view_get_uri(wv);
6706 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6707 return (FALSE);
6709 delete_tab(t);
6710 } else if (enable_scripts == 1)
6711 delete_tab(t);
6713 return (TRUE);
6717 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6719 /* we can not eat the event without throwing gtk off so defer it */
6721 /* catch middle click */
6722 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6723 t->ctrl_click = 1;
6724 goto done;
6727 /* catch ctrl click */
6728 if (e->type == GDK_BUTTON_RELEASE &&
6729 CLEAN(e->state) == GDK_CONTROL_MASK)
6730 t->ctrl_click = 1;
6731 else
6732 t->ctrl_click = 0;
6733 done:
6734 return (XT_CB_PASSTHROUGH);
6738 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6740 struct mime_type *m;
6742 m = find_mime_type(mime_type);
6743 if (m == NULL)
6744 return (1);
6745 if (m->mt_download)
6746 return (1);
6748 switch (fork()) {
6749 case -1:
6750 show_oops(t, "can't fork mime handler");
6751 return (1);
6752 /* NOTREACHED */
6753 case 0:
6754 break;
6755 default:
6756 return (0);
6759 /* child */
6760 execlp(m->mt_action, m->mt_action,
6761 webkit_network_request_get_uri(request), (void *)NULL);
6763 _exit(0);
6765 /* NOTREACHED */
6766 return (0);
6769 const gchar *
6770 get_mime_type(char *file)
6772 const char *mime_type;
6773 GFileInfo *fi;
6774 GFile *gf;
6776 if (g_str_has_prefix(file, "file://"))
6777 file += strlen("file://");
6779 gf = g_file_new_for_path(file);
6780 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6781 NULL, NULL);
6782 mime_type = g_file_info_get_content_type(fi);
6783 g_object_unref(fi);
6784 g_object_unref(gf);
6786 return (mime_type);
6790 run_download_mimehandler(char *mime_type, char *file)
6792 struct mime_type *m;
6794 m = find_mime_type(mime_type);
6795 if (m == NULL)
6796 return (1);
6798 switch (fork()) {
6799 case -1:
6800 show_oops(NULL, "can't fork download mime handler");
6801 return (1);
6802 /* NOTREACHED */
6803 case 0:
6804 break;
6805 default:
6806 return (0);
6809 /* child */
6810 if (g_str_has_prefix(file, "file://"))
6811 file += strlen("file://");
6812 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6814 _exit(0);
6816 /* NOTREACHED */
6817 return (0);
6820 void
6821 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6822 WebKitWebView *wv)
6824 WebKitDownloadStatus status;
6825 const gchar *file = NULL, *mime = NULL;
6827 if (download == NULL)
6828 return;
6829 status = webkit_download_get_status(download);
6830 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6831 return;
6833 file = webkit_download_get_destination_uri(download);
6834 if (file == NULL)
6835 return;
6836 mime = get_mime_type((char *)file);
6837 if (mime == NULL)
6838 return;
6840 run_download_mimehandler((char *)mime, (char *)file);
6844 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6845 WebKitNetworkRequest *request, char *mime_type,
6846 WebKitWebPolicyDecision *decision, struct tab *t)
6848 if (t == NULL) {
6849 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6850 return (FALSE);
6853 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6854 t->tab_id, mime_type);
6856 if (run_mimehandler(t, mime_type, request) == 0) {
6857 webkit_web_policy_decision_ignore(decision);
6858 focus_webview(t);
6859 return (TRUE);
6862 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6863 webkit_web_policy_decision_download(decision);
6864 return (TRUE);
6867 return (FALSE);
6871 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6872 struct tab *t)
6874 struct stat sb;
6875 const gchar *suggested_name;
6876 gchar *filename = NULL;
6877 char *uri = NULL;
6878 struct download *download_entry;
6879 int i, ret = TRUE;
6881 if (wk_download == NULL || t == NULL) {
6882 show_oops(NULL, "%s invalid parameters", __func__);
6883 return (FALSE);
6886 suggested_name = webkit_download_get_suggested_filename(wk_download);
6887 if (suggested_name == NULL)
6888 return (FALSE); /* abort download */
6890 i = 0;
6891 do {
6892 if (filename) {
6893 g_free(filename);
6894 filename = NULL;
6896 if (i) {
6897 g_free(uri);
6898 uri = NULL;
6899 filename = g_strdup_printf("%d%s", i, suggested_name);
6901 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6902 filename : suggested_name);
6903 i++;
6904 } while (!stat(uri + strlen("file://"), &sb));
6906 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6907 "local %s\n", __func__, t->tab_id, filename, uri);
6909 webkit_download_set_destination_uri(wk_download, uri);
6911 if (webkit_download_get_status(wk_download) ==
6912 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6913 show_oops(t, "%s: download failed to start", __func__);
6914 ret = FALSE;
6915 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6916 } else {
6917 /* connect "download first" mime handler */
6918 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6919 G_CALLBACK(download_status_changed_cb), NULL);
6921 download_entry = g_malloc(sizeof(struct download));
6922 download_entry->download = wk_download;
6923 download_entry->tab = t;
6924 download_entry->id = next_download_id++;
6925 RB_INSERT(download_list, &downloads, download_entry);
6926 /* get from history */
6927 g_object_ref(wk_download);
6928 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6929 show_oops(t, "Download of '%s' started...",
6930 basename((char *)webkit_download_get_destination_uri(wk_download)));
6933 if (uri)
6934 g_free(uri);
6936 if (filename)
6937 g_free(filename);
6939 /* sync other download manager tabs */
6940 update_download_tabs(NULL);
6943 * NOTE: never redirect/render the current tab before this
6944 * function returns. This will cause the download to never start.
6946 return (ret); /* start download */
6949 void
6950 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6952 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6954 if (t == NULL) {
6955 show_oops(NULL, "webview_hover_cb");
6956 return;
6959 if (uri)
6960 set_status(t, uri, XT_STATUS_LINK);
6961 else {
6962 if (t->status)
6963 set_status(t, t->status, XT_STATUS_NOTHING);
6968 mark(struct tab *t, struct karg *arg)
6970 char mark;
6971 int index;
6973 mark = arg->s[1];
6974 if ((index = marktoindex(mark)) == -1)
6975 return -1;
6977 if (arg->i == XT_MARK_SET)
6978 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6979 else if (arg->i == XT_MARK_GOTO) {
6980 if (t->mark[index] == XT_INVALID_MARK) {
6981 show_oops(t, "mark '%c' does not exist", mark);
6982 return -1;
6984 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6985 something changes the document size */
6986 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6989 return 0;
6992 void
6993 marks_clear(struct tab *t)
6995 int i;
6997 for (i = 0; i < LENGTH(t->mark); i++)
6998 t->mark[i] = XT_INVALID_MARK;
7002 qmarks_load(void)
7004 char file[PATH_MAX];
7005 char *line = NULL, *p, mark;
7006 int index, i;
7007 FILE *f;
7008 size_t linelen;
7010 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7011 if ((f = fopen(file, "r+")) == NULL) {
7012 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7013 return (1);
7016 for (i = 1; ; i++) {
7017 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7018 if (feof(f) || ferror(f))
7019 break;
7020 if (strlen(line) == 0 || line[0] == '#') {
7021 free(line);
7022 line = NULL;
7023 continue;
7026 p = strtok(line, " \t");
7028 if (p == NULL || strlen(p) != 1 ||
7029 (index = marktoindex(*p)) == -1) {
7030 warnx("corrupt quickmarks file, line %d", i);
7031 break;
7034 mark = *p;
7035 p = strtok(NULL, " \t");
7036 if (qmarks[index] != NULL)
7037 g_free(qmarks[index]);
7038 qmarks[index] = g_strdup(p);
7041 fclose(f);
7043 return (0);
7047 qmarks_save(void)
7049 char file[PATH_MAX];
7050 int i;
7051 FILE *f;
7053 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7054 if ((f = fopen(file, "r+")) == NULL) {
7055 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7056 return (1);
7059 for (i = 0; i < XT_NOMARKS; i++)
7060 if (qmarks[i] != NULL)
7061 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7063 fclose(f);
7065 return (0);
7069 qmark(struct tab *t, struct karg *arg)
7071 char mark;
7072 int index;
7074 mark = arg->s[strlen(arg->s)-1];
7075 index = marktoindex(mark);
7076 if (index == -1)
7077 return (-1);
7079 switch (arg->i) {
7080 case XT_QMARK_SET:
7081 if (qmarks[index] != NULL)
7082 g_free(qmarks[index]);
7084 qmarks_load(); /* sync if multiple instances */
7085 qmarks[index] = g_strdup(get_uri(t));
7086 qmarks_save();
7087 break;
7088 case XT_QMARK_OPEN:
7089 if (qmarks[index] != NULL)
7090 load_uri(t, qmarks[index]);
7091 else {
7092 show_oops(t, "quickmark \"%c\" does not exist",
7093 mark);
7094 return (-1);
7096 break;
7097 case XT_QMARK_TAB:
7098 if (qmarks[index] != NULL)
7099 create_new_tab(qmarks[index], NULL, 1, -1);
7100 else {
7101 show_oops(t, "quickmark \"%c\" does not exist",
7102 mark);
7103 return (-1);
7105 break;
7108 return (0);
7112 go_up(struct tab *t, struct karg *args)
7114 int levels;
7115 char *uri;
7116 char *tmp;
7118 levels = atoi(args->s);
7119 if (levels == 0)
7120 levels = 1;
7122 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7123 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7124 return 1;
7125 tmp += strlen(XT_PROTO_DELIM);
7127 /* if an uri starts with a slash, leave it alone (for file:///) */
7128 if (tmp[0] == '/')
7129 tmp++;
7131 while (levels--) {
7132 char *p;
7134 p = strrchr(tmp, '/');
7135 if (p != NULL)
7136 *p = '\0';
7137 else
7138 break;
7141 load_uri(t, uri);
7142 g_free(uri);
7144 return (0);
7148 gototab(struct tab *t, struct karg *args)
7150 int tab;
7151 struct karg arg = {0, NULL, -1};
7153 tab = atoi(args->s);
7155 arg.i = XT_TAB_NEXT;
7156 arg.precount = tab;
7158 movetab(t, &arg);
7160 return (0);
7164 zoom_amount(struct tab *t, struct karg *arg)
7166 struct karg narg = {0, NULL, -1};
7168 narg.i = atoi(arg->s);
7169 resizetab(t, &narg);
7171 return 0;
7175 flip_colon(struct tab *t, struct karg *arg)
7177 struct karg narg = {0, NULL, -1};
7178 char *p;
7180 if (t == NULL || arg == NULL)
7181 return (1);
7183 p = strstr(arg->s, ":");
7184 if (p == NULL)
7185 return (1);
7186 *p = '\0';
7188 narg.i = ':';
7189 narg.s = arg->s;
7190 command(t, &narg);
7192 return (0);
7195 /* buffer commands receive the regex that triggered them in arg.s */
7196 char bcmd[XT_BUFCMD_SZ];
7197 struct buffercmd {
7198 char *regex;
7199 int precount;
7200 #define XT_PRE_NO (0)
7201 #define XT_PRE_YES (1)
7202 #define XT_PRE_MAYBE (2)
7203 char *cmd;
7204 int (*func)(struct tab *, struct karg *);
7205 int arg;
7206 regex_t cregex;
7207 } buffercmds[] = {
7208 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7209 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7210 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7211 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7212 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7213 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7214 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7215 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7216 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7217 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7218 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7219 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7220 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7221 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7222 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7223 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7224 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7225 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7228 void
7229 buffercmd_init(void)
7231 int i;
7233 for (i = 0; i < LENGTH(buffercmds); i++)
7234 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7235 REG_EXTENDED);
7238 void
7239 buffercmd_abort(struct tab *t)
7241 int i;
7243 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7244 for (i = 0; i < LENGTH(bcmd); i++)
7245 bcmd[i] = '\0';
7247 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7248 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7251 void
7252 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7254 struct karg arg = {0, NULL, -1};
7256 arg.i = cmd->arg;
7257 arg.s = g_strdup(bcmd);
7259 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7260 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7261 cmd->func(t, &arg);
7263 if (arg.s)
7264 g_free(arg.s);
7266 buffercmd_abort(t);
7269 gboolean
7270 buffercmd_addkey(struct tab *t, guint keyval)
7272 int i, c, match ;
7273 char s[XT_BUFCMD_SZ];
7275 if (keyval == GDK_Escape) {
7276 buffercmd_abort(t);
7277 return (XT_CB_HANDLED);
7280 /* key with modifier or non-ascii character */
7281 if (!isascii(keyval))
7282 return (XT_CB_PASSTHROUGH);
7284 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7285 "to buffer \"%s\"\n", keyval, bcmd);
7287 for (i = 0; i < LENGTH(bcmd); i++)
7288 if (bcmd[i] == '\0') {
7289 bcmd[i] = keyval;
7290 break;
7293 /* buffer full, ignore input */
7294 if (i >= LENGTH(bcmd) -1) {
7295 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7296 buffercmd_abort(t);
7297 return (XT_CB_HANDLED);
7300 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7302 /* find exact match */
7303 for (i = 0; i < LENGTH(buffercmds); i++)
7304 if (regexec(&buffercmds[i].cregex, bcmd,
7305 (size_t) 0, NULL, 0) == 0) {
7306 buffercmd_execute(t, &buffercmds[i]);
7307 goto done;
7310 /* find non exact matches to see if we need to abort ot not */
7311 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7312 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7313 c = -1;
7314 s[0] = '\0';
7315 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7316 if (isdigit(bcmd[0])) {
7317 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7318 continue;
7319 } else {
7320 c = 0;
7321 if (sscanf(bcmd, "%s", s) == 0)
7322 continue;
7324 } else if (buffercmds[i].precount == XT_PRE_YES) {
7325 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7326 continue;
7327 } else {
7328 if (sscanf(bcmd, "%s", s) == 0)
7329 continue;
7331 if (c == -1 && buffercmds[i].precount)
7332 continue;
7333 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7334 match++;
7336 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7337 i, match, buffercmds[i].cmd, c, s);
7339 if (match == 0) {
7340 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7341 buffercmd_abort(t);
7344 done:
7345 return (XT_CB_HANDLED);
7348 gboolean
7349 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7351 struct key_binding *k;
7353 /* handle keybindings if buffercmd is empty.
7354 if not empty, allow commands like C-n */
7355 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7356 TAILQ_FOREACH(k, &kbl, entry)
7357 if (e->keyval == k->key
7358 && (entry ? k->use_in_entry : 1)) {
7359 if (k->mask == 0) {
7360 if ((e->state & (CTRL | MOD1)) == 0)
7361 return (cmd_execute(t, k->cmd));
7362 } else if ((e->state & k->mask) == k->mask) {
7363 return (cmd_execute(t, k->cmd));
7367 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7368 return buffercmd_addkey(t, e->keyval);
7370 return (XT_CB_PASSTHROUGH);
7374 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7376 char s[2], buf[128];
7377 const char *errstr = NULL;
7379 /* don't use w directly; use t->whatever instead */
7381 if (t == NULL) {
7382 show_oops(NULL, "wv_keypress_after_cb");
7383 return (XT_CB_PASSTHROUGH);
7386 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7387 e->keyval, e->state, t);
7389 if (t->hints_on) {
7390 /* ESC */
7391 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7392 disable_hints(t);
7393 return (XT_CB_HANDLED);
7396 /* RETURN */
7397 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7398 if (errstr) {
7399 /* we have a string */
7400 } else {
7401 /* we have a number */
7402 snprintf(buf, sizeof buf,
7403 "vimprobable_fire(%s)", t->hint_num);
7404 run_script(t, buf);
7406 disable_hints(t);
7409 /* BACKSPACE */
7410 /* XXX unfuck this */
7411 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7412 if (t->hint_mode == XT_HINT_NUMERICAL) {
7413 /* last input was numerical */
7414 int l;
7415 l = strlen(t->hint_num);
7416 if (l > 0) {
7417 l--;
7418 if (l == 0) {
7419 disable_hints(t);
7420 enable_hints(t);
7421 } else {
7422 t->hint_num[l] = '\0';
7423 goto num;
7426 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7427 /* last input was alphanumerical */
7428 int l;
7429 l = strlen(t->hint_buf);
7430 if (l > 0) {
7431 l--;
7432 if (l == 0) {
7433 disable_hints(t);
7434 enable_hints(t);
7435 } else {
7436 t->hint_buf[l] = '\0';
7437 goto anum;
7440 } else {
7441 /* bogus */
7442 disable_hints(t);
7446 /* numerical input */
7447 if (CLEAN(e->state) == 0 &&
7448 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7449 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7450 snprintf(s, sizeof s, "%c", e->keyval);
7451 strlcat(t->hint_num, s, sizeof t->hint_num);
7452 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7453 t->hint_num);
7454 num:
7455 if (errstr) {
7456 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7457 "invalid link number\n");
7458 disable_hints(t);
7459 } else {
7460 snprintf(buf, sizeof buf,
7461 "vimprobable_update_hints(%s)",
7462 t->hint_num);
7463 t->hint_mode = XT_HINT_NUMERICAL;
7464 run_script(t, buf);
7467 /* empty the counter buffer */
7468 bzero(t->hint_buf, sizeof t->hint_buf);
7469 return (XT_CB_HANDLED);
7472 /* alphanumerical input */
7473 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7474 e->keyval <= GDK_z) ||
7475 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7476 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7477 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7478 e->keyval <= GDK_9) ||
7479 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7480 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7481 snprintf(s, sizeof s, "%c", e->keyval);
7482 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7483 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7484 " %s\n", t->hint_buf);
7485 anum:
7486 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7487 run_script(t, buf);
7489 snprintf(buf, sizeof buf,
7490 "vimprobable_show_hints('%s')", t->hint_buf);
7491 t->hint_mode = XT_HINT_ALPHANUM;
7492 run_script(t, buf);
7494 /* empty the counter buffer */
7495 bzero(t->hint_num, sizeof t->hint_num);
7496 return (XT_CB_HANDLED);
7499 return (XT_CB_HANDLED);
7500 } else {
7501 /* prefix input*/
7502 snprintf(s, sizeof s, "%c", e->keyval);
7503 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7504 cmd_prefix = 10 * cmd_prefix + atoi(s);
7507 return (handle_keypress(t, e, 0));
7511 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7513 hide_oops(t);
7515 /* Hide buffers, if they are visible, with escape. */
7516 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7517 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7518 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7519 hide_buffers(t);
7520 return (XT_CB_HANDLED);
7523 return (XT_CB_PASSTHROUGH);
7526 gboolean
7527 search_continue(struct tab *t)
7529 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7530 gboolean rv = FALSE;
7532 if (c[0] == ':')
7533 goto done;
7534 if (strlen(c) == 1) {
7535 webkit_web_view_unmark_text_matches(t->wv);
7536 goto done;
7539 if (c[0] == '/')
7540 t->search_forward = TRUE;
7541 else if (c[0] == '?')
7542 t->search_forward = FALSE;
7543 else
7544 goto done;
7546 rv = TRUE;
7547 done:
7548 return (rv);
7551 gboolean
7552 search_cb(struct tab *t)
7554 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7555 GdkColor color;
7557 if (search_continue(t) == FALSE)
7558 goto done;
7560 /* search */
7561 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7562 TRUE) == FALSE) {
7563 /* not found, mark red */
7564 gdk_color_parse(XT_COLOR_RED, &color);
7565 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7566 /* unmark and remove selection */
7567 webkit_web_view_unmark_text_matches(t->wv);
7568 /* my kingdom for a way to unselect text in webview */
7569 } else {
7570 /* found, highlight all */
7571 webkit_web_view_unmark_text_matches(t->wv);
7572 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7573 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7574 gdk_color_parse(XT_COLOR_WHITE, &color);
7575 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7577 done:
7578 t->search_id = 0;
7579 return (FALSE);
7583 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7585 const gchar *c = gtk_entry_get_text(w);
7587 if (t == NULL) {
7588 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7589 return (XT_CB_PASSTHROUGH);
7592 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7593 e->keyval, e->state, t);
7595 if (search_continue(t) == FALSE)
7596 goto done;
7598 /* if search length is > 4 then no longer play timeout games */
7599 if (strlen(c) > 4) {
7600 if (t->search_id) {
7601 g_source_remove(t->search_id);
7602 t->search_id = 0;
7604 search_cb(t);
7605 goto done;
7608 /* reestablish a new timer if the user types fast */
7609 if (t->search_id)
7610 g_source_remove(t->search_id);
7611 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7613 done:
7614 return (XT_CB_PASSTHROUGH);
7617 gboolean
7618 match_uri(const gchar *uri, const gchar *key) {
7619 gchar *voffset;
7620 size_t len;
7621 gboolean match = FALSE;
7623 len = strlen(key);
7625 if (!strncmp(key, uri, len))
7626 match = TRUE;
7627 else {
7628 voffset = strstr(uri, "/") + 2;
7629 if (!strncmp(key, voffset, len))
7630 match = TRUE;
7631 else if (g_str_has_prefix(voffset, "www.")) {
7632 voffset = voffset + strlen("www.");
7633 if (!strncmp(key, voffset, len))
7634 match = TRUE;
7638 return (match);
7641 void
7642 cmd_getlist(int id, char *key)
7644 int i, dep, c = 0;
7645 struct history *h;
7647 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7648 RB_FOREACH_REVERSE(h, history_list, &hl)
7649 if (match_uri(h->uri, key)) {
7650 cmd_status.list[c] = (char *)h->uri;
7651 if (++c > 255)
7652 break;
7655 cmd_status.len = c;
7656 return;
7659 dep = (id == -1) ? 0 : cmds[id].level + 1;
7661 for (i = id + 1; i < LENGTH(cmds); i++) {
7662 if (cmds[i].level < dep)
7663 break;
7664 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7665 strlen(key)))
7666 cmd_status.list[c++] = cmds[i].cmd;
7670 cmd_status.len = c;
7673 char *
7674 cmd_getnext(int dir)
7676 cmd_status.index += dir;
7678 if (cmd_status.index < 0)
7679 cmd_status.index = cmd_status.len - 1;
7680 else if (cmd_status.index >= cmd_status.len)
7681 cmd_status.index = 0;
7683 return cmd_status.list[cmd_status.index];
7687 cmd_tokenize(char *s, char *tokens[])
7689 int i = 0;
7690 char *tok, *last;
7691 size_t len = strlen(s);
7692 bool blank;
7694 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7695 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7696 tok = strtok_r(NULL, " ", &last), i++)
7697 tokens[i] = tok;
7699 if (blank && i < 3)
7700 tokens[i++] = "";
7702 return (i);
7705 void
7706 cmd_complete(struct tab *t, char *str, int dir)
7708 GtkEntry *w = GTK_ENTRY(t->cmd);
7709 int i, j, levels, c = 0, dep = 0, parent = -1;
7710 int matchcount = 0;
7711 char *tok, *match, *s = g_strdup(str);
7712 char *tokens[3];
7713 char res[XT_MAX_URL_LENGTH + 32] = ":";
7714 char *sc = s;
7716 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7718 /* copy prefix*/
7719 for (i = 0; isdigit(s[i]); i++)
7720 res[i + 1] = s[i];
7722 for (; isspace(s[i]); i++)
7723 res[i + 1] = s[i];
7725 s += i;
7727 levels = cmd_tokenize(s, tokens);
7729 for (i = 0; i < levels - 1; i++) {
7730 tok = tokens[i];
7731 matchcount = 0;
7732 for (j = c; j < LENGTH(cmds); j++) {
7733 if (cmds[j].level < dep)
7734 break;
7735 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7736 strlen(tok))) {
7737 matchcount++;
7738 c = j + 1;
7739 if (strlen(tok) == strlen(cmds[j].cmd)) {
7740 matchcount = 1;
7741 break;
7746 if (matchcount == 1) {
7747 strlcat(res, tok, sizeof res);
7748 strlcat(res, " ", sizeof res);
7749 dep++;
7750 } else {
7751 g_free(sc);
7752 return;
7755 parent = c - 1;
7758 if (cmd_status.index == -1)
7759 cmd_getlist(parent, tokens[i]);
7761 if (cmd_status.len > 0) {
7762 match = cmd_getnext(dir);
7763 strlcat(res, match, sizeof res);
7764 gtk_entry_set_text(w, res);
7765 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7768 g_free(sc);
7771 gboolean
7772 cmd_execute(struct tab *t, char *str)
7774 struct cmd *cmd = NULL;
7775 char *tok, *last, *s = g_strdup(str), *sc;
7776 char prefixstr[4];
7777 int j, len, c = 0, dep = 0, matchcount = 0;
7778 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7779 struct karg arg = {0, NULL, -1};
7781 sc = s;
7783 /* copy prefix*/
7784 for (j = 0; j<3 && isdigit(s[j]); j++)
7785 prefixstr[j]=s[j];
7787 prefixstr[j]='\0';
7789 s += j;
7790 while (isspace(s[0]))
7791 s++;
7793 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7794 prefix = atoi(prefixstr);
7795 else
7796 s = sc;
7798 for (tok = strtok_r(s, " ", &last); tok;
7799 tok = strtok_r(NULL, " ", &last)) {
7800 matchcount = 0;
7801 for (j = c; j < LENGTH(cmds); j++) {
7802 if (cmds[j].level < dep)
7803 break;
7804 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7805 strlen(tok);
7806 if (cmds[j].level == dep &&
7807 !strncmp(tok, cmds[j].cmd, len)) {
7808 matchcount++;
7809 c = j + 1;
7810 cmd = &cmds[j];
7811 if (len == strlen(cmds[j].cmd)) {
7812 matchcount = 1;
7813 break;
7817 if (matchcount == 1) {
7818 if (cmd->type > 0)
7819 goto execute_cmd;
7820 dep++;
7821 } else {
7822 show_oops(t, "Invalid command: %s", str);
7823 goto done;
7826 execute_cmd:
7827 arg.i = cmd->arg;
7829 if (prefix != -1)
7830 arg.precount = prefix;
7831 else if (cmd_prefix > 0)
7832 arg.precount = cmd_prefix;
7834 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7835 show_oops(t, "No prefix allowed: %s", str);
7836 goto done;
7838 if (cmd->type > 1)
7839 arg.s = last ? g_strdup(last) : g_strdup("");
7840 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7841 arg.precount = atoi(arg.s);
7842 if (arg.precount <= 0) {
7843 if (arg.s[0] == '0')
7844 show_oops(t, "Zero count");
7845 else
7846 show_oops(t, "Trailing characters");
7847 goto done;
7851 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7852 __func__, arg.precount, arg.s);
7854 cmd->func(t, &arg);
7856 rv = XT_CB_HANDLED;
7857 done:
7858 if (j > 0)
7859 cmd_prefix = 0;
7860 g_free(sc);
7861 if (arg.s)
7862 g_free(arg.s);
7864 return (rv);
7868 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7870 if (t == NULL) {
7871 show_oops(NULL, "entry_key_cb invalid parameters");
7872 return (XT_CB_PASSTHROUGH);
7875 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7876 e->keyval, e->state, t);
7878 hide_oops(t);
7880 if (e->keyval == GDK_Escape) {
7881 /* don't use focus_webview(t) because we want to type :cmds */
7882 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7885 return (handle_keypress(t, e, 1));
7889 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7891 int rv = XT_CB_HANDLED;
7892 const gchar *c = gtk_entry_get_text(w);
7894 if (t == NULL) {
7895 show_oops(NULL, "cmd_keypress_cb parameters");
7896 return (XT_CB_PASSTHROUGH);
7899 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7900 e->keyval, e->state, t);
7902 /* sanity */
7903 if (c == NULL)
7904 e->keyval = GDK_Escape;
7905 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7906 e->keyval = GDK_Escape;
7908 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7909 e->keyval != GDK_ISO_Left_Tab)
7910 cmd_status.index = -1;
7912 switch (e->keyval) {
7913 case GDK_Tab:
7914 if (c[0] == ':')
7915 cmd_complete(t, (char *)&c[1], 1);
7916 goto done;
7917 case GDK_ISO_Left_Tab:
7918 if (c[0] == ':')
7919 cmd_complete(t, (char *)&c[1], -1);
7921 goto done;
7922 case GDK_BackSpace:
7923 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7924 break;
7925 /* FALLTHROUGH */
7926 case GDK_Escape:
7927 hide_cmd(t);
7928 focus_webview(t);
7930 /* cancel search */
7931 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7932 webkit_web_view_unmark_text_matches(t->wv);
7933 goto done;
7936 rv = XT_CB_PASSTHROUGH;
7937 done:
7938 return (rv);
7942 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7944 if (t == NULL) {
7945 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7946 return (XT_CB_PASSTHROUGH);
7948 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7950 hide_cmd(t);
7951 hide_oops(t);
7953 if (show_url == 0 || t->focus_wv)
7954 focus_webview(t);
7955 else
7956 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7958 return (XT_CB_PASSTHROUGH);
7961 void
7962 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7964 char *s;
7965 const gchar *c = gtk_entry_get_text(entry);
7967 if (t == NULL) {
7968 show_oops(NULL, "cmd_activate_cb invalid parameters");
7969 return;
7972 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7974 hide_cmd(t);
7976 /* sanity */
7977 if (c == NULL)
7978 goto done;
7979 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7980 goto done;
7981 if (strlen(c) < 2)
7982 goto done;
7983 s = (char *)&c[1];
7985 if (c[0] == '/' || c[0] == '?') {
7986 /* see if there is a timer pending */
7987 if (t->search_id) {
7988 g_source_remove(t->search_id);
7989 t->search_id = 0;
7990 search_cb(t);
7993 if (t->search_text) {
7994 g_free(t->search_text);
7995 t->search_text = NULL;
7998 t->search_text = g_strdup(s);
7999 if (global_search)
8000 g_free(global_search);
8001 global_search = g_strdup(s);
8002 t->search_forward = c[0] == '/';
8004 goto done;
8007 cmd_execute(t, s);
8009 done:
8010 return;
8013 void
8014 backward_cb(GtkWidget *w, struct tab *t)
8016 struct karg a;
8018 if (t == NULL) {
8019 show_oops(NULL, "backward_cb invalid parameters");
8020 return;
8023 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8025 a.i = XT_NAV_BACK;
8026 navaction(t, &a);
8029 void
8030 forward_cb(GtkWidget *w, struct tab *t)
8032 struct karg a;
8034 if (t == NULL) {
8035 show_oops(NULL, "forward_cb invalid parameters");
8036 return;
8039 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8041 a.i = XT_NAV_FORWARD;
8042 navaction(t, &a);
8045 void
8046 home_cb(GtkWidget *w, struct tab *t)
8048 if (t == NULL) {
8049 show_oops(NULL, "home_cb invalid parameters");
8050 return;
8053 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8055 load_uri(t, home);
8058 void
8059 stop_cb(GtkWidget *w, struct tab *t)
8061 WebKitWebFrame *frame;
8063 if (t == NULL) {
8064 show_oops(NULL, "stop_cb invalid parameters");
8065 return;
8068 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8070 frame = webkit_web_view_get_main_frame(t->wv);
8071 if (frame == NULL) {
8072 show_oops(t, "stop_cb: no frame");
8073 return;
8076 webkit_web_frame_stop_loading(frame);
8077 abort_favicon_download(t);
8080 void
8081 setup_webkit(struct tab *t)
8083 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8084 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8085 FALSE, (char *)NULL);
8086 else
8087 warnx("webkit does not have \"enable-dns-prefetching\" property");
8088 g_object_set(G_OBJECT(t->settings),
8089 "user-agent", t->user_agent, (char *)NULL);
8090 g_object_set(G_OBJECT(t->settings),
8091 "enable-scripts", enable_scripts, (char *)NULL);
8092 g_object_set(G_OBJECT(t->settings),
8093 "enable-plugins", enable_plugins, (char *)NULL);
8094 g_object_set(G_OBJECT(t->settings),
8095 "javascript-can-open-windows-automatically", enable_scripts,
8096 (char *)NULL);
8097 g_object_set(G_OBJECT(t->settings),
8098 "enable-html5-database", FALSE, (char *)NULL);
8099 g_object_set(G_OBJECT(t->settings),
8100 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8101 g_object_set(G_OBJECT(t->settings),
8102 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8103 g_object_set(G_OBJECT(t->settings),
8104 "spell_checking_languages", spell_check_languages, (char *)NULL);
8105 g_object_set(G_OBJECT(t->wv),
8106 "full-content-zoom", TRUE, (char *)NULL);
8108 webkit_web_view_set_settings(t->wv, t->settings);
8111 gboolean
8112 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8114 struct tab *ti, *t = NULL;
8115 gdouble view_size, value, max;
8116 gchar *position;
8118 TAILQ_FOREACH(ti, &tabs, entry)
8119 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8120 t = ti;
8121 break;
8124 if (t == NULL)
8125 return FALSE;
8127 if (adjustment == NULL)
8128 adjustment = gtk_scrolled_window_get_vadjustment(
8129 GTK_SCROLLED_WINDOW(t->browser_win));
8131 view_size = gtk_adjustment_get_page_size(adjustment);
8132 value = gtk_adjustment_get_value(adjustment);
8133 max = gtk_adjustment_get_upper(adjustment) - view_size;
8135 if (max == 0)
8136 position = g_strdup("All");
8137 else if (value == max)
8138 position = g_strdup("Bot");
8139 else if (value == 0)
8140 position = g_strdup("Top");
8141 else
8142 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8144 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8145 g_free(position);
8147 return (TRUE);
8150 GtkWidget *
8151 create_browser(struct tab *t)
8153 GtkWidget *w;
8154 gchar *strval;
8155 GtkAdjustment *adjustment;
8157 if (t == NULL) {
8158 show_oops(NULL, "create_browser invalid parameters");
8159 return (NULL);
8162 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8163 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8164 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8165 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8167 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8168 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8169 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8171 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8172 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8174 /* set defaults */
8175 t->settings = webkit_web_settings_new();
8177 if (user_agent == NULL) {
8178 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8179 (char *)NULL);
8180 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8181 g_free(strval);
8182 } else
8183 t->user_agent = g_strdup(user_agent);
8185 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8187 adjustment =
8188 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8189 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8190 G_CALLBACK(update_statusbar_position), NULL);
8192 setup_webkit(t);
8194 return (w);
8197 GtkWidget *
8198 create_window(void)
8200 GtkWidget *w;
8202 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8203 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8204 gtk_widget_set_name(w, "xxxterm");
8205 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8206 g_signal_connect(G_OBJECT(w), "delete_event",
8207 G_CALLBACK (gtk_main_quit), NULL);
8209 return (w);
8212 GtkWidget *
8213 create_kiosk_toolbar(struct tab *t)
8215 GtkWidget *toolbar = NULL, *b;
8217 b = gtk_hbox_new(FALSE, 0);
8218 toolbar = b;
8219 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8221 /* backward button */
8222 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8223 gtk_widget_set_sensitive(t->backward, FALSE);
8224 g_signal_connect(G_OBJECT(t->backward), "clicked",
8225 G_CALLBACK(backward_cb), t);
8226 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8228 /* forward button */
8229 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8230 gtk_widget_set_sensitive(t->forward, FALSE);
8231 g_signal_connect(G_OBJECT(t->forward), "clicked",
8232 G_CALLBACK(forward_cb), t);
8233 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8235 /* home button */
8236 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8237 gtk_widget_set_sensitive(t->gohome, true);
8238 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8239 G_CALLBACK(home_cb), t);
8240 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8242 /* create widgets but don't use them */
8243 t->uri_entry = gtk_entry_new();
8244 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8245 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8246 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8248 return (toolbar);
8251 GtkWidget *
8252 create_toolbar(struct tab *t)
8254 GtkWidget *toolbar = NULL, *b, *eb1;
8256 b = gtk_hbox_new(FALSE, 0);
8257 toolbar = b;
8258 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8260 /* backward button */
8261 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8262 gtk_widget_set_sensitive(t->backward, FALSE);
8263 g_signal_connect(G_OBJECT(t->backward), "clicked",
8264 G_CALLBACK(backward_cb), t);
8265 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8267 /* forward button */
8268 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8269 gtk_widget_set_sensitive(t->forward, FALSE);
8270 g_signal_connect(G_OBJECT(t->forward), "clicked",
8271 G_CALLBACK(forward_cb), t);
8272 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8273 FALSE, 0);
8275 /* stop button */
8276 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8277 gtk_widget_set_sensitive(t->stop, FALSE);
8278 g_signal_connect(G_OBJECT(t->stop), "clicked",
8279 G_CALLBACK(stop_cb), t);
8280 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8281 FALSE, 0);
8283 /* JS button */
8284 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8285 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8286 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8287 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8288 G_CALLBACK(js_toggle_cb), t);
8289 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8291 t->uri_entry = gtk_entry_new();
8292 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8293 G_CALLBACK(activate_uri_entry_cb), t);
8294 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8295 G_CALLBACK(entry_key_cb), t);
8296 completion_add(t);
8297 eb1 = gtk_hbox_new(FALSE, 0);
8298 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8299 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8300 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8302 /* search entry */
8303 if (search_string) {
8304 GtkWidget *eb2;
8305 t->search_entry = gtk_entry_new();
8306 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8307 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8308 G_CALLBACK(activate_search_entry_cb), t);
8309 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8310 G_CALLBACK(entry_key_cb), t);
8311 gtk_widget_set_size_request(t->search_entry, -1, -1);
8312 eb2 = gtk_hbox_new(FALSE, 0);
8313 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8314 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8316 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8319 return (toolbar);
8322 GtkWidget *
8323 create_buffers(struct tab *t)
8325 GtkCellRenderer *renderer;
8326 GtkWidget *view;
8328 view = gtk_tree_view_new();
8330 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8332 renderer = gtk_cell_renderer_text_new();
8333 gtk_tree_view_insert_column_with_attributes
8334 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8336 renderer = gtk_cell_renderer_text_new();
8337 gtk_tree_view_insert_column_with_attributes
8338 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8339 NULL);
8341 gtk_tree_view_set_model
8342 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8344 return view;
8347 void
8348 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8349 GtkTreeViewColumn *col, struct tab *t)
8351 GtkTreeIter iter;
8352 guint id;
8354 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8356 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8357 path)) {
8358 gtk_tree_model_get
8359 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8360 set_current_tab(id - 1);
8363 hide_buffers(t);
8366 /* after tab reordering/creation/removal */
8367 void
8368 recalc_tabs(void)
8370 struct tab *t;
8371 int maxid = 0;
8373 TAILQ_FOREACH(t, &tabs, entry) {
8374 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8375 if (t->tab_id > maxid)
8376 maxid = t->tab_id;
8378 gtk_widget_show(t->tab_elems.sep);
8381 TAILQ_FOREACH(t, &tabs, entry) {
8382 if (t->tab_id == maxid) {
8383 gtk_widget_hide(t->tab_elems.sep);
8384 break;
8389 /* after active tab change */
8390 void
8391 recolor_compact_tabs(void)
8393 struct tab *t;
8394 int curid = 0;
8395 GdkColor color;
8397 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8398 TAILQ_FOREACH(t, &tabs, entry)
8399 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8400 &color);
8402 curid = gtk_notebook_get_current_page(notebook);
8403 TAILQ_FOREACH(t, &tabs, entry)
8404 if (t->tab_id == curid) {
8405 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8406 gtk_widget_modify_fg(t->tab_elems.label,
8407 GTK_STATE_NORMAL, &color);
8408 break;
8412 void
8413 set_current_tab(int page_num)
8415 buffercmd_abort(get_current_tab());
8416 gtk_notebook_set_current_page(notebook, page_num);
8417 recolor_compact_tabs();
8421 undo_close_tab_save(struct tab *t)
8423 int m, n;
8424 const gchar *uri;
8425 struct undo *u1, *u2;
8426 GList *items;
8427 WebKitWebHistoryItem *item;
8429 if ((uri = get_uri(t)) == NULL)
8430 return (1);
8432 u1 = g_malloc0(sizeof(struct undo));
8433 u1->uri = g_strdup(uri);
8435 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8437 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8438 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8439 u1->back = n;
8441 /* forward history */
8442 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8444 while (items) {
8445 item = items->data;
8446 u1->history = g_list_prepend(u1->history,
8447 webkit_web_history_item_copy(item));
8448 items = g_list_next(items);
8451 /* current item */
8452 if (m) {
8453 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8454 u1->history = g_list_prepend(u1->history,
8455 webkit_web_history_item_copy(item));
8458 /* back history */
8459 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8461 while (items) {
8462 item = items->data;
8463 u1->history = g_list_prepend(u1->history,
8464 webkit_web_history_item_copy(item));
8465 items = g_list_next(items);
8468 TAILQ_INSERT_HEAD(&undos, u1, entry);
8470 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8471 u2 = TAILQ_LAST(&undos, undo_tailq);
8472 TAILQ_REMOVE(&undos, u2, entry);
8473 g_free(u2->uri);
8474 g_list_free(u2->history);
8475 g_free(u2);
8476 } else
8477 undo_count++;
8479 return (0);
8482 void
8483 delete_tab(struct tab *t)
8485 struct karg a;
8487 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8489 if (t == NULL)
8490 return;
8492 buffercmd_abort(t);
8493 TAILQ_REMOVE(&tabs, t, entry);
8495 /* Halt all webkit activity. */
8496 abort_favicon_download(t);
8497 webkit_web_view_stop_loading(t->wv);
8499 /* Save the tab, so we can undo the close. */
8500 undo_close_tab_save(t);
8502 if (browser_mode == XT_BM_KIOSK) {
8503 gtk_widget_destroy(t->uri_entry);
8504 gtk_widget_destroy(t->stop);
8505 gtk_widget_destroy(t->js_toggle);
8508 gtk_widget_destroy(t->tab_elems.eventbox);
8509 gtk_widget_destroy(t->vbox);
8511 /* just in case */
8512 if (t->search_id)
8513 g_source_remove(t->search_id);
8515 g_free(t->user_agent);
8516 g_free(t->stylesheet);
8517 g_free(t->tmp_uri);
8518 g_free(t);
8520 if (TAILQ_EMPTY(&tabs)) {
8521 if (browser_mode == XT_BM_KIOSK)
8522 create_new_tab(home, NULL, 1, -1);
8523 else
8524 create_new_tab(NULL, NULL, 1, -1);
8527 /* recreate session */
8528 if (session_autosave) {
8529 a.s = NULL;
8530 save_tabs(t, &a);
8533 recalc_tabs();
8534 recolor_compact_tabs();
8537 void
8538 update_statusbar_zoom(struct tab *t)
8540 gfloat zoom;
8541 char s[16] = { '\0' };
8543 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8544 if ((zoom <= 0.99 || zoom >= 1.01))
8545 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8546 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8549 void
8550 setzoom_webkit(struct tab *t, int adjust)
8552 #define XT_ZOOMPERCENT 0.04
8554 gfloat zoom;
8556 if (t == NULL) {
8557 show_oops(NULL, "setzoom_webkit invalid parameters");
8558 return;
8561 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8562 if (adjust == XT_ZOOM_IN)
8563 zoom += XT_ZOOMPERCENT;
8564 else if (adjust == XT_ZOOM_OUT)
8565 zoom -= XT_ZOOMPERCENT;
8566 else if (adjust > 0)
8567 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8568 else {
8569 show_oops(t, "setzoom_webkit invalid zoom value");
8570 return;
8573 if (zoom < XT_ZOOMPERCENT)
8574 zoom = XT_ZOOMPERCENT;
8575 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8576 update_statusbar_zoom(t);
8579 gboolean
8580 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8582 struct tab *t = (struct tab *) data;
8584 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8586 switch (event->button) {
8587 case 1:
8588 set_current_tab(t->tab_id);
8589 break;
8590 case 2:
8591 delete_tab(t);
8592 break;
8595 return TRUE;
8598 void
8599 append_tab(struct tab *t)
8601 if (t == NULL)
8602 return;
8604 TAILQ_INSERT_TAIL(&tabs, t, entry);
8605 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8608 GtkWidget *
8609 create_sbe(int width)
8611 GtkWidget *sbe;
8613 sbe = gtk_entry_new();
8614 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8615 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8616 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8617 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8618 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8619 gtk_widget_set_size_request(sbe, width, -1);
8621 return sbe;
8624 struct tab *
8625 create_new_tab(char *title, struct undo *u, int focus, int position)
8627 struct tab *t;
8628 int load = 1, id;
8629 GtkWidget *b, *bb;
8630 WebKitWebHistoryItem *item;
8631 GList *items;
8632 GdkColor color;
8633 char *p;
8634 int sbe_p = 0, sbe_b = 0,
8635 sbe_z = 0;
8637 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8639 if (tabless && !TAILQ_EMPTY(&tabs)) {
8640 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8641 return (NULL);
8644 t = g_malloc0(sizeof *t);
8646 if (title == NULL) {
8647 title = "(untitled)";
8648 load = 0;
8651 t->vbox = gtk_vbox_new(FALSE, 0);
8653 /* label + button for tab */
8654 b = gtk_hbox_new(FALSE, 0);
8655 t->tab_content = b;
8657 #if GTK_CHECK_VERSION(2, 20, 0)
8658 t->spinner = gtk_spinner_new();
8659 #endif
8660 t->label = gtk_label_new(title);
8661 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8662 gtk_widget_set_size_request(t->label, 100, 0);
8663 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8664 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8665 gtk_widget_set_size_request(b, 130, 0);
8667 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8668 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8669 #if GTK_CHECK_VERSION(2, 20, 0)
8670 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8671 #endif
8673 /* toolbar */
8674 if (browser_mode == XT_BM_KIOSK) {
8675 t->toolbar = create_kiosk_toolbar(t);
8676 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8678 } else {
8679 t->toolbar = create_toolbar(t);
8680 if (fancy_bar)
8681 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8682 FALSE, 0);
8685 /* marks */
8686 marks_clear(t);
8688 /* browser */
8689 t->browser_win = create_browser(t);
8690 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8692 /* oops message for user feedback */
8693 t->oops = gtk_entry_new();
8694 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8695 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8696 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8697 gdk_color_parse(XT_COLOR_RED, &color);
8698 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8699 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8700 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8702 /* command entry */
8703 t->cmd = gtk_entry_new();
8704 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8705 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8706 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8707 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8709 /* status bar */
8710 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8712 t->sbe.statusbar = gtk_entry_new();
8713 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8714 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8715 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8716 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8718 /* create these widgets only if specified in statusbar_elems */
8720 t->sbe.position = create_sbe(40);
8721 t->sbe.zoom = create_sbe(40);
8722 t->sbe.buffercmd = create_sbe(60);
8724 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8726 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8727 TRUE, FALSE);
8729 /* gtk widgets cannot be added to a box twice. sbe_* variables
8730 make sure of this */
8731 for (p = statusbar_elems; *p != '\0'; p++) {
8732 switch (*p) {
8733 case '|':
8735 GtkWidget *sep = gtk_vseparator_new();
8737 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8738 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8739 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8740 FALSE, FALSE, FALSE);
8741 break;
8743 case 'P':
8744 if (sbe_p) {
8745 warnx("flag \"%c\" specified more than "
8746 "once in statusbar_elems\n", *p);
8747 break;
8749 sbe_p = 1;
8750 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8751 t->sbe.position, FALSE, FALSE, FALSE);
8752 break;
8753 case 'B':
8754 if (sbe_b) {
8755 warnx("flag \"%c\" specified more than "
8756 "once in statusbar_elems\n", *p);
8757 break;
8759 sbe_b = 1;
8760 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8761 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8762 break;
8763 case 'Z':
8764 if (sbe_z) {
8765 warnx("flag \"%c\" specified more than "
8766 "once in statusbar_elems\n", *p);
8767 break;
8769 sbe_z = 1;
8770 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8771 t->sbe.zoom, FALSE, FALSE, FALSE);
8772 break;
8773 default:
8774 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8775 break;
8779 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8781 /* buffer list */
8782 t->buffers = create_buffers(t);
8783 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8785 /* xtp meaning is normal by default */
8786 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8788 /* set empty favicon */
8789 xt_icon_from_name(t, "text-html");
8791 /* and show it all */
8792 gtk_widget_show_all(b);
8793 gtk_widget_show_all(t->vbox);
8795 /* compact tab bar */
8796 t->tab_elems.label = gtk_label_new(title);
8797 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8798 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8799 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8800 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8802 t->tab_elems.eventbox = gtk_event_box_new();
8803 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8804 t->tab_elems.sep = gtk_vseparator_new();
8806 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8807 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8808 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8809 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8810 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8811 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8813 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8814 TRUE, 0);
8815 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8816 FALSE, 0);
8817 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8818 t->tab_elems.box);
8820 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8821 TRUE, 0);
8822 gtk_widget_show_all(t->tab_elems.eventbox);
8824 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8825 append_tab(t);
8826 else {
8827 id = position >= 0 ? position :
8828 gtk_notebook_get_current_page(notebook) + 1;
8829 if (id > gtk_notebook_get_n_pages(notebook))
8830 append_tab(t);
8831 else {
8832 TAILQ_INSERT_TAIL(&tabs, t, entry);
8833 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8834 gtk_box_reorder_child(GTK_BOX(tab_bar),
8835 t->tab_elems.eventbox, id);
8836 recalc_tabs();
8840 #if GTK_CHECK_VERSION(2, 20, 0)
8841 /* turn spinner off if we are a new tab without uri */
8842 if (!load) {
8843 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8844 gtk_widget_hide(t->spinner);
8846 #endif
8847 /* make notebook tabs reorderable */
8848 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8850 /* compact tabs clickable */
8851 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8852 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8854 g_object_connect(G_OBJECT(t->cmd),
8855 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8856 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8857 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8858 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8859 (char *)NULL);
8861 /* reuse wv_button_cb to hide oops */
8862 g_object_connect(G_OBJECT(t->oops),
8863 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8864 (char *)NULL);
8866 g_signal_connect(t->buffers,
8867 "row-activated", G_CALLBACK(row_activated_cb), t);
8868 g_object_connect(G_OBJECT(t->buffers),
8869 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8871 g_object_connect(G_OBJECT(t->wv),
8872 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8873 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8874 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8875 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8876 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8877 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8878 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8879 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8880 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8881 "signal::event", G_CALLBACK(webview_event_cb), t,
8882 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8883 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8884 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8885 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8886 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8887 (char *)NULL);
8888 g_signal_connect(t->wv,
8889 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8891 * XXX this puts invalid url in uri_entry and that is undesirable
8893 #if 0
8894 g_signal_connect(t->wv,
8895 "load-error", G_CALLBACK(notify_load_error_cb), t);
8896 #endif
8897 g_signal_connect(t->wv,
8898 "notify::title", G_CALLBACK(notify_title_cb), t);
8900 /* hijack the unused keys as if we were the browser */
8901 g_object_connect(G_OBJECT(t->toolbar),
8902 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8903 (char *)NULL);
8905 g_signal_connect(G_OBJECT(bb), "button_press_event",
8906 G_CALLBACK(tab_close_cb), t);
8908 /* hide stuff */
8909 hide_cmd(t);
8910 hide_oops(t);
8911 hide_buffers(t);
8912 url_set_visibility();
8913 statusbar_set_visibility();
8915 if (focus) {
8916 set_current_tab(t->tab_id);
8917 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8918 t->tab_id);
8921 if (load) {
8922 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8923 load_uri(t, title);
8924 } else {
8925 if (show_url == 1)
8926 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8927 else
8928 focus_webview(t);
8931 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8932 /* restore the tab's history */
8933 if (u && u->history) {
8934 items = u->history;
8935 while (items) {
8936 item = items->data;
8937 webkit_web_back_forward_list_add_item(t->bfl, item);
8938 items = g_list_next(items);
8941 item = g_list_nth_data(u->history, u->back);
8942 if (item)
8943 webkit_web_view_go_to_back_forward_item(t->wv, item);
8945 g_list_free(items);
8946 g_list_free(u->history);
8947 } else
8948 webkit_web_back_forward_list_clear(t->bfl);
8950 recolor_compact_tabs();
8951 setzoom_webkit(t, XT_ZOOM_NORMAL);
8952 return (t);
8955 void
8956 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8957 gpointer *udata)
8959 struct tab *t;
8960 const gchar *uri;
8962 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8964 if (gtk_notebook_get_current_page(notebook) == -1)
8965 recalc_tabs();
8967 TAILQ_FOREACH(t, &tabs, entry) {
8968 if (t->tab_id == pn) {
8969 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8970 "%d\n", pn);
8972 uri = get_title(t, TRUE);
8973 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8975 hide_cmd(t);
8976 hide_oops(t);
8978 if (t->focus_wv) {
8979 /* can't use focus_webview here */
8980 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8986 void
8987 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8988 gpointer *udata)
8990 struct tab *t = NULL, *tt;
8992 recalc_tabs();
8994 TAILQ_FOREACH(tt, &tabs, entry)
8995 if (tt->tab_id == pn) {
8996 t = tt;
8997 break;
9000 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9002 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9003 t->tab_id);
9006 void
9007 menuitem_response(struct tab *t)
9009 gtk_notebook_set_current_page(notebook, t->tab_id);
9012 gboolean
9013 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9015 GtkWidget *menu, *menu_items;
9016 GdkEventButton *bevent;
9017 const gchar *uri;
9018 struct tab *ti;
9020 if (event->type == GDK_BUTTON_PRESS) {
9021 bevent = (GdkEventButton *) event;
9022 menu = gtk_menu_new();
9024 TAILQ_FOREACH(ti, &tabs, entry) {
9025 if ((uri = get_uri(ti)) == NULL)
9026 /* XXX make sure there is something to print */
9027 /* XXX add gui pages in here to look purdy */
9028 uri = "(untitled)";
9029 menu_items = gtk_menu_item_new_with_label(uri);
9030 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9031 gtk_widget_show(menu_items);
9033 g_signal_connect_swapped((menu_items),
9034 "activate", G_CALLBACK(menuitem_response),
9035 (gpointer)ti);
9038 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9039 bevent->button, bevent->time);
9041 /* unref object so it'll free itself when popped down */
9042 #if !GTK_CHECK_VERSION(3, 0, 0)
9043 /* XXX does not need unref with gtk+3? */
9044 g_object_ref_sink(menu);
9045 g_object_unref(menu);
9046 #endif
9048 return (TRUE /* eat event */);
9051 return (FALSE /* propagate */);
9055 icon_size_map(int icon_size)
9057 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9058 icon_size > GTK_ICON_SIZE_DIALOG)
9059 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9061 return (icon_size);
9064 GtkWidget *
9065 create_button(char *name, char *stockid, int size)
9067 GtkWidget *button, *image;
9068 gchar *rcstring;
9069 int gtk_icon_size;
9071 rcstring = g_strdup_printf(
9072 "style \"%s-style\"\n"
9073 "{\n"
9074 " GtkWidget::focus-padding = 0\n"
9075 " GtkWidget::focus-line-width = 0\n"
9076 " xthickness = 0\n"
9077 " ythickness = 0\n"
9078 "}\n"
9079 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9080 gtk_rc_parse_string(rcstring);
9081 g_free(rcstring);
9082 button = gtk_button_new();
9083 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9084 gtk_icon_size = icon_size_map(size ? size : icon_size);
9086 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9087 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9088 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9089 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9090 gtk_widget_set_name(button, name);
9091 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9093 return (button);
9096 void
9097 button_set_stockid(GtkWidget *button, char *stockid)
9099 GtkWidget *image;
9101 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9102 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9103 gtk_button_set_image(GTK_BUTTON(button), image);
9106 void
9107 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9109 gchar *p = NULL;
9110 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9111 gint len;
9113 if (xterm_workaround == 0)
9114 return;
9117 * xterm doesn't play nice with clipboards because it clears the
9118 * primary when clicked. We rely on primary being set to properly
9119 * handle middle mouse button clicks (paste). So when someone clears
9120 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9121 * other application behavior (as in DON'T clear primary).
9124 p = gtk_clipboard_wait_for_text(primary);
9125 if (p == NULL) {
9126 if (gdk_property_get(gdk_get_default_root_window(),
9127 atom,
9128 gdk_atom_intern("STRING", FALSE),
9130 1024 * 1024 /* picked out of my butt */,
9131 FALSE,
9132 NULL,
9133 NULL,
9134 &len,
9135 (guchar **)&p)) {
9136 /* yes sir, we need to NUL the string */
9137 p[len] = '\0';
9138 gtk_clipboard_set_text(primary, p, -1);
9142 if (p)
9143 g_free(p);
9146 void
9147 create_canvas(void)
9149 GtkWidget *vbox;
9150 GList *l = NULL;
9151 GdkPixbuf *pb;
9152 char file[PATH_MAX];
9153 int i;
9155 vbox = gtk_vbox_new(FALSE, 0);
9156 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9157 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9158 #if !GTK_CHECK_VERSION(3, 0, 0)
9159 /* XXX seems to be needed with gtk+2 */
9160 gtk_notebook_set_tab_hborder(notebook, 0);
9161 gtk_notebook_set_tab_vborder(notebook, 0);
9162 #endif
9163 gtk_notebook_set_scrollable(notebook, TRUE);
9164 gtk_notebook_set_show_border(notebook, FALSE);
9165 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9167 abtn = gtk_button_new();
9168 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9169 gtk_widget_set_size_request(arrow, -1, -1);
9170 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9171 gtk_widget_set_size_request(abtn, -1, 20);
9173 #if GTK_CHECK_VERSION(2, 20, 0)
9174 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9175 #endif
9176 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9178 /* compact tab bar */
9179 tab_bar = gtk_hbox_new(TRUE, 0);
9181 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9182 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9183 gtk_widget_set_size_request(vbox, -1, -1);
9185 g_object_connect(G_OBJECT(notebook),
9186 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9187 (char *)NULL);
9188 g_object_connect(G_OBJECT(notebook),
9189 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9190 NULL, (char *)NULL);
9191 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9192 G_CALLBACK(arrow_cb), NULL);
9194 main_window = create_window();
9195 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9197 /* icons */
9198 for (i = 0; i < LENGTH(icons); i++) {
9199 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9200 pb = gdk_pixbuf_new_from_file(file, NULL);
9201 l = g_list_append(l, pb);
9203 gtk_window_set_default_icon_list(l);
9205 /* clipboard work around */
9206 if (xterm_workaround)
9207 g_signal_connect(
9208 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9209 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9211 gtk_widget_show_all(abtn);
9212 gtk_widget_show_all(main_window);
9213 notebook_tab_set_visibility();
9216 void
9217 set_hook(void **hook, char *name)
9219 if (hook == NULL)
9220 errx(1, "set_hook");
9222 if (*hook == NULL) {
9223 *hook = dlsym(RTLD_NEXT, name);
9224 if (*hook == NULL)
9225 errx(1, "can't hook %s", name);
9229 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9230 gboolean
9231 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9233 g_return_val_if_fail(cookie1, FALSE);
9234 g_return_val_if_fail(cookie2, FALSE);
9236 return (!strcmp (cookie1->name, cookie2->name) &&
9237 !strcmp (cookie1->value, cookie2->value) &&
9238 !strcmp (cookie1->path, cookie2->path) &&
9239 !strcmp (cookie1->domain, cookie2->domain));
9242 void
9243 transfer_cookies(void)
9245 GSList *cf;
9246 SoupCookie *sc, *pc;
9248 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9250 for (;cf; cf = cf->next) {
9251 pc = cf->data;
9252 sc = soup_cookie_copy(pc);
9253 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9256 soup_cookies_free(cf);
9259 void
9260 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9262 GSList *cf;
9263 SoupCookie *ci;
9265 print_cookie("soup_cookie_jar_delete_cookie", c);
9267 if (cookies_enabled == 0)
9268 return;
9270 if (jar == NULL || c == NULL)
9271 return;
9273 /* find and remove from persistent jar */
9274 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9276 for (;cf; cf = cf->next) {
9277 ci = cf->data;
9278 if (soup_cookie_equal(ci, c)) {
9279 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9280 break;
9284 soup_cookies_free(cf);
9286 /* delete from session jar */
9287 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9290 void
9291 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9293 struct domain *d = NULL;
9294 SoupCookie *c;
9295 FILE *r_cookie_f;
9297 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9298 jar, p_cookiejar, s_cookiejar);
9300 if (cookies_enabled == 0)
9301 return;
9303 /* see if we are up and running */
9304 if (p_cookiejar == NULL) {
9305 _soup_cookie_jar_add_cookie(jar, cookie);
9306 return;
9308 /* disallow p_cookiejar adds, shouldn't happen */
9309 if (jar == p_cookiejar)
9310 return;
9312 /* sanity */
9313 if (jar == NULL || cookie == NULL)
9314 return;
9316 if (enable_cookie_whitelist &&
9317 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9318 blocked_cookies++;
9319 DNPRINTF(XT_D_COOKIE,
9320 "soup_cookie_jar_add_cookie: reject %s\n",
9321 cookie->domain);
9322 if (save_rejected_cookies) {
9323 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9324 show_oops(NULL, "can't open reject cookie file");
9325 return;
9327 fseek(r_cookie_f, 0, SEEK_END);
9328 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9329 cookie->http_only ? "#HttpOnly_" : "",
9330 cookie->domain,
9331 *cookie->domain == '.' ? "TRUE" : "FALSE",
9332 cookie->path,
9333 cookie->secure ? "TRUE" : "FALSE",
9334 cookie->expires ?
9335 (gulong)soup_date_to_time_t(cookie->expires) :
9337 cookie->name,
9338 cookie->value);
9339 fflush(r_cookie_f);
9340 fclose(r_cookie_f);
9342 if (!allow_volatile_cookies)
9343 return;
9346 if (cookie->expires == NULL && session_timeout) {
9347 soup_cookie_set_expires(cookie,
9348 soup_date_new_from_now(session_timeout));
9349 print_cookie("modified add cookie", cookie);
9352 /* see if we are white listed for persistence */
9353 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9354 /* add to persistent jar */
9355 c = soup_cookie_copy(cookie);
9356 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9357 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9360 /* add to session jar */
9361 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9362 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9365 void
9366 setup_cookies(void)
9368 char file[PATH_MAX];
9370 set_hook((void *)&_soup_cookie_jar_add_cookie,
9371 "soup_cookie_jar_add_cookie");
9372 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9373 "soup_cookie_jar_delete_cookie");
9375 if (cookies_enabled == 0)
9376 return;
9379 * the following code is intricate due to overriding several libsoup
9380 * functions.
9381 * do not alter order of these operations.
9384 /* rejected cookies */
9385 if (save_rejected_cookies)
9386 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9387 XT_REJECT_FILE);
9389 /* persistent cookies */
9390 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9391 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9393 /* session cookies */
9394 s_cookiejar = soup_cookie_jar_new();
9395 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9396 cookie_policy, (void *)NULL);
9397 transfer_cookies();
9399 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9402 void
9403 setup_proxy(char *uri)
9405 SoupURI *suri;
9407 if (proxy_uri) {
9408 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9409 soup_uri_free(proxy_uri);
9410 proxy_uri = NULL;
9412 if (http_proxy) {
9413 if (http_proxy != uri) {
9414 g_free(http_proxy);
9415 http_proxy = NULL;
9419 if (uri) {
9420 http_proxy = g_strdup(uri);
9421 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9422 suri = soup_uri_new(http_proxy);
9423 if (!(suri == NULL || !SOUP_URI_VALID_FOR_HTTP(suri)))
9424 g_object_set(session, "proxy-uri", proxy_uri,
9425 (char *)NULL);
9426 if (suri)
9427 soup_uri_free(suri);
9432 set_http_proxy(char *proxy)
9434 SoupURI *uri;
9436 if (proxy == NULL)
9437 return (1);
9439 /* see if we need to clear it instead */
9440 if (strlen(proxy) == 0) {
9441 setup_proxy(NULL);
9442 return (0);
9445 uri = soup_uri_new(proxy);
9446 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9447 return (1);
9449 setup_proxy(proxy);
9451 soup_uri_free(uri);
9453 return (0);
9457 send_cmd_to_socket(char *cmd)
9459 int s, len, rv = 1;
9460 struct sockaddr_un sa;
9462 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9463 warnx("%s: socket", __func__);
9464 return (rv);
9467 sa.sun_family = AF_UNIX;
9468 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9469 work_dir, XT_SOCKET_FILE);
9470 len = SUN_LEN(&sa);
9472 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9473 warnx("%s: connect", __func__);
9474 goto done;
9477 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9478 warnx("%s: send", __func__);
9479 goto done;
9482 rv = 0;
9483 done:
9484 close(s);
9485 return (rv);
9488 gboolean
9489 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9491 int s, n;
9492 char str[XT_MAX_URL_LENGTH];
9493 socklen_t t = sizeof(struct sockaddr_un);
9494 struct sockaddr_un sa;
9495 struct passwd *p;
9496 uid_t uid;
9497 gid_t gid;
9498 struct tab *tt;
9499 gint fd = g_io_channel_unix_get_fd(source);
9501 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9502 warn("accept");
9503 return (FALSE);
9506 if (getpeereid(s, &uid, &gid) == -1) {
9507 warn("getpeereid");
9508 return (FALSE);
9510 if (uid != getuid() || gid != getgid()) {
9511 warnx("unauthorized user");
9512 return (FALSE);
9515 p = getpwuid(uid);
9516 if (p == NULL) {
9517 warnx("not a valid user");
9518 return (FALSE);
9521 n = recv(s, str, sizeof(str), 0);
9522 if (n <= 0)
9523 return (TRUE);
9525 tt = TAILQ_LAST(&tabs, tab_list);
9526 cmd_execute(tt, str);
9527 return (TRUE);
9531 is_running(void)
9533 int s, len, rv = 1;
9534 struct sockaddr_un sa;
9536 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9537 warn("is_running: socket");
9538 return (-1);
9541 sa.sun_family = AF_UNIX;
9542 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9543 work_dir, XT_SOCKET_FILE);
9544 len = SUN_LEN(&sa);
9546 /* connect to see if there is a listener */
9547 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9548 rv = 0; /* not running */
9549 else
9550 rv = 1; /* already running */
9552 close(s);
9554 return (rv);
9558 build_socket(void)
9560 int s, len;
9561 struct sockaddr_un sa;
9563 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9564 warn("build_socket: socket");
9565 return (-1);
9568 sa.sun_family = AF_UNIX;
9569 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9570 work_dir, XT_SOCKET_FILE);
9571 len = SUN_LEN(&sa);
9573 /* connect to see if there is a listener */
9574 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9575 /* no listener so we will */
9576 unlink(sa.sun_path);
9578 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9579 warn("build_socket: bind");
9580 goto done;
9583 if (listen(s, 1) == -1) {
9584 warn("build_socket: listen");
9585 goto done;
9588 return (s);
9591 done:
9592 close(s);
9593 return (-1);
9596 gboolean
9597 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9598 GtkTreeIter *iter, struct tab *t)
9600 gchar *value;
9602 gtk_tree_model_get(model, iter, 0, &value, -1);
9603 load_uri(t, value);
9604 g_free(value);
9606 return (FALSE);
9609 gboolean
9610 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9611 GtkTreeIter *iter, struct tab *t)
9613 gchar *value;
9615 gtk_tree_model_get(model, iter, 0, &value, -1);
9616 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9617 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9618 g_free(value);
9620 return (TRUE);
9623 void
9624 completion_add_uri(const gchar *uri)
9626 GtkTreeIter iter;
9628 /* add uri to list_store */
9629 gtk_list_store_append(completion_model, &iter);
9630 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9633 gboolean
9634 completion_match(GtkEntryCompletion *completion, const gchar *key,
9635 GtkTreeIter *iter, gpointer user_data)
9637 gchar *value;
9638 gboolean match = FALSE;
9640 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9641 -1);
9643 if (value == NULL)
9644 return FALSE;
9646 match = match_uri(value, key);
9648 g_free(value);
9649 return (match);
9652 void
9653 completion_add(struct tab *t)
9655 /* enable completion for tab */
9656 t->completion = gtk_entry_completion_new();
9657 gtk_entry_completion_set_text_column(t->completion, 0);
9658 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9659 gtk_entry_completion_set_model(t->completion,
9660 GTK_TREE_MODEL(completion_model));
9661 gtk_entry_completion_set_match_func(t->completion, completion_match,
9662 NULL, NULL);
9663 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9664 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9665 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9666 G_CALLBACK(completion_select_cb), t);
9667 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9668 G_CALLBACK(completion_hover_cb), t);
9671 void
9672 xxx_dir(char *dir)
9674 struct stat sb;
9676 if (stat(dir, &sb)) {
9677 if (mkdir(dir, S_IRWXU) == -1)
9678 err(1, "mkdir %s", dir);
9679 if (stat(dir, &sb))
9680 err(1, "stat %s", dir);
9682 if (S_ISDIR(sb.st_mode) == 0)
9683 errx(1, "%s not a dir", dir);
9684 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9685 warnx("fixing invalid permissions on %s", dir);
9686 if (chmod(dir, S_IRWXU) == -1)
9687 err(1, "chmod %s", dir);
9691 void
9692 usage(void)
9694 fprintf(stderr,
9695 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9696 exit(0);
9701 main(int argc, char *argv[])
9703 struct stat sb;
9704 int c, s, optn = 0, opte = 0, focus = 1;
9705 char conf[PATH_MAX] = { '\0' };
9706 char file[PATH_MAX];
9707 char *env_proxy = NULL;
9708 char *cmd = NULL;
9709 FILE *f = NULL;
9710 struct karg a;
9711 struct sigaction sact;
9712 GIOChannel *channel;
9713 struct rlimit rlp;
9715 start_argv = argv;
9717 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9719 RB_INIT(&hl);
9720 RB_INIT(&js_wl);
9721 RB_INIT(&downloads);
9723 TAILQ_INIT(&tabs);
9724 TAILQ_INIT(&mtl);
9725 TAILQ_INIT(&aliases);
9726 TAILQ_INIT(&undos);
9727 TAILQ_INIT(&kbl);
9728 TAILQ_INIT(&spl);
9730 /* fiddle with ulimits */
9731 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9732 warn("getrlimit");
9733 else {
9734 /* just use them all */
9735 rlp.rlim_cur = rlp.rlim_max;
9736 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9737 warn("setrlimit");
9738 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9739 warn("getrlimit");
9740 else if (rlp.rlim_cur <= 256)
9741 startpage_add("%s requires at least 256 file "
9742 "descriptors, currently it has up to %d available",
9743 __progname, rlp.rlim_cur);
9746 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9747 switch (c) {
9748 case 'S':
9749 show_url = 0;
9750 break;
9751 case 'T':
9752 show_tabs = 0;
9753 break;
9754 case 'V':
9755 errx(0 , "Version: %s", version);
9756 break;
9757 case 'f':
9758 strlcpy(conf, optarg, sizeof(conf));
9759 break;
9760 case 's':
9761 strlcpy(named_session, optarg, sizeof(named_session));
9762 break;
9763 case 't':
9764 tabless = 1;
9765 break;
9766 case 'n':
9767 optn = 1;
9768 break;
9769 case 'e':
9770 opte = 1;
9771 break;
9772 default:
9773 usage();
9774 /* NOTREACHED */
9777 argc -= optind;
9778 argv += optind;
9780 init_keybindings();
9782 gnutls_global_init();
9784 /* generate session keys for xtp pages */
9785 generate_xtp_session_key(&dl_session_key);
9786 generate_xtp_session_key(&hl_session_key);
9787 generate_xtp_session_key(&cl_session_key);
9788 generate_xtp_session_key(&fl_session_key);
9790 /* prepare gtk */
9791 if (!g_thread_supported()) {
9792 g_thread_init(NULL);
9793 gdk_threads_init();
9794 gdk_threads_enter();
9796 gtk_init(&argc, &argv);
9798 /* signals */
9799 bzero(&sact, sizeof(sact));
9800 sigemptyset(&sact.sa_mask);
9801 sact.sa_handler = sigchild;
9802 sact.sa_flags = SA_NOCLDSTOP;
9803 sigaction(SIGCHLD, &sact, NULL);
9805 /* set download dir */
9806 pwd = getpwuid(getuid());
9807 if (pwd == NULL)
9808 errx(1, "invalid user %d", getuid());
9809 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9811 /* compile buffer command regexes */
9812 buffercmd_init();
9814 /* set default string settings */
9815 home = g_strdup("https://www.cyphertite.com");
9816 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9817 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9818 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9819 cmd_font_name = g_strdup("monospace normal 9");
9820 oops_font_name = g_strdup("monospace normal 9");
9821 statusbar_font_name = g_strdup("monospace normal 9");
9822 tabbar_font_name = g_strdup("monospace normal 9");
9823 statusbar_elems = g_strdup("BP");
9825 /* read config file */
9826 if (strlen(conf) == 0)
9827 snprintf(conf, sizeof conf, "%s/.%s",
9828 pwd->pw_dir, XT_CONF_FILE);
9829 config_parse(conf, 0);
9831 /* init fonts */
9832 cmd_font = pango_font_description_from_string(cmd_font_name);
9833 oops_font = pango_font_description_from_string(oops_font_name);
9834 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9835 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9837 /* working directory */
9838 if (strlen(work_dir) == 0)
9839 snprintf(work_dir, sizeof work_dir, "%s/%s",
9840 pwd->pw_dir, XT_DIR);
9841 xxx_dir(work_dir);
9843 /* icon cache dir */
9844 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9845 xxx_dir(cache_dir);
9847 /* certs dir */
9848 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9849 xxx_dir(certs_dir);
9851 /* sessions dir */
9852 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9853 work_dir, XT_SESSIONS_DIR);
9854 xxx_dir(sessions_dir);
9856 /* runtime settings that can override config file */
9857 if (runtime_settings[0] != '\0')
9858 config_parse(runtime_settings, 1);
9860 /* download dir */
9861 if (!strcmp(download_dir, pwd->pw_dir))
9862 strlcat(download_dir, "/downloads", sizeof download_dir);
9863 xxx_dir(download_dir);
9865 /* favorites file */
9866 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9867 if (stat(file, &sb)) {
9868 warnx("favorites file doesn't exist, creating it");
9869 if ((f = fopen(file, "w")) == NULL)
9870 err(1, "favorites");
9871 fclose(f);
9874 /* quickmarks file */
9875 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9876 if (stat(file, &sb)) {
9877 warnx("quickmarks file doesn't exist, creating it");
9878 if ((f = fopen(file, "w")) == NULL)
9879 err(1, "quickmarks");
9880 fclose(f);
9883 /* cookies */
9884 session = webkit_get_default_session();
9885 setup_cookies();
9887 /* certs */
9888 if (ssl_ca_file) {
9889 if (stat(ssl_ca_file, &sb)) {
9890 warnx("no CA file: %s", ssl_ca_file);
9891 g_free(ssl_ca_file);
9892 ssl_ca_file = NULL;
9893 } else
9894 g_object_set(session,
9895 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9896 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9897 (void *)NULL);
9900 /* proxy */
9901 env_proxy = getenv("http_proxy");
9902 if (env_proxy)
9903 setup_proxy(env_proxy);
9904 else
9905 setup_proxy(http_proxy);
9907 if (opte) {
9908 send_cmd_to_socket(argv[0]);
9909 exit(0);
9912 /* set some connection parameters */
9913 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9914 g_object_set(session, "max-conns-per-host", max_host_connections,
9915 (char *)NULL);
9917 /* see if there is already an xxxterm running */
9918 if (single_instance && is_running()) {
9919 optn = 1;
9920 warnx("already running");
9923 if (optn) {
9924 while (argc) {
9925 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9926 send_cmd_to_socket(cmd);
9927 if (cmd)
9928 g_free(cmd);
9930 argc--;
9931 argv++;
9933 exit(0);
9936 /* uri completion */
9937 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9939 /* buffers */
9940 buffers_store = gtk_list_store_new
9941 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9943 qmarks_load();
9945 /* go graphical */
9946 create_canvas();
9947 notebook_tab_set_visibility();
9949 if (save_global_history)
9950 restore_global_history();
9952 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9953 restore_saved_tabs();
9954 else {
9955 a.s = named_session;
9956 a.i = XT_SES_DONOTHING;
9957 open_tabs(NULL, &a);
9960 /* see if we have an exception */
9961 if (!TAILQ_EMPTY(&spl)) {
9962 create_new_tab("about:startpage", NULL, focus, -1);
9963 focus = 0;
9966 while (argc) {
9967 create_new_tab(argv[0], NULL, focus, -1);
9968 focus = 0;
9970 argc--;
9971 argv++;
9974 if (TAILQ_EMPTY(&tabs))
9975 create_new_tab(home, NULL, 1, -1);
9977 if (enable_socket)
9978 if ((s = build_socket()) != -1) {
9979 channel = g_io_channel_unix_new(s);
9980 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9983 gtk_main();
9985 if (!g_thread_supported()) {
9986 gdk_threads_leave();
9989 gnutls_global_deinit();
9991 return (0);