allow http_proxy to be set at runtime.
[xxxterm.git] / xxxterm.c
blob127cb2963a6414fa66df4bdf9609b210a1e21f68
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;
1620 url_out = match_alias(url_in);
1621 if (url_out != NULL)
1622 return (url_out);
1624 if (guess_search) {
1626 * If there is no dot nor slash in the string and it isn't a
1627 * path to a local file and doesn't resolves to an IP, assume
1628 * that the user wants to search for the string.
1631 if (strchr(url_in, '.') == NULL &&
1632 strchr(url_in, '/') == NULL &&
1633 stat(url_in, &sb) != 0 &&
1634 gethostbyname(url_in) == NULL) {
1636 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1637 url_out = g_strdup_printf(search_string, enc_search);
1638 g_free(enc_search);
1639 return (url_out);
1643 /* XXX not sure about this heuristic */
1644 if (stat(url_in, &sb) == 0)
1645 url_out = g_strdup_printf("file://%s", url_in);
1646 else
1647 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1649 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1651 return (url_out);
1654 void
1655 load_uri(struct tab *t, gchar *uri)
1657 struct karg args;
1658 gchar *newuri = NULL;
1659 int i;
1661 if (uri == NULL)
1662 return;
1664 /* Strip leading spaces. */
1665 while (*uri && isspace(*uri))
1666 uri++;
1668 if (strlen(uri) == 0) {
1669 blank(t, NULL);
1670 return;
1673 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1675 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1676 for (i = 0; i < LENGTH(about_list); i++)
1677 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1678 bzero(&args, sizeof args);
1679 about_list[i].func(t, &args);
1680 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1681 FALSE);
1682 return;
1684 show_oops(t, "invalid about page");
1685 return;
1688 if (valid_url_type(uri)) {
1689 newuri = guess_url_type(uri);
1690 uri = newuri;
1693 set_status(t, (char *)uri, XT_STATUS_LOADING);
1694 marks_clear(t);
1695 webkit_web_view_load_uri(t->wv, uri);
1697 if (newuri)
1698 g_free(newuri);
1701 const gchar *
1702 get_uri(struct tab *t)
1704 const gchar *uri = NULL;
1706 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1707 return t->tmp_uri;
1708 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1709 uri = webkit_web_view_get_uri(t->wv);
1710 } else {
1711 /* use tmp_uri to make sure it is g_freed */
1712 if (t->tmp_uri)
1713 g_free(t->tmp_uri);
1714 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1715 about_list[t->xtp_meaning].name);
1716 uri = t->tmp_uri;
1718 return uri;
1721 const gchar *
1722 get_title(struct tab *t, bool window)
1724 const gchar *set = NULL, *title = NULL;
1725 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1727 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1728 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1729 goto notitle;
1731 title = webkit_web_view_get_title(t->wv);
1732 if ((set = title ? title : get_uri(t)))
1733 return set;
1735 notitle:
1736 set = window ? XT_NAME : "(untitled)";
1738 return set;
1742 add_alias(struct settings *s, char *line)
1744 char *l, *alias;
1745 struct alias *a = NULL;
1747 if (s == NULL || line == NULL) {
1748 show_oops(NULL, "add_alias invalid parameters");
1749 return (1);
1752 l = line;
1753 a = g_malloc(sizeof(*a));
1755 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1756 show_oops(NULL, "add_alias: incomplete alias definition");
1757 goto bad;
1759 if (strlen(alias) == 0 || strlen(l) == 0) {
1760 show_oops(NULL, "add_alias: invalid alias definition");
1761 goto bad;
1764 a->a_name = g_strdup(alias);
1765 a->a_uri = g_strdup(l);
1767 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1769 TAILQ_INSERT_TAIL(&aliases, a, entry);
1771 return (0);
1772 bad:
1773 if (a)
1774 g_free(a);
1775 return (1);
1779 add_mime_type(struct settings *s, char *line)
1781 char *mime_type;
1782 char *l;
1783 struct mime_type *m = NULL;
1784 int downloadfirst = 0;
1786 /* XXX this could be smarter */
1788 if (line == NULL || strlen(line) == 0) {
1789 show_oops(NULL, "add_mime_type invalid parameters");
1790 return (1);
1793 l = line;
1794 if (*l == '@') {
1795 downloadfirst = 1;
1796 l++;
1798 m = g_malloc(sizeof(*m));
1800 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1801 show_oops(NULL, "add_mime_type: invalid mime_type");
1802 goto bad;
1804 if (mime_type[strlen(mime_type) - 1] == '*') {
1805 mime_type[strlen(mime_type) - 1] = '\0';
1806 m->mt_default = 1;
1807 } else
1808 m->mt_default = 0;
1810 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1811 show_oops(NULL, "add_mime_type: invalid mime_type");
1812 goto bad;
1815 m->mt_type = g_strdup(mime_type);
1816 m->mt_action = g_strdup(l);
1817 m->mt_download = downloadfirst;
1819 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1820 m->mt_type, m->mt_action, m->mt_default);
1822 TAILQ_INSERT_TAIL(&mtl, m, entry);
1824 return (0);
1825 bad:
1826 if (m)
1827 g_free(m);
1828 return (1);
1831 struct mime_type *
1832 find_mime_type(char *mime_type)
1834 struct mime_type *m, *def = NULL, *rv = NULL;
1836 TAILQ_FOREACH(m, &mtl, entry) {
1837 if (m->mt_default &&
1838 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1839 def = m;
1841 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1842 rv = m;
1843 break;
1847 if (rv == NULL)
1848 rv = def;
1850 return (rv);
1853 void
1854 walk_mime_type(struct settings *s,
1855 void (*cb)(struct settings *, char *, void *), void *cb_args)
1857 struct mime_type *m;
1858 char *str;
1860 if (s == NULL || cb == NULL) {
1861 show_oops(NULL, "walk_mime_type invalid parameters");
1862 return;
1865 TAILQ_FOREACH(m, &mtl, entry) {
1866 str = g_strdup_printf("%s%s --> %s",
1867 m->mt_type,
1868 m->mt_default ? "*" : "",
1869 m->mt_action);
1870 cb(s, str, cb_args);
1871 g_free(str);
1875 void
1876 wl_add(char *str, struct domain_list *wl, int handy)
1878 struct domain *d;
1879 int add_dot = 0;
1881 if (str == NULL || wl == NULL || strlen(str) < 2)
1882 return;
1884 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1886 /* treat *.moo.com the same as .moo.com */
1887 if (str[0] == '*' && str[1] == '.')
1888 str = &str[1];
1889 else if (str[0] == '.')
1890 str = &str[0];
1891 else
1892 add_dot = 1;
1894 d = g_malloc(sizeof *d);
1895 if (add_dot)
1896 d->d = g_strdup_printf(".%s", str);
1897 else
1898 d->d = g_strdup(str);
1899 d->handy = handy;
1901 if (RB_INSERT(domain_list, wl, d))
1902 goto unwind;
1904 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1905 return;
1906 unwind:
1907 if (d) {
1908 if (d->d)
1909 g_free(d->d);
1910 g_free(d);
1915 add_cookie_wl(struct settings *s, char *entry)
1917 wl_add(entry, &c_wl, 1);
1918 return (0);
1921 void
1922 walk_cookie_wl(struct settings *s,
1923 void (*cb)(struct settings *, char *, void *), void *cb_args)
1925 struct domain *d;
1927 if (s == NULL || cb == NULL) {
1928 show_oops(NULL, "walk_cookie_wl invalid parameters");
1929 return;
1932 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1933 cb(s, d->d, cb_args);
1936 void
1937 walk_js_wl(struct settings *s,
1938 void (*cb)(struct settings *, char *, void *), void *cb_args)
1940 struct domain *d;
1942 if (s == NULL || cb == NULL) {
1943 show_oops(NULL, "walk_js_wl invalid parameters");
1944 return;
1947 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1948 cb(s, d->d, cb_args);
1952 add_js_wl(struct settings *s, char *entry)
1954 wl_add(entry, &js_wl, 1 /* persistent */);
1955 return (0);
1958 struct domain *
1959 wl_find(const gchar *search, struct domain_list *wl)
1961 int i;
1962 struct domain *d = NULL, dfind;
1963 gchar *s = NULL;
1965 if (search == NULL || wl == NULL)
1966 return (NULL);
1967 if (strlen(search) < 2)
1968 return (NULL);
1970 if (search[0] != '.')
1971 s = g_strdup_printf(".%s", search);
1972 else
1973 s = g_strdup(search);
1975 for (i = strlen(s) - 1; i >= 0; i--) {
1976 if (s[i] == '.') {
1977 dfind.d = &s[i];
1978 d = RB_FIND(domain_list, wl, &dfind);
1979 if (d)
1980 goto done;
1984 done:
1985 if (s)
1986 g_free(s);
1988 return (d);
1991 struct domain *
1992 wl_find_uri(const gchar *s, struct domain_list *wl)
1994 int i;
1995 char *ss;
1996 struct domain *r;
1998 if (s == NULL || wl == NULL)
1999 return (NULL);
2001 if (!strncmp(s, "http://", strlen("http://")))
2002 s = &s[strlen("http://")];
2003 else if (!strncmp(s, "https://", strlen("https://")))
2004 s = &s[strlen("https://")];
2006 if (strlen(s) < 2)
2007 return (NULL);
2009 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2010 /* chop string at first slash */
2011 if (s[i] == '/' || s[i] == '\0') {
2012 ss = g_strdup(s);
2013 ss[i] = '\0';
2014 r = wl_find(ss, wl);
2015 g_free(ss);
2016 return (r);
2019 return (NULL);
2023 settings_add(char *var, char *val)
2025 int i, rv, *p;
2026 gfloat *f;
2027 char **s;
2029 /* get settings */
2030 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2031 if (strcmp(var, rs[i].name))
2032 continue;
2034 if (rs[i].s) {
2035 if (rs[i].s->set(&rs[i], val))
2036 errx(1, "invalid value for %s: %s", var, val);
2037 rv = 1;
2038 break;
2039 } else
2040 switch (rs[i].type) {
2041 case XT_S_INT:
2042 p = rs[i].ival;
2043 *p = atoi(val);
2044 rv = 1;
2045 break;
2046 case XT_S_STR:
2047 s = rs[i].sval;
2048 if (s == NULL)
2049 errx(1, "invalid sval for %s",
2050 rs[i].name);
2051 if (*s)
2052 g_free(*s);
2053 *s = g_strdup(val);
2054 rv = 1;
2055 break;
2056 case XT_S_FLOAT:
2057 f = rs[i].fval;
2058 *f = atof(val);
2059 rv = 1;
2060 break;
2061 case XT_S_INVALID:
2062 default:
2063 errx(1, "invalid type for %s", var);
2065 break;
2067 return (rv);
2070 #define WS "\n= \t"
2071 void
2072 config_parse(char *filename, int runtime)
2074 FILE *config, *f;
2075 char *line, *cp, *var, *val;
2076 size_t len, lineno = 0;
2077 int handled;
2078 char file[PATH_MAX];
2079 struct stat sb;
2081 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2083 if (filename == NULL)
2084 return;
2086 if (runtime && runtime_settings[0] != '\0') {
2087 snprintf(file, sizeof file, "%s/%s",
2088 work_dir, runtime_settings);
2089 if (stat(file, &sb)) {
2090 warnx("runtime file doesn't exist, creating it");
2091 if ((f = fopen(file, "w")) == NULL)
2092 err(1, "runtime");
2093 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2094 fclose(f);
2096 } else
2097 strlcpy(file, filename, sizeof file);
2099 if ((config = fopen(file, "r")) == NULL) {
2100 warn("config_parse: cannot open %s", filename);
2101 return;
2104 for (;;) {
2105 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2106 if (feof(config) || ferror(config))
2107 break;
2109 cp = line;
2110 cp += (long)strspn(cp, WS);
2111 if (cp[0] == '\0') {
2112 /* empty line */
2113 free(line);
2114 continue;
2117 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2118 startpage_add("invalid configuration file entry: %s",
2119 line);
2121 cp += (long)strspn(cp, WS);
2123 if ((val = strsep(&cp, "\0")) == NULL)
2124 break;
2126 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2127 handled = settings_add(var, val);
2128 if (handled == 0)
2129 startpage_add("invalid configuration file entry: %s=%s",
2130 var, val);
2132 free(line);
2135 fclose(config);
2138 char *
2139 js_ref_to_string(JSContextRef context, JSValueRef ref)
2141 char *s = NULL;
2142 size_t l;
2143 JSStringRef jsref;
2145 jsref = JSValueToStringCopy(context, ref, NULL);
2146 if (jsref == NULL)
2147 return (NULL);
2149 l = JSStringGetMaximumUTF8CStringSize(jsref);
2150 s = g_malloc(l);
2151 if (s)
2152 JSStringGetUTF8CString(jsref, s, l);
2153 JSStringRelease(jsref);
2155 return (s);
2158 void
2159 disable_hints(struct tab *t)
2161 bzero(t->hint_buf, sizeof t->hint_buf);
2162 bzero(t->hint_num, sizeof t->hint_num);
2163 run_script(t, "vimprobable_clear()");
2164 t->hints_on = 0;
2165 t->hint_mode = XT_HINT_NONE;
2168 void
2169 enable_hints(struct tab *t)
2171 bzero(t->hint_buf, sizeof t->hint_buf);
2172 run_script(t, "vimprobable_show_hints()");
2173 t->hints_on = 1;
2174 t->hint_mode = XT_HINT_NONE;
2177 #define XT_JS_OPEN ("open;")
2178 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2179 #define XT_JS_FIRE ("fire;")
2180 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2181 #define XT_JS_FOUND ("found;")
2182 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2185 run_script(struct tab *t, char *s)
2187 JSGlobalContextRef ctx;
2188 WebKitWebFrame *frame;
2189 JSStringRef str;
2190 JSValueRef val, exception;
2191 char *es, buf[128];
2193 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2194 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2196 frame = webkit_web_view_get_main_frame(t->wv);
2197 ctx = webkit_web_frame_get_global_context(frame);
2199 str = JSStringCreateWithUTF8CString(s);
2200 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2201 NULL, 0, &exception);
2202 JSStringRelease(str);
2204 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2205 if (val == NULL) {
2206 es = js_ref_to_string(ctx, exception);
2207 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2208 g_free(es);
2209 return (1);
2210 } else {
2211 es = js_ref_to_string(ctx, val);
2212 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2214 /* handle return value right here */
2215 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2216 disable_hints(t);
2217 marks_clear(t);
2218 load_uri(t, &es[XT_JS_OPEN_LEN]);
2221 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2222 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2223 &es[XT_JS_FIRE_LEN]);
2224 run_script(t, buf);
2225 disable_hints(t);
2228 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2229 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2230 disable_hints(t);
2233 g_free(es);
2236 return (0);
2240 hint(struct tab *t, struct karg *args)
2243 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2245 if (t->hints_on == 0)
2246 enable_hints(t);
2247 else
2248 disable_hints(t);
2250 return (0);
2253 void
2254 apply_style(struct tab *t)
2256 g_object_set(G_OBJECT(t->settings),
2257 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2261 userstyle(struct tab *t, struct karg *args)
2263 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2265 if (t->styled) {
2266 t->styled = 0;
2267 g_object_set(G_OBJECT(t->settings),
2268 "user-stylesheet-uri", NULL, (char *)NULL);
2269 } else {
2270 t->styled = 1;
2271 apply_style(t);
2273 return (0);
2277 * Doesn't work fully, due to the following bug:
2278 * https://bugs.webkit.org/show_bug.cgi?id=51747
2281 restore_global_history(void)
2283 char file[PATH_MAX];
2284 FILE *f;
2285 struct history *h;
2286 gchar *uri;
2287 gchar *title;
2288 const char delim[3] = {'\\', '\\', '\0'};
2290 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2292 if ((f = fopen(file, "r")) == NULL) {
2293 warnx("%s: fopen", __func__);
2294 return (1);
2297 for (;;) {
2298 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2299 if (feof(f) || ferror(f))
2300 break;
2302 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2303 if (feof(f) || ferror(f)) {
2304 free(uri);
2305 warnx("%s: broken history file\n", __func__);
2306 return (1);
2309 if (uri && strlen(uri) && title && strlen(title)) {
2310 webkit_web_history_item_new_with_data(uri, title);
2311 h = g_malloc(sizeof(struct history));
2312 h->uri = g_strdup(uri);
2313 h->title = g_strdup(title);
2314 RB_INSERT(history_list, &hl, h);
2315 completion_add_uri(h->uri);
2316 } else {
2317 warnx("%s: failed to restore history\n", __func__);
2318 free(uri);
2319 free(title);
2320 return (1);
2323 free(uri);
2324 free(title);
2325 uri = NULL;
2326 title = NULL;
2329 return (0);
2333 save_global_history_to_disk(struct tab *t)
2335 char file[PATH_MAX];
2336 FILE *f;
2337 struct history *h;
2339 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2341 if ((f = fopen(file, "w")) == NULL) {
2342 show_oops(t, "%s: global history file: %s",
2343 __func__, strerror(errno));
2344 return (1);
2347 RB_FOREACH_REVERSE(h, history_list, &hl) {
2348 if (h->uri && h->title)
2349 fprintf(f, "%s\n%s\n", h->uri, h->title);
2352 fclose(f);
2354 return (0);
2358 quit(struct tab *t, struct karg *args)
2360 if (save_global_history)
2361 save_global_history_to_disk(t);
2363 gtk_main_quit();
2365 return (1);
2369 open_tabs(struct tab *t, struct karg *a)
2371 char file[PATH_MAX];
2372 FILE *f = NULL;
2373 char *uri = NULL;
2374 int rv = 1;
2375 struct tab *ti, *tt;
2377 if (a == NULL)
2378 goto done;
2380 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2381 if ((f = fopen(file, "r")) == NULL)
2382 goto done;
2384 ti = TAILQ_LAST(&tabs, tab_list);
2386 for (;;) {
2387 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2388 if (feof(f) || ferror(f))
2389 break;
2391 /* retrieve session name */
2392 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2393 strlcpy(named_session,
2394 &uri[strlen(XT_SAVE_SESSION_ID)],
2395 sizeof named_session);
2396 continue;
2399 if (uri && strlen(uri))
2400 create_new_tab(uri, NULL, 1, -1);
2402 free(uri);
2403 uri = NULL;
2406 /* close open tabs */
2407 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2408 for (;;) {
2409 tt = TAILQ_FIRST(&tabs);
2410 if (tt != ti) {
2411 delete_tab(tt);
2412 continue;
2414 delete_tab(tt);
2415 break;
2417 recalc_tabs();
2420 rv = 0;
2421 done:
2422 if (f)
2423 fclose(f);
2425 return (rv);
2429 restore_saved_tabs(void)
2431 char file[PATH_MAX];
2432 int unlink_file = 0;
2433 struct stat sb;
2434 struct karg a;
2435 int rv = 0;
2437 snprintf(file, sizeof file, "%s/%s",
2438 sessions_dir, XT_RESTART_TABS_FILE);
2439 if (stat(file, &sb) == -1)
2440 a.s = XT_SAVED_TABS_FILE;
2441 else {
2442 unlink_file = 1;
2443 a.s = XT_RESTART_TABS_FILE;
2446 a.i = XT_SES_DONOTHING;
2447 rv = open_tabs(NULL, &a);
2449 if (unlink_file)
2450 unlink(file);
2452 return (rv);
2456 save_tabs(struct tab *t, struct karg *a)
2458 char file[PATH_MAX];
2459 FILE *f;
2460 int num_tabs = 0, i;
2461 struct tab **stabs = NULL;
2463 if (a == NULL)
2464 return (1);
2465 if (a->s == NULL)
2466 snprintf(file, sizeof file, "%s/%s",
2467 sessions_dir, named_session);
2468 else
2469 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2471 if ((f = fopen(file, "w")) == NULL) {
2472 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2473 return (1);
2476 /* save session name */
2477 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2479 /* Save tabs, in the order they are arranged in the notebook. */
2480 num_tabs = sort_tabs_by_page_num(&stabs);
2482 for (i = 0; i < num_tabs; i++)
2483 if (stabs[i]) {
2484 if (get_uri(stabs[i]) != NULL)
2485 fprintf(f, "%s\n", get_uri(stabs[i]));
2486 else if (gtk_entry_get_text(GTK_ENTRY(
2487 stabs[i]->uri_entry)))
2488 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2489 stabs[i]->uri_entry)));
2492 g_free(stabs);
2494 /* try and make sure this gets to disk NOW. XXX Backup first? */
2495 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2496 show_oops(t, "May not have managed to save session: %s",
2497 strerror(errno));
2500 fclose(f);
2502 return (0);
2506 save_tabs_and_quit(struct tab *t, struct karg *args)
2508 struct karg a;
2510 a.s = NULL;
2511 save_tabs(t, &a);
2512 quit(t, NULL);
2514 return (1);
2518 run_page_script(struct tab *t, struct karg *args)
2520 const gchar *uri;
2521 char *tmp, script[PATH_MAX];
2523 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2524 if (tmp[0] == '\0') {
2525 show_oops(t, "no script specified");
2526 return (1);
2529 if ((uri = get_uri(t)) == NULL) {
2530 show_oops(t, "tab is empty, not running script");
2531 return (1);
2534 if (tmp[0] == '~')
2535 snprintf(script, sizeof script, "%s/%s",
2536 pwd->pw_dir, &tmp[1]);
2537 else
2538 strlcpy(script, tmp, sizeof script);
2540 switch (fork()) {
2541 case -1:
2542 show_oops(t, "can't fork to run script");
2543 return (1);
2544 /* NOTREACHED */
2545 case 0:
2546 break;
2547 default:
2548 return (0);
2551 /* child */
2552 execlp(script, script, uri, (void *)NULL);
2554 _exit(0);
2556 /* NOTREACHED */
2558 return (0);
2562 yank_uri(struct tab *t, struct karg *args)
2564 const gchar *uri;
2565 GtkClipboard *clipboard;
2567 if ((uri = get_uri(t)) == NULL)
2568 return (1);
2570 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2571 gtk_clipboard_set_text(clipboard, uri, -1);
2573 return (0);
2577 paste_uri(struct tab *t, struct karg *args)
2579 GtkClipboard *clipboard;
2580 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2581 gint len;
2582 gchar *p = NULL, *uri;
2584 /* try primary clipboard first */
2585 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2586 p = gtk_clipboard_wait_for_text(clipboard);
2588 /* if it failed get whatever text is in cut_buffer0 */
2589 if (p == NULL && xterm_workaround)
2590 if (gdk_property_get(gdk_get_default_root_window(),
2591 atom,
2592 gdk_atom_intern("STRING", FALSE),
2594 1024 * 1024 /* picked out of my butt */,
2595 FALSE,
2596 NULL,
2597 NULL,
2598 &len,
2599 (guchar **)&p)) {
2600 /* yes sir, we need to NUL the string */
2601 p[len] = '\0';
2604 if (p) {
2605 uri = p;
2606 while (*uri && isspace(*uri))
2607 uri++;
2608 if (strlen(uri) == 0) {
2609 show_oops(t, "empty paste buffer");
2610 goto done;
2612 if (guess_search == 0 && valid_url_type(uri)) {
2613 /* we can be clever and paste this in search box */
2614 show_oops(t, "not a valid URL");
2615 goto done;
2618 if (args->i == XT_PASTE_CURRENT_TAB)
2619 load_uri(t, uri);
2620 else if (args->i == XT_PASTE_NEW_TAB)
2621 create_new_tab(uri, NULL, 1, -1);
2624 done:
2625 if (p)
2626 g_free(p);
2628 return (0);
2631 gchar *
2632 find_domain(const gchar *s, int toplevel)
2634 SoupURI *uri;
2635 gchar *ret, *p;
2637 if (s == NULL)
2638 return (NULL);
2640 uri = soup_uri_new(s);
2642 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2643 return (NULL);
2646 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2647 if ((p = strrchr(uri->host, '.')) != NULL) {
2648 while(--p >= uri->host && *p != '.');
2649 p++;
2650 } else
2651 p = uri->host;
2652 } else
2653 p = uri->host;
2655 if (uri->port == 80)
2656 ret = g_strdup_printf(".%s", p);
2657 else
2658 ret = g_strdup_printf(".%s:%d", p, uri->port);
2660 soup_uri_free(uri);
2662 return ret;
2666 toggle_cwl(struct tab *t, struct karg *args)
2668 struct domain *d;
2669 const gchar *uri;
2670 char *dom = NULL;
2671 int es;
2673 if (args == NULL)
2674 return (1);
2676 uri = get_uri(t);
2677 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2679 if (uri == NULL || dom == NULL ||
2680 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2681 show_oops(t, "Can't toggle domain in cookie white list");
2682 goto done;
2684 d = wl_find(dom, &c_wl);
2686 if (d == NULL)
2687 es = 0;
2688 else
2689 es = 1;
2691 if (args->i & XT_WL_TOGGLE)
2692 es = !es;
2693 else if ((args->i & XT_WL_ENABLE) && es != 1)
2694 es = 1;
2695 else if ((args->i & XT_WL_DISABLE) && es != 0)
2696 es = 0;
2698 if (es)
2699 /* enable cookies for domain */
2700 wl_add(dom, &c_wl, 0);
2701 else
2702 /* disable cookies for domain */
2703 RB_REMOVE(domain_list, &c_wl, d);
2705 if (args->i & XT_WL_RELOAD)
2706 webkit_web_view_reload(t->wv);
2708 done:
2709 g_free(dom);
2710 return (0);
2714 toggle_js(struct tab *t, struct karg *args)
2716 int es;
2717 const gchar *uri;
2718 struct domain *d;
2719 char *dom = NULL;
2721 if (args == NULL)
2722 return (1);
2724 g_object_get(G_OBJECT(t->settings),
2725 "enable-scripts", &es, (char *)NULL);
2726 if (args->i & XT_WL_TOGGLE)
2727 es = !es;
2728 else if ((args->i & XT_WL_ENABLE) && es != 1)
2729 es = 1;
2730 else if ((args->i & XT_WL_DISABLE) && es != 0)
2731 es = 0;
2732 else
2733 return (1);
2735 uri = get_uri(t);
2736 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2738 if (uri == NULL || dom == NULL ||
2739 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2740 show_oops(t, "Can't toggle domain in JavaScript white list");
2741 goto done;
2744 if (es) {
2745 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2746 wl_add(dom, &js_wl, 0 /* session */);
2747 } else {
2748 d = wl_find(dom, &js_wl);
2749 if (d)
2750 RB_REMOVE(domain_list, &js_wl, d);
2751 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2753 g_object_set(G_OBJECT(t->settings),
2754 "enable-scripts", es, (char *)NULL);
2755 g_object_set(G_OBJECT(t->settings),
2756 "javascript-can-open-windows-automatically", es, (char *)NULL);
2757 webkit_web_view_set_settings(t->wv, t->settings);
2759 if (args->i & XT_WL_RELOAD)
2760 webkit_web_view_reload(t->wv);
2761 done:
2762 if (dom)
2763 g_free(dom);
2764 return (0);
2767 void
2768 js_toggle_cb(GtkWidget *w, struct tab *t)
2770 struct karg a;
2772 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2773 toggle_cwl(t, &a);
2775 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2776 toggle_js(t, &a);
2780 toggle_src(struct tab *t, struct karg *args)
2782 gboolean mode;
2784 if (t == NULL)
2785 return (0);
2787 mode = webkit_web_view_get_view_source_mode(t->wv);
2788 webkit_web_view_set_view_source_mode(t->wv, !mode);
2789 webkit_web_view_reload(t->wv);
2791 return (0);
2794 void
2795 focus_webview(struct tab *t)
2797 if (t == NULL)
2798 return;
2800 /* only grab focus if we are visible */
2801 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2802 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2806 focus(struct tab *t, struct karg *args)
2808 if (t == NULL || args == NULL)
2809 return (1);
2811 if (show_url == 0)
2812 return (0);
2814 if (args->i == XT_FOCUS_URI)
2815 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2816 else if (args->i == XT_FOCUS_SEARCH)
2817 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2819 return (0);
2823 stats(struct tab *t, struct karg *args)
2825 char *page, *body, *s, line[64 * 1024];
2826 uint64_t line_count = 0;
2827 FILE *r_cookie_f;
2829 if (t == NULL)
2830 show_oops(NULL, "stats invalid parameters");
2832 line[0] = '\0';
2833 if (save_rejected_cookies) {
2834 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2835 for (;;) {
2836 s = fgets(line, sizeof line, r_cookie_f);
2837 if (s == NULL || feof(r_cookie_f) ||
2838 ferror(r_cookie_f))
2839 break;
2840 line_count++;
2842 fclose(r_cookie_f);
2843 snprintf(line, sizeof line,
2844 "<br/>Cookies blocked(*) total: %llu", line_count);
2845 } else
2846 show_oops(t, "Can't open blocked cookies file: %s",
2847 strerror(errno));
2850 body = g_strdup_printf(
2851 "Cookies blocked(*) this session: %llu"
2852 "%s"
2853 "<p><small><b>*</b> results vary based on settings</small></p>",
2854 blocked_cookies,
2855 line);
2857 page = get_html_page("Statistics", body, "", 0);
2858 g_free(body);
2860 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2861 g_free(page);
2863 return (0);
2867 marco(struct tab *t, struct karg *args)
2869 char *page, line[64 * 1024];
2870 int len;
2872 if (t == NULL)
2873 show_oops(NULL, "marco invalid parameters");
2875 line[0] = '\0';
2876 snprintf(line, sizeof line, "%s", marco_message(&len));
2878 page = get_html_page("Marco Sez...", line, "", 0);
2880 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2881 g_free(page);
2883 return (0);
2887 blank(struct tab *t, struct karg *args)
2889 if (t == NULL)
2890 show_oops(NULL, "blank invalid parameters");
2892 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2894 return (0);
2898 about(struct tab *t, struct karg *args)
2900 char *page, *body;
2902 if (t == NULL)
2903 show_oops(NULL, "about invalid parameters");
2905 body = g_strdup_printf("<b>Version: %s</b><p>"
2906 "Authors:"
2907 "<ul>"
2908 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2909 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2910 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2911 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2912 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2913 "</ul>"
2914 "Copyrights and licenses can be found on the XXXTerm "
2915 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2916 version
2919 page = get_html_page("About", body, "", 0);
2920 g_free(body);
2922 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2923 g_free(page);
2925 return (0);
2929 help(struct tab *t, struct karg *args)
2931 char *page, *head, *body;
2933 if (t == NULL)
2934 show_oops(NULL, "help invalid parameters");
2936 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2937 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2938 "</head>\n";
2939 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2940 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2941 "cgi-bin/man-cgi?xxxterm</a>";
2943 page = get_html_page(XT_NAME, body, head, FALSE);
2945 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2946 g_free(page);
2948 return (0);
2952 startpage(struct tab *t, struct karg *args)
2954 char *page, *body, *b;
2955 struct sp *s;
2957 if (t == NULL)
2958 show_oops(NULL, "startpage invalid parameters");
2960 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
2962 TAILQ_FOREACH(s, &spl, entry) {
2963 b = body;
2964 body = g_strdup_printf("%s%s<br>", body, s->line);
2965 g_free(b);
2968 page = get_html_page("Startup Exception", body, "", 0);
2969 g_free(body);
2971 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
2972 g_free(page);
2974 return (0);
2977 void
2978 startpage_add(const char *fmt, ...)
2980 va_list ap;
2981 char *msg;
2982 struct sp *s;
2984 if (fmt == NULL)
2985 return;
2987 va_start(ap, fmt);
2988 if (vasprintf(&msg, fmt, ap) == -1)
2989 errx(1, "startpage_add failed");
2990 va_end(ap);
2992 s = g_malloc0(sizeof *s);
2993 s->line = msg;
2995 TAILQ_INSERT_TAIL(&spl, s, entry);
2999 * update all favorite tabs apart from one. Pass NULL if
3000 * you want to update all.
3002 void
3003 update_favorite_tabs(struct tab *apart_from)
3005 struct tab *t;
3006 if (!updating_fl_tabs) {
3007 updating_fl_tabs = 1; /* stop infinite recursion */
3008 TAILQ_FOREACH(t, &tabs, entry)
3009 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3010 && (t != apart_from))
3011 xtp_page_fl(t, NULL);
3012 updating_fl_tabs = 0;
3016 /* show a list of favorites (bookmarks) */
3018 xtp_page_fl(struct tab *t, struct karg *args)
3020 char file[PATH_MAX];
3021 FILE *f;
3022 char *uri = NULL, *title = NULL;
3023 size_t len, lineno = 0;
3024 int i, failed = 0;
3025 char *body, *tmp, *page = NULL;
3026 const char delim[3] = {'\\', '\\', '\0'};
3028 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3030 if (t == NULL)
3031 warn("%s: bad param", __func__);
3033 /* new session key */
3034 if (!updating_fl_tabs)
3035 generate_xtp_session_key(&fl_session_key);
3037 /* open favorites */
3038 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3039 if ((f = fopen(file, "r")) == NULL) {
3040 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3041 return (1);
3044 /* body */
3045 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3046 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3047 "<th style='width: 40px'>Rm</th></tr>\n");
3049 for (i = 1;;) {
3050 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3051 if (feof(f) || ferror(f))
3052 break;
3053 if (strlen(title) == 0 || title[0] == '#') {
3054 free(title);
3055 title = NULL;
3056 continue;
3059 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3060 if (feof(f) || ferror(f)) {
3061 show_oops(t, "favorites file corrupt");
3062 failed = 1;
3063 break;
3066 tmp = body;
3067 body = g_strdup_printf("%s<tr>"
3068 "<td>%d</td>"
3069 "<td><a href='%s'>%s</a></td>"
3070 "<td style='text-align: center'>"
3071 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3072 "</tr>\n",
3073 body, i, uri, title,
3074 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3076 g_free(tmp);
3078 free(uri);
3079 uri = NULL;
3080 free(title);
3081 title = NULL;
3082 i++;
3084 fclose(f);
3086 /* if none, say so */
3087 if (i == 1) {
3088 tmp = body;
3089 body = g_strdup_printf("%s<tr>"
3090 "<td colspan='3' style='text-align: center'>"
3091 "No favorites - To add one use the 'favadd' command."
3092 "</td></tr>", body);
3093 g_free(tmp);
3096 tmp = body;
3097 body = g_strdup_printf("%s</table>", body);
3098 g_free(tmp);
3100 if (uri)
3101 free(uri);
3102 if (title)
3103 free(title);
3105 /* render */
3106 if (!failed) {
3107 page = get_html_page("Favorites", body, "", 1);
3108 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3109 g_free(page);
3112 update_favorite_tabs(t);
3114 if (body)
3115 g_free(body);
3117 return (failed);
3120 void
3121 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3122 size_t cert_count, char *title)
3124 gnutls_datum_t cinfo;
3125 char *tmp, *body;
3126 int i;
3128 body = g_strdup("");
3130 for (i = 0; i < cert_count; i++) {
3131 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3132 &cinfo))
3133 return;
3135 tmp = body;
3136 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3137 body, i, cinfo.data);
3138 gnutls_free(cinfo.data);
3139 g_free(tmp);
3142 tmp = get_html_page(title, body, "", 0);
3143 g_free(body);
3145 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3146 g_free(tmp);
3150 ca_cmd(struct tab *t, struct karg *args)
3152 FILE *f = NULL;
3153 int rv = 1, certs = 0, certs_read;
3154 struct stat sb;
3155 gnutls_datum_t dt;
3156 gnutls_x509_crt_t *c = NULL;
3157 char *certs_buf = NULL, *s;
3159 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3160 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3161 return (1);
3164 if (fstat(fileno(f), &sb) == -1) {
3165 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3166 goto done;
3169 certs_buf = g_malloc(sb.st_size + 1);
3170 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3171 show_oops(t, "Can't read CA file: %s", strerror(errno));
3172 goto done;
3174 certs_buf[sb.st_size] = '\0';
3176 s = certs_buf;
3177 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3178 certs++;
3179 s += strlen("BEGIN CERTIFICATE");
3182 bzero(&dt, sizeof dt);
3183 dt.data = (unsigned char *)certs_buf;
3184 dt.size = sb.st_size;
3185 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3186 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3187 GNUTLS_X509_FMT_PEM, 0);
3188 if (certs_read <= 0) {
3189 show_oops(t, "No cert(s) available");
3190 goto done;
3192 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3193 done:
3194 if (c)
3195 g_free(c);
3196 if (certs_buf)
3197 g_free(certs_buf);
3198 if (f)
3199 fclose(f);
3201 return (rv);
3205 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3206 size_t domain_sz)
3208 SoupURI *su = NULL;
3209 struct addrinfo hints, *res = NULL, *ai;
3210 int rv = -1, s = -1, on, error;
3211 char port[8];
3213 if (uri && !g_str_has_prefix(uri, "https://")) {
3214 show_oops(t, "invalid URI");
3215 goto done;
3218 su = soup_uri_new(uri);
3219 if (su == NULL) {
3220 show_oops(t, "invalid soup URI");
3221 goto done;
3223 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3224 show_oops(t, "invalid HTTPS URI");
3225 goto done;
3228 snprintf(port, sizeof port, "%d", su->port);
3229 bzero(&hints, sizeof(struct addrinfo));
3230 hints.ai_flags = AI_CANONNAME;
3231 hints.ai_family = AF_UNSPEC;
3232 hints.ai_socktype = SOCK_STREAM;
3234 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3235 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3236 goto done;
3239 for (ai = res; ai; ai = ai->ai_next) {
3240 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3241 continue;
3243 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3244 if (s == -1) {
3245 show_oops(t, "socket failed: %s", strerror(errno));
3246 goto done;
3248 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3249 sizeof(on)) == -1) {
3250 show_oops(t, "setsockopt failed: %s", strerror(errno));
3251 goto done;
3253 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3254 show_oops(t, "connect failed: %s", strerror(errno));
3255 goto done;
3258 break;
3261 if (domain)
3262 strlcpy(domain, su->host, domain_sz);
3263 rv = s;
3264 done:
3265 if (su)
3266 soup_uri_free(su);
3267 if (res)
3268 freeaddrinfo(res);
3269 if (rv == -1 && s != -1)
3270 close(s);
3272 return (rv);
3276 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3278 if (gsession)
3279 gnutls_deinit(gsession);
3280 if (xcred)
3281 gnutls_certificate_free_credentials(xcred);
3283 return (0);
3287 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3288 gnutls_certificate_credentials_t *xc)
3290 gnutls_certificate_credentials_t xcred;
3291 gnutls_session_t gsession;
3292 int rv = 1;
3294 if (gs == NULL || xc == NULL)
3295 goto done;
3297 *gs = NULL;
3298 *xc = NULL;
3300 gnutls_certificate_allocate_credentials(&xcred);
3301 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3302 GNUTLS_X509_FMT_PEM);
3304 gnutls_init(&gsession, GNUTLS_CLIENT);
3305 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3306 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3307 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3308 if ((rv = gnutls_handshake(gsession)) < 0) {
3309 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3311 gnutls_error_is_fatal(rv),
3312 gnutls_strerror_name(rv));
3313 stop_tls(gsession, xcred);
3314 goto done;
3317 gnutls_credentials_type_t cred;
3318 cred = gnutls_auth_get_type(gsession);
3319 if (cred != GNUTLS_CRD_CERTIFICATE) {
3320 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3321 stop_tls(gsession, xcred);
3322 goto done;
3325 *gs = gsession;
3326 *xc = xcred;
3327 rv = 0;
3328 done:
3329 return (rv);
3333 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3334 size_t *cert_count)
3336 unsigned int len;
3337 const gnutls_datum_t *cl;
3338 gnutls_x509_crt_t *all_certs;
3339 int i, rv = 1;
3341 if (certs == NULL || cert_count == NULL)
3342 goto done;
3343 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3344 goto done;
3345 cl = gnutls_certificate_get_peers(gsession, &len);
3346 if (len == 0)
3347 goto done;
3349 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3350 for (i = 0; i < len; i++) {
3351 gnutls_x509_crt_init(&all_certs[i]);
3352 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3353 GNUTLS_X509_FMT_PEM < 0)) {
3354 g_free(all_certs);
3355 goto done;
3359 *certs = all_certs;
3360 *cert_count = len;
3361 rv = 0;
3362 done:
3363 return (rv);
3366 void
3367 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3369 int i;
3371 for (i = 0; i < cert_count; i++)
3372 gnutls_x509_crt_deinit(certs[i]);
3373 g_free(certs);
3376 void
3377 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3379 GdkColor c_text, c_base;
3381 gdk_color_parse(text, &c_text);
3382 gdk_color_parse(base, &c_base);
3384 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3385 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3386 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3387 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3389 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3390 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3391 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3392 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3395 void
3396 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3397 size_t cert_count, char *domain)
3399 size_t cert_buf_sz;
3400 char cert_buf[64 * 1024], file[PATH_MAX];
3401 int i;
3402 FILE *f;
3403 GdkColor color;
3405 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3406 return;
3408 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3409 if ((f = fopen(file, "w")) == NULL) {
3410 show_oops(t, "Can't create cert file %s %s",
3411 file, strerror(errno));
3412 return;
3415 for (i = 0; i < cert_count; i++) {
3416 cert_buf_sz = sizeof cert_buf;
3417 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3418 cert_buf, &cert_buf_sz)) {
3419 show_oops(t, "gnutls_x509_crt_export failed");
3420 goto done;
3422 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3423 show_oops(t, "Can't write certs: %s", strerror(errno));
3424 goto done;
3428 /* not the best spot but oh well */
3429 gdk_color_parse("lightblue", &color);
3430 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3431 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3432 done:
3433 fclose(f);
3436 enum cert_trust {
3437 CERT_LOCAL,
3438 CERT_TRUSTED,
3439 CERT_UNTRUSTED,
3440 CERT_BAD
3443 enum cert_trust
3444 load_compare_cert(struct tab *t, struct karg *args)
3446 const gchar *uri;
3447 char domain[8182], file[PATH_MAX];
3448 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3449 int s = -1, i, error;
3450 FILE *f = NULL;
3451 size_t cert_buf_sz, cert_count;
3452 enum cert_trust rv = CERT_UNTRUSTED;
3453 char serr[80];
3454 gnutls_session_t gsession;
3455 gnutls_x509_crt_t *certs;
3456 gnutls_certificate_credentials_t xcred;
3458 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3460 if (t == NULL)
3461 return (rv);
3463 if ((uri = get_uri(t)) == NULL)
3464 return (rv);
3465 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3467 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3468 return (rv);
3469 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3471 /* go ssl/tls */
3472 if (start_tls(t, s, &gsession, &xcred))
3473 goto done;
3474 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3476 /* verify certs in case cert file doesn't exist */
3477 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3478 GNUTLS_E_SUCCESS) {
3479 show_oops(t, "Invalid certificates");
3480 goto done;
3483 /* get certs */
3484 if (get_connection_certs(gsession, &certs, &cert_count)) {
3485 show_oops(t, "Can't get connection certificates");
3486 goto done;
3489 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3490 if ((f = fopen(file, "r")) == NULL) {
3491 if (!error)
3492 rv = CERT_TRUSTED;
3493 goto freeit;
3496 for (i = 0; i < cert_count; i++) {
3497 cert_buf_sz = sizeof cert_buf;
3498 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3499 cert_buf, &cert_buf_sz)) {
3500 goto freeit;
3502 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3503 rv = CERT_BAD; /* critical */
3504 goto freeit;
3506 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3507 rv = CERT_BAD; /* critical */
3508 goto freeit;
3510 rv = CERT_LOCAL;
3513 freeit:
3514 if (f)
3515 fclose(f);
3516 free_connection_certs(certs, cert_count);
3517 done:
3518 /* we close the socket first for speed */
3519 if (s != -1)
3520 close(s);
3522 /* only complain if we didn't save it locally */
3523 if (error && rv != CERT_LOCAL) {
3524 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3525 if (error & GNUTLS_CERT_INVALID)
3526 strlcat(serr, "invalid, ", sizeof serr);
3527 if (error & GNUTLS_CERT_REVOKED)
3528 strlcat(serr, "revoked, ", sizeof serr);
3529 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3530 strlcat(serr, "signer not found, ", sizeof serr);
3531 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3532 strlcat(serr, "not signed by CA, ", sizeof serr);
3533 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3534 strlcat(serr, "insecure algorithm, ", sizeof serr);
3535 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3536 strlcat(serr, "not activated, ", sizeof serr);
3537 if (error & GNUTLS_CERT_EXPIRED)
3538 strlcat(serr, "expired, ", sizeof serr);
3539 for (i = strlen(serr) - 1; i > 0; i--)
3540 if (serr[i] == ',') {
3541 serr[i] = '\0';
3542 break;
3544 show_oops(t, serr);
3547 stop_tls(gsession, xcred);
3549 return (rv);
3553 cert_cmd(struct tab *t, struct karg *args)
3555 const gchar *uri;
3556 char domain[8182];
3557 int s = -1;
3558 size_t cert_count;
3559 gnutls_session_t gsession;
3560 gnutls_x509_crt_t *certs;
3561 gnutls_certificate_credentials_t xcred;
3563 if (t == NULL)
3564 return (1);
3566 if (ssl_ca_file == NULL) {
3567 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3568 return (1);
3571 if ((uri = get_uri(t)) == NULL) {
3572 show_oops(t, "Invalid URI");
3573 return (1);
3576 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3577 show_oops(t, "Invalid certificate URI: %s", uri);
3578 return (1);
3581 /* go ssl/tls */
3582 if (start_tls(t, s, &gsession, &xcred))
3583 goto done;
3585 /* get certs */
3586 if (get_connection_certs(gsession, &certs, &cert_count)) {
3587 show_oops(t, "get_connection_certs failed");
3588 goto done;
3591 if (args->i & XT_SHOW)
3592 show_certs(t, certs, cert_count, "Certificate Chain");
3593 else if (args->i & XT_SAVE)
3594 save_certs(t, certs, cert_count, domain);
3596 free_connection_certs(certs, cert_count);
3597 done:
3598 /* we close the socket first for speed */
3599 if (s != -1)
3600 close(s);
3601 stop_tls(gsession, xcred);
3603 return (0);
3607 remove_cookie(int index)
3609 int i, rv = 1;
3610 GSList *cf;
3611 SoupCookie *c;
3613 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3615 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3617 for (i = 1; cf; cf = cf->next, i++) {
3618 if (i != index)
3619 continue;
3620 c = cf->data;
3621 print_cookie("remove cookie", c);
3622 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3623 rv = 0;
3624 break;
3627 soup_cookies_free(cf);
3629 return (rv);
3633 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3635 struct domain *d;
3636 char *tmp, *body;
3638 body = g_strdup("");
3640 /* p list */
3641 if (args->i & XT_WL_PERSISTENT) {
3642 tmp = body;
3643 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3644 g_free(tmp);
3645 RB_FOREACH(d, domain_list, wl) {
3646 if (d->handy == 0)
3647 continue;
3648 tmp = body;
3649 body = g_strdup_printf("%s%s<br/>", body, d->d);
3650 g_free(tmp);
3654 /* s list */
3655 if (args->i & XT_WL_SESSION) {
3656 tmp = body;
3657 body = g_strdup_printf("%s<h2>Session</h2>", body);
3658 g_free(tmp);
3659 RB_FOREACH(d, domain_list, wl) {
3660 if (d->handy == 1)
3661 continue;
3662 tmp = body;
3663 body = g_strdup_printf("%s%s<br/>", body, d->d);
3664 g_free(tmp);
3668 tmp = get_html_page(title, body, "", 0);
3669 g_free(body);
3670 if (wl == &js_wl)
3671 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3672 else
3673 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3674 g_free(tmp);
3675 return (0);
3679 wl_save(struct tab *t, struct karg *args, int js)
3681 char file[PATH_MAX];
3682 FILE *f;
3683 char *line = NULL, *lt = NULL, *dom = NULL;
3684 size_t linelen;
3685 const gchar *uri;
3686 struct karg a;
3687 struct domain *d;
3688 GSList *cf;
3689 SoupCookie *ci, *c;
3691 if (t == NULL || args == NULL)
3692 return (1);
3694 if (runtime_settings[0] == '\0')
3695 return (1);
3697 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3698 if ((f = fopen(file, "r+")) == NULL)
3699 return (1);
3701 uri = get_uri(t);
3702 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3703 if (uri == NULL || dom == NULL ||
3704 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3705 show_oops(t, "Can't add domain to %s white list",
3706 js ? "JavaScript" : "cookie");
3707 goto done;
3710 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3712 while (!feof(f)) {
3713 line = fparseln(f, &linelen, NULL, NULL, 0);
3714 if (line == NULL)
3715 continue;
3716 if (!strcmp(line, lt))
3717 goto done;
3718 free(line);
3719 line = NULL;
3722 fprintf(f, "%s\n", lt);
3724 a.i = XT_WL_ENABLE;
3725 a.i |= args->i;
3726 if (js) {
3727 d = wl_find(dom, &js_wl);
3728 if (!d) {
3729 settings_add("js_wl", dom);
3730 d = wl_find(dom, &js_wl);
3732 toggle_js(t, &a);
3733 } else {
3734 d = wl_find(dom, &c_wl);
3735 if (!d) {
3736 settings_add("cookie_wl", dom);
3737 d = wl_find(dom, &c_wl);
3739 toggle_cwl(t, &a);
3741 /* find and add to persistent jar */
3742 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3743 for (;cf; cf = cf->next) {
3744 ci = cf->data;
3745 if (!strcmp(dom, ci->domain) ||
3746 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3747 c = soup_cookie_copy(ci);
3748 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3751 soup_cookies_free(cf);
3753 if (d)
3754 d->handy = 1;
3756 done:
3757 if (line)
3758 free(line);
3759 if (dom)
3760 g_free(dom);
3761 if (lt)
3762 g_free(lt);
3763 fclose(f);
3765 return (0);
3769 js_show_wl(struct tab *t, struct karg *args)
3771 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3772 wl_show(t, args, "JavaScript White List", &js_wl);
3774 return (0);
3778 cookie_show_wl(struct tab *t, struct karg *args)
3780 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3781 wl_show(t, args, "Cookie White List", &c_wl);
3783 return (0);
3787 cookie_cmd(struct tab *t, struct karg *args)
3789 if (args->i & XT_SHOW)
3790 wl_show(t, args, "Cookie White List", &c_wl);
3791 else if (args->i & XT_WL_TOGGLE) {
3792 args->i |= XT_WL_RELOAD;
3793 toggle_cwl(t, args);
3794 } else if (args->i & XT_SAVE) {
3795 args->i |= XT_WL_RELOAD;
3796 wl_save(t, args, 0);
3797 } else if (args->i & XT_DELETE)
3798 show_oops(t, "'cookie delete' currently unimplemented");
3800 return (0);
3804 js_cmd(struct tab *t, struct karg *args)
3806 if (args->i & XT_SHOW)
3807 wl_show(t, args, "JavaScript White List", &js_wl);
3808 else if (args->i & XT_SAVE) {
3809 args->i |= XT_WL_RELOAD;
3810 wl_save(t, args, 1);
3811 } else if (args->i & XT_WL_TOGGLE) {
3812 args->i |= XT_WL_RELOAD;
3813 toggle_js(t, args);
3814 } else if (args->i & XT_DELETE)
3815 show_oops(t, "'js delete' currently unimplemented");
3817 return (0);
3821 toplevel_cmd(struct tab *t, struct karg *args)
3823 js_toggle_cb(t->js_toggle, t);
3825 return (0);
3829 add_favorite(struct tab *t, struct karg *args)
3831 char file[PATH_MAX];
3832 FILE *f;
3833 char *line = NULL;
3834 size_t urilen, linelen;
3835 const gchar *uri, *title;
3837 if (t == NULL)
3838 return (1);
3840 /* don't allow adding of xtp pages to favorites */
3841 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3842 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3843 return (1);
3846 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3847 if ((f = fopen(file, "r+")) == NULL) {
3848 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3849 return (1);
3852 title = get_title(t, FALSE);
3853 uri = get_uri(t);
3855 if (title == NULL || uri == NULL) {
3856 show_oops(t, "can't add page to favorites");
3857 goto done;
3860 urilen = strlen(uri);
3862 for (;;) {
3863 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3864 if (feof(f) || ferror(f))
3865 break;
3867 if (linelen == urilen && !strcmp(line, uri))
3868 goto done;
3870 free(line);
3871 line = NULL;
3874 fprintf(f, "\n%s\n%s", title, uri);
3875 done:
3876 if (line)
3877 free(line);
3878 fclose(f);
3880 update_favorite_tabs(NULL);
3882 return (0);
3886 navaction(struct tab *t, struct karg *args)
3888 WebKitWebHistoryItem *item;
3890 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3891 t->tab_id, args->i);
3893 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3895 if (t->item) {
3896 if (args->i == XT_NAV_BACK)
3897 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3898 else
3899 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3900 if (item == NULL)
3901 return (XT_CB_PASSTHROUGH);
3902 webkit_web_view_go_to_back_forward_item(t->wv, item);
3903 t->item = NULL;
3904 return (XT_CB_PASSTHROUGH);
3907 switch (args->i) {
3908 case XT_NAV_BACK:
3909 marks_clear(t);
3910 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3911 if (item)
3912 webkit_web_view_go_to_back_forward_item(t->wv, item);
3913 break;
3914 case XT_NAV_FORWARD:
3915 marks_clear(t);
3916 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3917 if (item)
3918 webkit_web_view_go_to_back_forward_item(t->wv, item);
3919 break;
3920 case XT_NAV_RELOAD:
3921 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3922 if (item)
3923 webkit_web_view_go_to_back_forward_item(t->wv, item);
3924 break;
3926 return (XT_CB_PASSTHROUGH);
3930 move(struct tab *t, struct karg *args)
3932 GtkAdjustment *adjust;
3933 double pi, si, pos, ps, upper, lower, max;
3934 double percent;
3936 switch (args->i) {
3937 case XT_MOVE_DOWN:
3938 case XT_MOVE_UP:
3939 case XT_MOVE_BOTTOM:
3940 case XT_MOVE_TOP:
3941 case XT_MOVE_PAGEDOWN:
3942 case XT_MOVE_PAGEUP:
3943 case XT_MOVE_HALFDOWN:
3944 case XT_MOVE_HALFUP:
3945 case XT_MOVE_PERCENT:
3946 adjust = t->adjust_v;
3947 break;
3948 default:
3949 adjust = t->adjust_h;
3950 break;
3953 pos = gtk_adjustment_get_value(adjust);
3954 ps = gtk_adjustment_get_page_size(adjust);
3955 upper = gtk_adjustment_get_upper(adjust);
3956 lower = gtk_adjustment_get_lower(adjust);
3957 si = gtk_adjustment_get_step_increment(adjust);
3958 pi = gtk_adjustment_get_page_increment(adjust);
3959 max = upper - ps;
3961 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3962 "max %f si %f pi %f\n",
3963 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3964 pos, ps, upper, lower, max, si, pi);
3966 switch (args->i) {
3967 case XT_MOVE_DOWN:
3968 case XT_MOVE_RIGHT:
3969 pos += si;
3970 gtk_adjustment_set_value(adjust, MIN(pos, max));
3971 break;
3972 case XT_MOVE_UP:
3973 case XT_MOVE_LEFT:
3974 pos -= si;
3975 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3976 break;
3977 case XT_MOVE_BOTTOM:
3978 case XT_MOVE_FARRIGHT:
3979 gtk_adjustment_set_value(adjust, max);
3980 break;
3981 case XT_MOVE_TOP:
3982 case XT_MOVE_FARLEFT:
3983 gtk_adjustment_set_value(adjust, lower);
3984 break;
3985 case XT_MOVE_PAGEDOWN:
3986 pos += pi;
3987 gtk_adjustment_set_value(adjust, MIN(pos, max));
3988 break;
3989 case XT_MOVE_PAGEUP:
3990 pos -= pi;
3991 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3992 break;
3993 case XT_MOVE_HALFDOWN:
3994 pos += pi / 2;
3995 gtk_adjustment_set_value(adjust, MIN(pos, max));
3996 break;
3997 case XT_MOVE_HALFUP:
3998 pos -= pi / 2;
3999 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4000 break;
4001 case XT_MOVE_PERCENT:
4002 percent = atoi(args->s) / 100.0;
4003 pos = max * percent;
4004 if (pos < 0.0 || pos > max)
4005 break;
4006 gtk_adjustment_set_value(adjust, pos);
4007 break;
4008 default:
4009 return (XT_CB_PASSTHROUGH);
4012 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4014 return (XT_CB_HANDLED);
4017 void
4018 url_set_visibility(void)
4020 struct tab *t;
4022 TAILQ_FOREACH(t, &tabs, entry)
4023 if (show_url == 0) {
4024 gtk_widget_hide(t->toolbar);
4025 focus_webview(t);
4026 } else
4027 gtk_widget_show(t->toolbar);
4030 void
4031 notebook_tab_set_visibility(void)
4033 if (show_tabs == 0) {
4034 gtk_widget_hide(tab_bar);
4035 gtk_notebook_set_show_tabs(notebook, FALSE);
4036 } else {
4037 if (tab_style == XT_TABS_NORMAL) {
4038 gtk_widget_hide(tab_bar);
4039 gtk_notebook_set_show_tabs(notebook, TRUE);
4040 } else if (tab_style == XT_TABS_COMPACT) {
4041 gtk_widget_show(tab_bar);
4042 gtk_notebook_set_show_tabs(notebook, FALSE);
4047 void
4048 statusbar_set_visibility(void)
4050 struct tab *t;
4052 TAILQ_FOREACH(t, &tabs, entry)
4053 if (show_statusbar == 0) {
4054 gtk_widget_hide(t->statusbar_box);
4055 focus_webview(t);
4056 } else
4057 gtk_widget_show(t->statusbar_box);
4060 void
4061 url_set(struct tab *t, int enable_url_entry)
4063 GdkPixbuf *pixbuf;
4064 int progress;
4066 show_url = enable_url_entry;
4068 if (enable_url_entry) {
4069 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4070 GTK_ENTRY_ICON_PRIMARY, NULL);
4071 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4072 } else {
4073 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4074 GTK_ENTRY_ICON_PRIMARY);
4075 progress =
4076 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4077 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4078 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4079 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4080 progress);
4085 fullscreen(struct tab *t, struct karg *args)
4087 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4089 if (t == NULL)
4090 return (XT_CB_PASSTHROUGH);
4092 if (show_url == 0) {
4093 url_set(t, 1);
4094 show_tabs = 1;
4095 } else {
4096 url_set(t, 0);
4097 show_tabs = 0;
4100 url_set_visibility();
4101 notebook_tab_set_visibility();
4103 return (XT_CB_HANDLED);
4107 statustoggle(struct tab *t, struct karg *args)
4109 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4111 if (show_statusbar == 1) {
4112 show_statusbar = 0;
4113 statusbar_set_visibility();
4114 } else if (show_statusbar == 0) {
4115 show_statusbar = 1;
4116 statusbar_set_visibility();
4118 return (XT_CB_HANDLED);
4122 urlaction(struct tab *t, struct karg *args)
4124 int rv = XT_CB_HANDLED;
4126 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4128 if (t == NULL)
4129 return (XT_CB_PASSTHROUGH);
4131 switch (args->i) {
4132 case XT_URL_SHOW:
4133 if (show_url == 0) {
4134 url_set(t, 1);
4135 url_set_visibility();
4137 break;
4138 case XT_URL_HIDE:
4139 if (show_url == 1) {
4140 url_set(t, 0);
4141 url_set_visibility();
4143 break;
4145 return (rv);
4149 tabaction(struct tab *t, struct karg *args)
4151 int rv = XT_CB_HANDLED;
4152 char *url = args->s;
4153 struct undo *u;
4154 struct tab *tt;
4156 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4158 if (t == NULL)
4159 return (XT_CB_PASSTHROUGH);
4161 switch (args->i) {
4162 case XT_TAB_NEW:
4163 if (strlen(url) > 0)
4164 create_new_tab(url, NULL, 1, args->precount);
4165 else
4166 create_new_tab(NULL, NULL, 1, args->precount);
4167 break;
4168 case XT_TAB_DELETE:
4169 if (args->precount < 0)
4170 delete_tab(t);
4171 else
4172 TAILQ_FOREACH(tt, &tabs, entry)
4173 if (tt->tab_id == args->precount - 1) {
4174 delete_tab(tt);
4175 break;
4177 break;
4178 case XT_TAB_DELQUIT:
4179 if (gtk_notebook_get_n_pages(notebook) > 1)
4180 delete_tab(t);
4181 else
4182 quit(t, args);
4183 break;
4184 case XT_TAB_OPEN:
4185 if (strlen(url) > 0)
4187 else {
4188 rv = XT_CB_PASSTHROUGH;
4189 goto done;
4191 load_uri(t, url);
4192 break;
4193 case XT_TAB_SHOW:
4194 if (show_tabs == 0) {
4195 show_tabs = 1;
4196 notebook_tab_set_visibility();
4198 break;
4199 case XT_TAB_HIDE:
4200 if (show_tabs == 1) {
4201 show_tabs = 0;
4202 notebook_tab_set_visibility();
4204 break;
4205 case XT_TAB_NEXTSTYLE:
4206 if (tab_style == XT_TABS_NORMAL) {
4207 tab_style = XT_TABS_COMPACT;
4208 recolor_compact_tabs();
4210 else
4211 tab_style = XT_TABS_NORMAL;
4212 notebook_tab_set_visibility();
4213 break;
4214 case XT_TAB_UNDO_CLOSE:
4215 if (undo_count == 0) {
4216 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4217 __func__);
4218 goto done;
4219 } else {
4220 undo_count--;
4221 u = TAILQ_FIRST(&undos);
4222 create_new_tab(u->uri, u, 1, -1);
4224 TAILQ_REMOVE(&undos, u, entry);
4225 g_free(u->uri);
4226 /* u->history is freed in create_new_tab() */
4227 g_free(u);
4229 break;
4230 default:
4231 rv = XT_CB_PASSTHROUGH;
4232 goto done;
4235 done:
4236 if (args->s) {
4237 g_free(args->s);
4238 args->s = NULL;
4241 return (rv);
4245 resizetab(struct tab *t, struct karg *args)
4247 if (t == NULL || args == NULL) {
4248 show_oops(NULL, "resizetab invalid parameters");
4249 return (XT_CB_PASSTHROUGH);
4252 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4253 t->tab_id, args->i);
4255 setzoom_webkit(t, args->i);
4257 return (XT_CB_HANDLED);
4261 movetab(struct tab *t, struct karg *args)
4263 int n, dest;
4265 if (t == NULL || args == NULL) {
4266 show_oops(NULL, "movetab invalid parameters");
4267 return (XT_CB_PASSTHROUGH);
4270 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4271 t->tab_id, args->i);
4273 if (args->i >= XT_TAB_INVALID)
4274 return (XT_CB_PASSTHROUGH);
4276 if (TAILQ_EMPTY(&tabs))
4277 return (XT_CB_PASSTHROUGH);
4279 n = gtk_notebook_get_n_pages(notebook);
4280 dest = gtk_notebook_get_current_page(notebook);
4282 switch (args->i) {
4283 case XT_TAB_NEXT:
4284 if (args->precount < 0)
4285 dest = dest == n - 1 ? 0 : dest + 1;
4286 else
4287 dest = args->precount - 1;
4289 break;
4290 case XT_TAB_PREV:
4291 if (args->precount < 0)
4292 dest -= 1;
4293 else
4294 dest -= args->precount % n;
4296 if (dest < 0)
4297 dest += n;
4299 break;
4300 case XT_TAB_FIRST:
4301 dest = 0;
4302 break;
4303 case XT_TAB_LAST:
4304 dest = n - 1;
4305 break;
4306 default:
4307 return (XT_CB_PASSTHROUGH);
4310 if (dest < 0 || dest >= n)
4311 return (XT_CB_PASSTHROUGH);
4312 if (t->tab_id == dest) {
4313 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4314 return (XT_CB_HANDLED);
4317 set_current_tab(dest);
4319 return (XT_CB_HANDLED);
4322 int cmd_prefix = 0;
4325 command(struct tab *t, struct karg *args)
4327 char *s = NULL, *ss = NULL;
4328 GdkColor color;
4329 const gchar *uri;
4331 if (t == NULL || args == NULL) {
4332 show_oops(NULL, "command invalid parameters");
4333 return (XT_CB_PASSTHROUGH);
4336 switch (args->i) {
4337 case '/':
4338 s = "/";
4339 break;
4340 case '?':
4341 s = "?";
4342 break;
4343 case ':':
4344 if (cmd_prefix == 0)
4345 s = ":";
4346 else {
4347 ss = g_strdup_printf(":%d", cmd_prefix);
4348 s = ss;
4349 cmd_prefix = 0;
4351 break;
4352 case XT_CMD_OPEN:
4353 s = ":open ";
4354 break;
4355 case XT_CMD_TABNEW:
4356 s = ":tabnew ";
4357 break;
4358 case XT_CMD_OPEN_CURRENT:
4359 s = ":open ";
4360 /* FALL THROUGH */
4361 case XT_CMD_TABNEW_CURRENT:
4362 if (!s) /* FALL THROUGH? */
4363 s = ":tabnew ";
4364 if ((uri = get_uri(t)) != NULL) {
4365 ss = g_strdup_printf("%s%s", s, uri);
4366 s = ss;
4368 break;
4369 default:
4370 show_oops(t, "command: invalid opcode %d", args->i);
4371 return (XT_CB_PASSTHROUGH);
4374 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4376 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4377 gdk_color_parse(XT_COLOR_WHITE, &color);
4378 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4379 show_cmd(t);
4380 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4381 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4383 if (ss)
4384 g_free(ss);
4386 return (XT_CB_HANDLED);
4390 * Return a new string with a download row (in html)
4391 * appended. Old string is freed.
4393 char *
4394 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4397 WebKitDownloadStatus stat;
4398 char *status_html = NULL, *cmd_html = NULL, *new_html;
4399 gdouble progress;
4400 char cur_sz[FMT_SCALED_STRSIZE];
4401 char tot_sz[FMT_SCALED_STRSIZE];
4402 char *xtp_prefix;
4404 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4406 /* All actions wil take this form:
4407 * xxxt://class/seskey
4409 xtp_prefix = g_strdup_printf("%s%d/%s/",
4410 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4412 stat = webkit_download_get_status(dl->download);
4414 switch (stat) {
4415 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4416 status_html = g_strdup_printf("Finished");
4417 cmd_html = g_strdup_printf(
4418 "<a href='%s%d/%d'>Remove</a>",
4419 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4420 break;
4421 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4422 /* gather size info */
4423 progress = 100 * webkit_download_get_progress(dl->download);
4425 fmt_scaled(
4426 webkit_download_get_current_size(dl->download), cur_sz);
4427 fmt_scaled(
4428 webkit_download_get_total_size(dl->download), tot_sz);
4430 status_html = g_strdup_printf(
4431 "<div style='width: 100%%' align='center'>"
4432 "<div class='progress-outer'>"
4433 "<div class='progress-inner' style='width: %.2f%%'>"
4434 "</div></div></div>"
4435 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4436 progress, cur_sz, tot_sz, progress);
4438 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4439 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4441 break;
4442 /* LLL */
4443 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4444 status_html = g_strdup_printf("Cancelled");
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_ERROR:
4449 status_html = g_strdup_printf("Error!");
4450 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4451 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4452 break;
4453 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4454 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4455 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4456 status_html = g_strdup_printf("Starting");
4457 break;
4458 default:
4459 show_oops(t, "%s: unknown download status", __func__);
4462 new_html = g_strdup_printf(
4463 "%s\n<tr><td>%s</td><td>%s</td>"
4464 "<td style='text-align:center'>%s</td></tr>\n",
4465 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4466 status_html, cmd_html);
4467 g_free(html);
4469 if (status_html)
4470 g_free(status_html);
4472 if (cmd_html)
4473 g_free(cmd_html);
4475 g_free(xtp_prefix);
4477 return new_html;
4481 * update all download tabs apart from one. Pass NULL if
4482 * you want to update all.
4484 void
4485 update_download_tabs(struct tab *apart_from)
4487 struct tab *t;
4488 if (!updating_dl_tabs) {
4489 updating_dl_tabs = 1; /* stop infinite recursion */
4490 TAILQ_FOREACH(t, &tabs, entry)
4491 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4492 && (t != apart_from))
4493 xtp_page_dl(t, NULL);
4494 updating_dl_tabs = 0;
4499 * update all cookie tabs apart from one. Pass NULL if
4500 * you want to update all.
4502 void
4503 update_cookie_tabs(struct tab *apart_from)
4505 struct tab *t;
4506 if (!updating_cl_tabs) {
4507 updating_cl_tabs = 1; /* stop infinite recursion */
4508 TAILQ_FOREACH(t, &tabs, entry)
4509 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4510 && (t != apart_from))
4511 xtp_page_cl(t, NULL);
4512 updating_cl_tabs = 0;
4517 * update all history tabs apart from one. Pass NULL if
4518 * you want to update all.
4520 void
4521 update_history_tabs(struct tab *apart_from)
4523 struct tab *t;
4525 if (!updating_hl_tabs) {
4526 updating_hl_tabs = 1; /* stop infinite recursion */
4527 TAILQ_FOREACH(t, &tabs, entry)
4528 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4529 && (t != apart_from))
4530 xtp_page_hl(t, NULL);
4531 updating_hl_tabs = 0;
4535 /* cookie management XTP page */
4537 xtp_page_cl(struct tab *t, struct karg *args)
4539 char *body, *page, *tmp;
4540 int i = 1; /* all ids start 1 */
4541 GSList *sc, *pc, *pc_start;
4542 SoupCookie *c;
4543 char *type, *table_headers, *last_domain;
4545 DNPRINTF(XT_D_CMD, "%s", __func__);
4547 if (t == NULL) {
4548 show_oops(NULL, "%s invalid parameters", __func__);
4549 return (1);
4552 /* Generate a new session key */
4553 if (!updating_cl_tabs)
4554 generate_xtp_session_key(&cl_session_key);
4556 /* table headers */
4557 table_headers = g_strdup_printf("<table><tr>"
4558 "<th>Type</th>"
4559 "<th>Name</th>"
4560 "<th style='width:200px'>Value</th>"
4561 "<th>Path</th>"
4562 "<th>Expires</th>"
4563 "<th>Secure</th>"
4564 "<th>HTTP<br />only</th>"
4565 "<th style='width:40px'>Rm</th></tr>\n");
4567 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4568 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4569 pc_start = pc;
4571 body = NULL;
4572 last_domain = strdup("");
4573 for (; sc; sc = sc->next) {
4574 c = sc->data;
4576 if (strcmp(last_domain, c->domain) != 0) {
4577 /* new domain */
4578 free(last_domain);
4579 last_domain = strdup(c->domain);
4581 if (body != NULL) {
4582 tmp = body;
4583 body = g_strdup_printf("%s</table>"
4584 "<h2>%s</h2>%s\n",
4585 body, c->domain, table_headers);
4586 g_free(tmp);
4587 } else {
4588 /* first domain */
4589 body = g_strdup_printf("<h2>%s</h2>%s\n",
4590 c->domain, table_headers);
4594 type = "Session";
4595 for (pc = pc_start; pc; pc = pc->next)
4596 if (soup_cookie_equal(pc->data, c)) {
4597 type = "Session + Persistent";
4598 break;
4601 tmp = body;
4602 body = g_strdup_printf(
4603 "%s\n<tr>"
4604 "<td>%s</td>"
4605 "<td style='word-wrap:normal'>%s</td>"
4606 "<td>"
4607 " <textarea rows='4'>%s</textarea>"
4608 "</td>"
4609 "<td>%s</td>"
4610 "<td>%s</td>"
4611 "<td>%d</td>"
4612 "<td>%d</td>"
4613 "<td style='text-align:center'>"
4614 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4615 body,
4616 type,
4617 c->name,
4618 c->value,
4619 c->path,
4620 c->expires ?
4621 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4622 c->secure,
4623 c->http_only,
4625 XT_XTP_STR,
4626 XT_XTP_CL,
4627 cl_session_key,
4628 XT_XTP_CL_REMOVE,
4632 g_free(tmp);
4633 i++;
4636 soup_cookies_free(sc);
4637 soup_cookies_free(pc);
4639 /* small message if there are none */
4640 if (i == 1) {
4641 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4642 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4644 tmp = body;
4645 body = g_strdup_printf("%s</table>", body);
4646 g_free(tmp);
4648 page = get_html_page("Cookie Jar", body, "", TRUE);
4649 g_free(body);
4650 g_free(table_headers);
4651 g_free(last_domain);
4653 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4654 update_cookie_tabs(t);
4656 g_free(page);
4658 return (0);
4662 xtp_page_hl(struct tab *t, struct karg *args)
4664 char *body, *page, *tmp;
4665 struct history *h;
4666 int i = 1; /* all ids start 1 */
4668 DNPRINTF(XT_D_CMD, "%s", __func__);
4670 if (t == NULL) {
4671 show_oops(NULL, "%s invalid parameters", __func__);
4672 return (1);
4675 /* Generate a new session key */
4676 if (!updating_hl_tabs)
4677 generate_xtp_session_key(&hl_session_key);
4679 /* body */
4680 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4681 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4683 RB_FOREACH_REVERSE(h, history_list, &hl) {
4684 tmp = body;
4685 body = g_strdup_printf(
4686 "%s\n<tr>"
4687 "<td><a href='%s'>%s</a></td>"
4688 "<td>%s</td>"
4689 "<td style='text-align: center'>"
4690 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4691 body, h->uri, h->uri, h->title,
4692 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4693 XT_XTP_HL_REMOVE, i);
4695 g_free(tmp);
4696 i++;
4699 /* small message if there are none */
4700 if (i == 1) {
4701 tmp = body;
4702 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4703 "colspan='3'>No History</td></tr>\n", body);
4704 g_free(tmp);
4707 tmp = body;
4708 body = g_strdup_printf("%s</table>", body);
4709 g_free(tmp);
4711 page = get_html_page("History", body, "", TRUE);
4712 g_free(body);
4715 * update all history manager tabs as the xtp session
4716 * key has now changed. No need to update the current tab.
4717 * Already did that above.
4719 update_history_tabs(t);
4721 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4722 g_free(page);
4724 return (0);
4728 * Generate a web page detailing the status of any downloads
4731 xtp_page_dl(struct tab *t, struct karg *args)
4733 struct download *dl;
4734 char *body, *page, *tmp;
4735 char *ref;
4736 int n_dl = 1;
4738 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4740 if (t == NULL) {
4741 show_oops(NULL, "%s invalid parameters", __func__);
4742 return (1);
4746 * Generate a new session key for next page instance.
4747 * This only happens for the top level call to xtp_page_dl()
4748 * in which case updating_dl_tabs is 0.
4750 if (!updating_dl_tabs)
4751 generate_xtp_session_key(&dl_session_key);
4753 /* header - with refresh so as to update */
4754 if (refresh_interval >= 1)
4755 ref = g_strdup_printf(
4756 "<meta http-equiv='refresh' content='%u"
4757 ";url=%s%d/%s/%d' />\n",
4758 refresh_interval,
4759 XT_XTP_STR,
4760 XT_XTP_DL,
4761 dl_session_key,
4762 XT_XTP_DL_LIST);
4763 else
4764 ref = g_strdup("");
4766 body = g_strdup_printf("<div align='center'>"
4767 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4768 "</p><table><tr><th style='width: 60%%'>"
4769 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4770 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4772 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4773 body = xtp_page_dl_row(t, body, dl);
4774 n_dl++;
4777 /* message if no downloads in list */
4778 if (n_dl == 1) {
4779 tmp = body;
4780 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4781 " style='text-align: center'>"
4782 "No downloads</td></tr>\n", body);
4783 g_free(tmp);
4786 tmp = body;
4787 body = g_strdup_printf("%s</table></div>", body);
4788 g_free(tmp);
4790 page = get_html_page("Downloads", body, ref, 1);
4791 g_free(ref);
4792 g_free(body);
4795 * update all download manager tabs as the xtp session
4796 * key has now changed. No need to update the current tab.
4797 * Already did that above.
4799 update_download_tabs(t);
4801 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4802 g_free(page);
4804 return (0);
4808 search(struct tab *t, struct karg *args)
4810 gboolean d;
4812 if (t == NULL || args == NULL) {
4813 show_oops(NULL, "search invalid parameters");
4814 return (1);
4816 if (t->search_text == NULL) {
4817 if (global_search == NULL)
4818 return (XT_CB_PASSTHROUGH);
4819 else {
4820 t->search_text = g_strdup(global_search);
4821 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4822 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4826 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4827 t->tab_id, args->i, t->search_forward, t->search_text);
4829 switch (args->i) {
4830 case XT_SEARCH_NEXT:
4831 d = t->search_forward;
4832 break;
4833 case XT_SEARCH_PREV:
4834 d = !t->search_forward;
4835 break;
4836 default:
4837 return (XT_CB_PASSTHROUGH);
4840 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4842 return (XT_CB_HANDLED);
4845 struct settings_args {
4846 char **body;
4847 int i;
4850 void
4851 print_setting(struct settings *s, char *val, void *cb_args)
4853 char *tmp, *color;
4854 struct settings_args *sa = cb_args;
4856 if (sa == NULL)
4857 return;
4859 if (s->flags & XT_SF_RUNTIME)
4860 color = "#22cc22";
4861 else
4862 color = "#cccccc";
4864 tmp = *sa->body;
4865 *sa->body = g_strdup_printf(
4866 "%s\n<tr>"
4867 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4868 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4869 *sa->body,
4870 color,
4871 s->name,
4872 color,
4875 g_free(tmp);
4876 sa->i++;
4880 set_show(struct tab *t, struct karg *args)
4882 char *body, *page, *tmp;
4883 int i = 1;
4884 struct settings_args sa;
4886 bzero(&sa, sizeof sa);
4887 sa.body = &body;
4889 /* body */
4890 body = g_strdup_printf("<div align='center'><table><tr>"
4891 "<th align='left'>Setting</th>"
4892 "<th align='left'>Value</th></tr>\n");
4894 settings_walk(print_setting, &sa);
4895 i = sa.i;
4897 /* small message if there are none */
4898 if (i == 1) {
4899 tmp = body;
4900 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4901 "colspan='2'>No settings</td></tr>\n", body);
4902 g_free(tmp);
4905 tmp = body;
4906 body = g_strdup_printf("%s</table></div>", body);
4907 g_free(tmp);
4909 page = get_html_page("Settings", body, "", 0);
4911 g_free(body);
4913 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4915 g_free(page);
4917 return (XT_CB_PASSTHROUGH);
4921 set(struct tab *t, struct karg *args)
4923 char *p, *val;
4924 int i;
4926 if (args == NULL || args->s == NULL)
4927 return (set_show(t, args));
4929 /* strip spaces */
4930 p = g_strstrip(args->s);
4932 if (strlen(p) == 0)
4933 return (set_show(t, args));
4935 /* we got some sort of string */
4936 val = g_strrstr(p, "=");
4937 if (val) {
4938 *val++ = '\0';
4939 val = g_strchomp(val);
4940 p = g_strchomp(p);
4942 for (i = 0; i < LENGTH(rs); i++) {
4943 if (strcmp(rs[i].name, p))
4944 continue;
4946 if (rs[i].activate) {
4947 if (rs[i].activate(val))
4948 show_oops(t, "%s invalid value %s",
4949 p, val);
4950 else
4951 show_oops(t, ":set %s = %s", p, val);
4952 goto done;
4953 } else {
4954 show_oops(t, "not a runtime option: %s", p);
4955 goto done;
4958 show_oops(t, "unknown option: %s", p);
4959 } else {
4960 p = g_strchomp(p);
4962 for (i = 0; i < LENGTH(rs); i++) {
4963 if (strcmp(rs[i].name, p))
4964 continue;
4966 /* XXX this could use some cleanup */
4967 switch (rs[i].type) {
4968 case XT_S_INT:
4969 if (rs[i].ival)
4970 show_oops(t, "%s = %d",
4971 rs[i].name, *rs[i].ival);
4972 else if (rs[i].s && rs[i].s->get)
4973 show_oops(t, "%s = %s",
4974 rs[i].name,
4975 rs[i].s->get(&rs[i]));
4976 else if (rs[i].s && rs[i].s->get == NULL)
4977 show_oops(t, "%s = ...", rs[i].name);
4978 else
4979 show_oops(t, "%s = ", rs[i].name);
4980 break;
4981 case XT_S_FLOAT:
4982 if (rs[i].fval)
4983 show_oops(t, "%s = %f",
4984 rs[i].name, *rs[i].fval);
4985 else if (rs[i].s && rs[i].s->get)
4986 show_oops(t, "%s = %s",
4987 rs[i].name,
4988 rs[i].s->get(&rs[i]));
4989 else if (rs[i].s && rs[i].s->get == NULL)
4990 show_oops(t, "%s = ...", rs[i].name);
4991 else
4992 show_oops(t, "%s = ", rs[i].name);
4993 break;
4994 case XT_S_STR:
4995 if (rs[i].sval && *rs[i].sval)
4996 show_oops(t, "%s = %s",
4997 rs[i].name, *rs[i].sval);
4998 else if (rs[i].s && rs[i].s->get)
4999 show_oops(t, "%s = %s",
5000 rs[i].name,
5001 rs[i].s->get(&rs[i]));
5002 else if (rs[i].s && rs[i].s->get == NULL)
5003 show_oops(t, "%s = ...", rs[i].name);
5004 else
5005 show_oops(t, "%s = ", rs[i].name);
5006 break;
5007 default:
5008 show_oops(t, "unknown type for %s", rs[i].name);
5009 goto done;
5012 goto done;
5014 show_oops(t, "unknown option: %s", p);
5016 done:
5017 return (XT_CB_PASSTHROUGH);
5021 session_save(struct tab *t, char *filename)
5023 struct karg a;
5024 int rv = 1;
5026 if (strlen(filename) == 0)
5027 goto done;
5029 if (filename[0] == '.' || filename[0] == '/')
5030 goto done;
5032 a.s = filename;
5033 if (save_tabs(t, &a))
5034 goto done;
5035 strlcpy(named_session, filename, sizeof named_session);
5037 rv = 0;
5038 done:
5039 return (rv);
5043 session_open(struct tab *t, char *filename)
5045 struct karg a;
5046 int rv = 1;
5048 if (strlen(filename) == 0)
5049 goto done;
5051 if (filename[0] == '.' || filename[0] == '/')
5052 goto done;
5054 a.s = filename;
5055 a.i = XT_SES_CLOSETABS;
5056 if (open_tabs(t, &a))
5057 goto done;
5059 strlcpy(named_session, filename, sizeof named_session);
5061 rv = 0;
5062 done:
5063 return (rv);
5067 session_delete(struct tab *t, char *filename)
5069 char file[PATH_MAX];
5070 int rv = 1;
5072 if (strlen(filename) == 0)
5073 goto done;
5075 if (filename[0] == '.' || filename[0] == '/')
5076 goto done;
5078 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5079 if (unlink(file))
5080 goto done;
5082 if (!strcmp(filename, named_session))
5083 strlcpy(named_session, XT_SAVED_TABS_FILE,
5084 sizeof named_session);
5086 rv = 0;
5087 done:
5088 return (rv);
5092 session_cmd(struct tab *t, struct karg *args)
5094 char *filename = args->s;
5096 if (t == NULL)
5097 return (1);
5099 if (args->i & XT_SHOW)
5100 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5101 XT_SAVED_TABS_FILE : named_session);
5102 else if (args->i & XT_SAVE) {
5103 if (session_save(t, filename)) {
5104 show_oops(t, "Can't save session: %s",
5105 filename ? filename : "INVALID");
5106 goto done;
5108 } else if (args->i & XT_OPEN) {
5109 if (session_open(t, filename)) {
5110 show_oops(t, "Can't open session: %s",
5111 filename ? filename : "INVALID");
5112 goto done;
5114 } else if (args->i & XT_DELETE) {
5115 if (session_delete(t, filename)) {
5116 show_oops(t, "Can't delete session: %s",
5117 filename ? filename : "INVALID");
5118 goto done;
5121 done:
5122 return (XT_CB_PASSTHROUGH);
5126 * Make a hardcopy of the page
5129 print_page(struct tab *t, struct karg *args)
5131 WebKitWebFrame *frame;
5132 GtkPageSetup *ps;
5133 GtkPrintOperation *op;
5134 GtkPrintOperationAction action;
5135 GtkPrintOperationResult print_res;
5136 GError *g_err = NULL;
5137 int marg_l, marg_r, marg_t, marg_b;
5139 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5141 ps = gtk_page_setup_new();
5142 op = gtk_print_operation_new();
5143 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5144 frame = webkit_web_view_get_main_frame(t->wv);
5146 /* the default margins are too small, so we will bump them */
5147 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5148 XT_PRINT_EXTRA_MARGIN;
5149 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5150 XT_PRINT_EXTRA_MARGIN;
5151 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5152 XT_PRINT_EXTRA_MARGIN;
5153 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5154 XT_PRINT_EXTRA_MARGIN;
5156 /* set margins */
5157 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5158 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5159 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5160 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5162 gtk_print_operation_set_default_page_setup(op, ps);
5164 /* this appears to free 'op' and 'ps' */
5165 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5167 /* check it worked */
5168 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5169 show_oops(NULL, "can't print: %s", g_err->message);
5170 g_error_free (g_err);
5171 return (1);
5174 return (0);
5178 go_home(struct tab *t, struct karg *args)
5180 load_uri(t, home);
5181 return (0);
5185 restart(struct tab *t, struct karg *args)
5187 struct karg a;
5189 a.s = XT_RESTART_TABS_FILE;
5190 save_tabs(t, &a);
5191 execvp(start_argv[0], start_argv);
5192 /* NOTREACHED */
5194 return (0);
5197 #define CTRL GDK_CONTROL_MASK
5198 #define MOD1 GDK_MOD1_MASK
5199 #define SHFT GDK_SHIFT_MASK
5201 /* inherent to GTK not all keys will be caught at all times */
5202 /* XXX sort key bindings */
5203 struct key_binding {
5204 char *cmd;
5205 guint mask;
5206 guint use_in_entry;
5207 guint key;
5208 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5209 } keys[] = {
5210 { "cookiejar", MOD1, 0, GDK_j },
5211 { "downloadmgr", MOD1, 0, GDK_d },
5212 { "history", MOD1, 0, GDK_h },
5213 { "print", CTRL, 0, GDK_p },
5214 { "search", 0, 0, GDK_slash },
5215 { "searchb", 0, 0, GDK_question },
5216 { "statustoggle", CTRL, 0, GDK_n },
5217 { "command", 0, 0, GDK_colon },
5218 { "qa", CTRL, 0, GDK_q },
5219 { "restart", MOD1, 0, GDK_q },
5220 { "js toggle", CTRL, 0, GDK_j },
5221 { "cookie toggle", MOD1, 0, GDK_c },
5222 { "togglesrc", CTRL, 0, GDK_s },
5223 { "yankuri", 0, 0, GDK_y },
5224 { "pasteuricur", 0, 0, GDK_p },
5225 { "pasteurinew", 0, 0, GDK_P },
5226 { "toplevel toggle", 0, 0, GDK_F4 },
5227 { "help", 0, 0, GDK_F1 },
5228 { "run_script", MOD1, 0, GDK_r },
5230 /* search */
5231 { "searchnext", 0, 0, GDK_n },
5232 { "searchprevious", 0, 0, GDK_N },
5234 /* focus */
5235 { "focusaddress", 0, 0, GDK_F6 },
5236 { "focussearch", 0, 0, GDK_F7 },
5238 /* hinting */
5239 { "hinting", 0, 0, GDK_f },
5241 /* custom stylesheet */
5242 { "userstyle", 0, 0, GDK_i },
5244 /* navigation */
5245 { "goback", 0, 0, GDK_BackSpace },
5246 { "goback", MOD1, 0, GDK_Left },
5247 { "goforward", SHFT, 0, GDK_BackSpace },
5248 { "goforward", MOD1, 0, GDK_Right },
5249 { "reload", 0, 0, GDK_F5 },
5250 { "reload", CTRL, 0, GDK_r },
5251 { "reload", CTRL, 0, GDK_l },
5252 { "favorites", MOD1, 1, GDK_f },
5254 /* vertical movement */
5255 { "scrolldown", 0, 0, GDK_j },
5256 { "scrolldown", 0, 0, GDK_Down },
5257 { "scrollup", 0, 0, GDK_Up },
5258 { "scrollup", 0, 0, GDK_k },
5259 { "scrollbottom", 0, 0, GDK_G },
5260 { "scrollbottom", 0, 0, GDK_End },
5261 { "scrolltop", 0, 0, GDK_Home },
5262 { "scrollpagedown", 0, 0, GDK_space },
5263 { "scrollpagedown", CTRL, 0, GDK_f },
5264 { "scrollhalfdown", CTRL, 0, GDK_d },
5265 { "scrollpagedown", 0, 0, GDK_Page_Down },
5266 { "scrollpageup", 0, 0, GDK_Page_Up },
5267 { "scrollpageup", CTRL, 0, GDK_b },
5268 { "scrollhalfup", CTRL, 0, GDK_u },
5269 /* horizontal movement */
5270 { "scrollright", 0, 0, GDK_l },
5271 { "scrollright", 0, 0, GDK_Right },
5272 { "scrollleft", 0, 0, GDK_Left },
5273 { "scrollleft", 0, 0, GDK_h },
5274 { "scrollfarright", 0, 0, GDK_dollar },
5275 { "scrollfarleft", 0, 0, GDK_0 },
5277 /* tabs */
5278 { "tabnew", CTRL, 0, GDK_t },
5279 { "999tabnew", CTRL, 0, GDK_T },
5280 { "tabclose", CTRL, 1, GDK_w },
5281 { "tabundoclose", 0, 0, GDK_U },
5282 { "tabnext 1", CTRL, 0, GDK_1 },
5283 { "tabnext 2", CTRL, 0, GDK_2 },
5284 { "tabnext 3", CTRL, 0, GDK_3 },
5285 { "tabnext 4", CTRL, 0, GDK_4 },
5286 { "tabnext 5", CTRL, 0, GDK_5 },
5287 { "tabnext 6", CTRL, 0, GDK_6 },
5288 { "tabnext 7", CTRL, 0, GDK_7 },
5289 { "tabnext 8", CTRL, 0, GDK_8 },
5290 { "tabnext 9", CTRL, 0, GDK_9 },
5291 { "tabfirst", CTRL, 0, GDK_less },
5292 { "tablast", CTRL, 0, GDK_greater },
5293 { "tabprevious", CTRL, 0, GDK_Left },
5294 { "tabnext", CTRL, 0, GDK_Right },
5295 { "focusout", CTRL, 0, GDK_minus },
5296 { "focusin", CTRL, 0, GDK_plus },
5297 { "focusin", CTRL, 0, GDK_equal },
5298 { "focusreset", CTRL, 0, GDK_0 },
5300 /* command aliases (handy when -S flag is used) */
5301 { "promptopen", 0, 0, GDK_F9 },
5302 { "promptopencurrent", 0, 0, GDK_F10 },
5303 { "prompttabnew", 0, 0, GDK_F11 },
5304 { "prompttabnewcurrent",0, 0, GDK_F12 },
5306 TAILQ_HEAD(keybinding_list, key_binding);
5308 void
5309 walk_kb(struct settings *s,
5310 void (*cb)(struct settings *, char *, void *), void *cb_args)
5312 struct key_binding *k;
5313 char str[1024];
5315 if (s == NULL || cb == NULL) {
5316 show_oops(NULL, "walk_kb invalid parameters");
5317 return;
5320 TAILQ_FOREACH(k, &kbl, entry) {
5321 if (k->cmd == NULL)
5322 continue;
5323 str[0] = '\0';
5325 /* sanity */
5326 if (gdk_keyval_name(k->key) == NULL)
5327 continue;
5329 strlcat(str, k->cmd, sizeof str);
5330 strlcat(str, ",", sizeof str);
5332 if (k->mask & GDK_SHIFT_MASK)
5333 strlcat(str, "S-", sizeof str);
5334 if (k->mask & GDK_CONTROL_MASK)
5335 strlcat(str, "C-", sizeof str);
5336 if (k->mask & GDK_MOD1_MASK)
5337 strlcat(str, "M1-", sizeof str);
5338 if (k->mask & GDK_MOD2_MASK)
5339 strlcat(str, "M2-", sizeof str);
5340 if (k->mask & GDK_MOD3_MASK)
5341 strlcat(str, "M3-", sizeof str);
5342 if (k->mask & GDK_MOD4_MASK)
5343 strlcat(str, "M4-", sizeof str);
5344 if (k->mask & GDK_MOD5_MASK)
5345 strlcat(str, "M5-", sizeof str);
5347 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5348 cb(s, str, cb_args);
5352 void
5353 init_keybindings(void)
5355 int i;
5356 struct key_binding *k;
5358 for (i = 0; i < LENGTH(keys); i++) {
5359 k = g_malloc0(sizeof *k);
5360 k->cmd = keys[i].cmd;
5361 k->mask = keys[i].mask;
5362 k->use_in_entry = keys[i].use_in_entry;
5363 k->key = keys[i].key;
5364 TAILQ_INSERT_HEAD(&kbl, k, entry);
5366 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5367 k->cmd ? k->cmd : "unnamed key");
5371 void
5372 keybinding_clearall(void)
5374 struct key_binding *k, *next;
5376 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5377 next = TAILQ_NEXT(k, entry);
5378 if (k->cmd == NULL)
5379 continue;
5381 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5382 k->cmd ? k->cmd : "unnamed key");
5383 TAILQ_REMOVE(&kbl, k, entry);
5384 g_free(k);
5389 keybinding_add(char *cmd, char *key, int use_in_entry)
5391 struct key_binding *k;
5392 guint keyval, mask = 0;
5393 int i;
5395 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5397 /* Keys which are to be used in entry have been prefixed with an
5398 * exclamation mark. */
5399 if (use_in_entry)
5400 key++;
5402 /* find modifier keys */
5403 if (strstr(key, "S-"))
5404 mask |= GDK_SHIFT_MASK;
5405 if (strstr(key, "C-"))
5406 mask |= GDK_CONTROL_MASK;
5407 if (strstr(key, "M1-"))
5408 mask |= GDK_MOD1_MASK;
5409 if (strstr(key, "M2-"))
5410 mask |= GDK_MOD2_MASK;
5411 if (strstr(key, "M3-"))
5412 mask |= GDK_MOD3_MASK;
5413 if (strstr(key, "M4-"))
5414 mask |= GDK_MOD4_MASK;
5415 if (strstr(key, "M5-"))
5416 mask |= GDK_MOD5_MASK;
5418 /* find keyname */
5419 for (i = strlen(key) - 1; i > 0; i--)
5420 if (key[i] == '-')
5421 key = &key[i + 1];
5423 /* validate keyname */
5424 keyval = gdk_keyval_from_name(key);
5425 if (keyval == GDK_VoidSymbol) {
5426 warnx("invalid keybinding name %s", key);
5427 return (1);
5429 /* must run this test too, gtk+ doesn't handle 10 for example */
5430 if (gdk_keyval_name(keyval) == NULL) {
5431 warnx("invalid keybinding name %s", key);
5432 return (1);
5435 /* Remove eventual dupes. */
5436 TAILQ_FOREACH(k, &kbl, entry)
5437 if (k->key == keyval && k->mask == mask) {
5438 TAILQ_REMOVE(&kbl, k, entry);
5439 g_free(k);
5440 break;
5443 /* add keyname */
5444 k = g_malloc0(sizeof *k);
5445 k->cmd = g_strdup(cmd);
5446 k->mask = mask;
5447 k->use_in_entry = use_in_entry;
5448 k->key = keyval;
5450 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5451 k->cmd,
5452 k->mask,
5453 k->use_in_entry,
5454 k->key);
5455 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5456 k->cmd, gdk_keyval_name(keyval));
5458 TAILQ_INSERT_HEAD(&kbl, k, entry);
5460 return (0);
5464 add_kb(struct settings *s, char *entry)
5466 char *kb, *key;
5468 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5470 /* clearall is special */
5471 if (!strcmp(entry, "clearall")) {
5472 keybinding_clearall();
5473 return (0);
5476 kb = strstr(entry, ",");
5477 if (kb == NULL)
5478 return (1);
5479 *kb = '\0';
5480 key = kb + 1;
5482 return (keybinding_add(entry, key, key[0] == '!'));
5485 struct cmd {
5486 char *cmd;
5487 int level;
5488 int (*func)(struct tab *, struct karg *);
5489 int arg;
5490 int type;
5491 } cmds[] = {
5492 { "command", 0, command, ':', 0 },
5493 { "search", 0, command, '/', 0 },
5494 { "searchb", 0, command, '?', 0 },
5495 { "togglesrc", 0, toggle_src, 0, 0 },
5497 /* yanking and pasting */
5498 { "yankuri", 0, yank_uri, 0, 0 },
5499 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5500 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5501 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5503 /* search */
5504 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5505 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5507 /* focus */
5508 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5509 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5511 /* hinting */
5512 { "hinting", 0, hint, 0, 0 },
5514 /* custom stylesheet */
5515 { "userstyle", 0, userstyle, 0, 0 },
5517 /* navigation */
5518 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5519 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5520 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5522 /* vertical movement */
5523 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5524 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5525 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5526 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5527 { "1", 0, move, XT_MOVE_TOP, 0 },
5528 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5529 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5530 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5531 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5532 /* horizontal movement */
5533 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5534 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5535 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5536 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5538 { "favorites", 0, xtp_page_fl, 0, 0 },
5539 { "fav", 0, xtp_page_fl, 0, 0 },
5540 { "favadd", 0, add_favorite, 0, 0 },
5542 { "qall", 0, quit, 0, 0 },
5543 { "quitall", 0, quit, 0, 0 },
5544 { "w", 0, save_tabs, 0, 0 },
5545 { "wq", 0, save_tabs_and_quit, 0, 0 },
5546 { "help", 0, help, 0, 0 },
5547 { "about", 0, about, 0, 0 },
5548 { "stats", 0, stats, 0, 0 },
5549 { "version", 0, about, 0, 0 },
5551 /* js command */
5552 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5553 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5554 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5555 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5556 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5557 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5558 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5559 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5560 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5561 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5562 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5564 /* cookie command */
5565 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5566 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5567 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5568 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5569 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5570 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5571 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5572 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5573 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5574 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5575 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5577 /* toplevel (domain) command */
5578 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5579 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5581 /* cookie jar */
5582 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5584 /* cert command */
5585 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5586 { "save", 1, cert_cmd, XT_SAVE, 0 },
5587 { "show", 1, cert_cmd, XT_SHOW, 0 },
5589 { "ca", 0, ca_cmd, 0, 0 },
5590 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5591 { "dl", 0, xtp_page_dl, 0, 0 },
5592 { "h", 0, xtp_page_hl, 0, 0 },
5593 { "history", 0, xtp_page_hl, 0, 0 },
5594 { "home", 0, go_home, 0, 0 },
5595 { "restart", 0, restart, 0, 0 },
5596 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5597 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5598 { "statustoggle", 0, statustoggle, 0, 0 },
5599 { "run_script", 0, run_page_script, 0, XT_USERARG },
5601 { "print", 0, print_page, 0, 0 },
5603 /* tabs */
5604 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5605 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5606 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5607 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5608 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5609 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5610 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5611 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5612 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5613 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5614 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5615 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5616 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5617 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5618 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5619 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5620 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5621 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5622 { "buffers", 0, buffers, 0, 0 },
5623 { "ls", 0, buffers, 0, 0 },
5624 { "tabs", 0, buffers, 0, 0 },
5626 /* command aliases (handy when -S flag is used) */
5627 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5628 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5629 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5630 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5632 /* settings */
5633 { "set", 0, set, 0, XT_USERARG },
5635 { "fullscreen", 0, fullscreen, 0, 0 },
5636 { "f", 0, fullscreen, 0, 0 },
5638 /* sessions */
5639 { "session", 0, session_cmd, XT_SHOW, 0 },
5640 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5641 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5642 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5643 { "show", 1, session_cmd, XT_SHOW, 0 },
5646 struct {
5647 int index;
5648 int len;
5649 gchar *list[256];
5650 } cmd_status = {-1, 0};
5652 gboolean
5653 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5656 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5657 btn_down = 0;
5659 return (FALSE);
5662 gboolean
5663 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5665 struct karg a;
5667 hide_oops(t);
5668 hide_buffers(t);
5670 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5671 btn_down = 1;
5672 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5673 /* go backward */
5674 a.i = XT_NAV_BACK;
5675 navaction(t, &a);
5677 return (TRUE);
5678 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5679 /* go forward */
5680 a.i = XT_NAV_FORWARD;
5681 navaction(t, &a);
5683 return (TRUE);
5686 return (FALSE);
5689 gboolean
5690 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5692 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5694 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5695 delete_tab(t);
5697 return (FALSE);
5701 * cancel, remove, etc. downloads
5703 void
5704 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5706 struct download find, *d = NULL;
5708 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5710 /* some commands require a valid download id */
5711 if (cmd != XT_XTP_DL_LIST) {
5712 /* lookup download in question */
5713 find.id = id;
5714 d = RB_FIND(download_list, &downloads, &find);
5716 if (d == NULL) {
5717 show_oops(t, "%s: no such download", __func__);
5718 return;
5722 /* decide what to do */
5723 switch (cmd) {
5724 case XT_XTP_DL_CANCEL:
5725 webkit_download_cancel(d->download);
5726 break;
5727 case XT_XTP_DL_REMOVE:
5728 webkit_download_cancel(d->download); /* just incase */
5729 g_object_unref(d->download);
5730 RB_REMOVE(download_list, &downloads, d);
5731 break;
5732 case XT_XTP_DL_LIST:
5733 /* Nothing */
5734 break;
5735 default:
5736 show_oops(t, "%s: unknown command", __func__);
5737 break;
5739 xtp_page_dl(t, NULL);
5743 * Actions on history, only does one thing for now, but
5744 * we provide the function for future actions
5746 void
5747 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5749 struct history *h, *next;
5750 int i = 1;
5752 switch (cmd) {
5753 case XT_XTP_HL_REMOVE:
5754 /* walk backwards, as listed in reverse */
5755 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5756 next = RB_PREV(history_list, &hl, h);
5757 if (id == i) {
5758 RB_REMOVE(history_list, &hl, h);
5759 g_free((gpointer) h->title);
5760 g_free((gpointer) h->uri);
5761 g_free(h);
5762 break;
5764 i++;
5766 break;
5767 case XT_XTP_HL_LIST:
5768 /* Nothing - just xtp_page_hl() below */
5769 break;
5770 default:
5771 show_oops(t, "%s: unknown command", __func__);
5772 break;
5775 xtp_page_hl(t, NULL);
5778 /* remove a favorite */
5779 void
5780 remove_favorite(struct tab *t, int index)
5782 char file[PATH_MAX], *title, *uri = NULL;
5783 char *new_favs, *tmp;
5784 FILE *f;
5785 int i;
5786 size_t len, lineno;
5788 /* open favorites */
5789 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5791 if ((f = fopen(file, "r")) == NULL) {
5792 show_oops(t, "%s: can't open favorites: %s",
5793 __func__, strerror(errno));
5794 return;
5797 /* build a string which will become the new favroites file */
5798 new_favs = g_strdup("");
5800 for (i = 1;;) {
5801 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5802 if (feof(f) || ferror(f))
5803 break;
5804 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5805 if (len == 0) {
5806 free(title);
5807 title = NULL;
5808 continue;
5811 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5812 if (feof(f) || ferror(f)) {
5813 show_oops(t, "%s: can't parse favorites %s",
5814 __func__, strerror(errno));
5815 goto clean;
5819 /* as long as this isn't the one we are deleting add to file */
5820 if (i != index) {
5821 tmp = new_favs;
5822 new_favs = g_strdup_printf("%s%s\n%s\n",
5823 new_favs, title, uri);
5824 g_free(tmp);
5827 free(uri);
5828 uri = NULL;
5829 free(title);
5830 title = NULL;
5831 i++;
5833 fclose(f);
5835 /* write back new favorites file */
5836 if ((f = fopen(file, "w")) == NULL) {
5837 show_oops(t, "%s: can't open favorites: %s",
5838 __func__, strerror(errno));
5839 goto clean;
5842 fwrite(new_favs, strlen(new_favs), 1, f);
5843 fclose(f);
5845 clean:
5846 if (uri)
5847 free(uri);
5848 if (title)
5849 free(title);
5851 g_free(new_favs);
5854 void
5855 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5857 switch (cmd) {
5858 case XT_XTP_FL_LIST:
5859 /* nothing, just the below call to xtp_page_fl() */
5860 break;
5861 case XT_XTP_FL_REMOVE:
5862 remove_favorite(t, arg);
5863 break;
5864 default:
5865 show_oops(t, "%s: invalid favorites command", __func__);
5866 break;
5869 xtp_page_fl(t, NULL);
5872 void
5873 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5875 switch (cmd) {
5876 case XT_XTP_CL_LIST:
5877 /* nothing, just xtp_page_cl() */
5878 break;
5879 case XT_XTP_CL_REMOVE:
5880 remove_cookie(arg);
5881 break;
5882 default:
5883 show_oops(t, "%s: unknown cookie xtp command", __func__);
5884 break;
5887 xtp_page_cl(t, NULL);
5890 /* link an XTP class to it's session key and handler function */
5891 struct xtp_despatch {
5892 uint8_t xtp_class;
5893 char **session_key;
5894 void (*handle_func)(struct tab *, uint8_t, int);
5897 struct xtp_despatch xtp_despatches[] = {
5898 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5899 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5900 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5901 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5902 { XT_XTP_INVALID, NULL, NULL }
5906 * is the url xtp protocol? (xxxt://)
5907 * if so, parse and despatch correct bahvior
5910 parse_xtp_url(struct tab *t, const char *url)
5912 char *dup = NULL, *p, *last;
5913 uint8_t n_tokens = 0;
5914 char *tokens[4] = {NULL, NULL, NULL, ""};
5915 struct xtp_despatch *dsp, *dsp_match = NULL;
5916 uint8_t req_class;
5917 int ret = FALSE;
5920 * tokens array meaning:
5921 * tokens[0] = class
5922 * tokens[1] = session key
5923 * tokens[2] = action
5924 * tokens[3] = optional argument
5927 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5929 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5930 goto clean;
5932 dup = g_strdup(url + strlen(XT_XTP_STR));
5934 /* split out the url */
5935 for ((p = strtok_r(dup, "/", &last)); p;
5936 (p = strtok_r(NULL, "/", &last))) {
5937 if (n_tokens < 4)
5938 tokens[n_tokens++] = p;
5941 /* should be atleast three fields 'class/seskey/command/arg' */
5942 if (n_tokens < 3)
5943 goto clean;
5945 dsp = xtp_despatches;
5946 req_class = atoi(tokens[0]);
5947 while (dsp->xtp_class) {
5948 if (dsp->xtp_class == req_class) {
5949 dsp_match = dsp;
5950 break;
5952 dsp++;
5955 /* did we find one atall? */
5956 if (dsp_match == NULL) {
5957 show_oops(t, "%s: no matching xtp despatch found", __func__);
5958 goto clean;
5961 /* check session key and call despatch function */
5962 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5963 ret = TRUE; /* all is well, this was a valid xtp request */
5964 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5967 clean:
5968 if (dup)
5969 g_free(dup);
5971 return (ret);
5976 void
5977 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5979 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5981 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5983 if (t == NULL) {
5984 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5985 return;
5988 if (uri == NULL) {
5989 show_oops(t, "activate_uri_entry_cb no uri");
5990 return;
5993 uri += strspn(uri, "\t ");
5995 /* if xxxt:// treat specially */
5996 if (parse_xtp_url(t, uri))
5997 return;
5999 /* otherwise continue to load page normally */
6000 load_uri(t, (gchar *)uri);
6001 focus_webview(t);
6004 void
6005 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6007 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6008 char *newuri = NULL;
6009 gchar *enc_search;
6011 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6013 if (t == NULL) {
6014 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6015 return;
6018 if (search_string == NULL) {
6019 show_oops(t, "no search_string");
6020 return;
6023 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6025 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6026 newuri = g_strdup_printf(search_string, enc_search);
6027 g_free(enc_search);
6029 marks_clear(t);
6030 webkit_web_view_load_uri(t->wv, newuri);
6031 focus_webview(t);
6033 if (newuri)
6034 g_free(newuri);
6037 void
6038 check_and_set_cookie(const gchar *uri, struct tab *t)
6040 struct domain *d = NULL;
6041 int es = 0;
6043 if (uri == NULL || t == NULL)
6044 return;
6046 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6047 es = 0;
6048 else
6049 es = 1;
6051 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6052 es ? "enable" : "disable", uri);
6054 g_object_set(G_OBJECT(t->settings),
6055 "enable-html5-local-storage", es, (char *)NULL);
6056 webkit_web_view_set_settings(t->wv, t->settings);
6059 void
6060 check_and_set_js(const gchar *uri, struct tab *t)
6062 struct domain *d = NULL;
6063 int es = 0;
6065 if (uri == NULL || t == NULL)
6066 return;
6068 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6069 es = 0;
6070 else
6071 es = 1;
6073 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6074 es ? "enable" : "disable", uri);
6076 g_object_set(G_OBJECT(t->settings),
6077 "enable-scripts", es, (char *)NULL);
6078 g_object_set(G_OBJECT(t->settings),
6079 "javascript-can-open-windows-automatically", es, (char *)NULL);
6080 webkit_web_view_set_settings(t->wv, t->settings);
6082 button_set_stockid(t->js_toggle,
6083 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6086 gboolean
6087 color_address_bar(gpointer p)
6089 GdkColor color;
6090 struct tab *tt, *t = p;
6091 gchar *col_str = XT_COLOR_YELLOW;
6093 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6095 /* make sure t still exists */
6096 if (t == NULL)
6097 goto done;
6098 TAILQ_FOREACH(tt, &tabs, entry)
6099 if (t == tt)
6100 break;
6101 if (t != tt)
6102 goto done;
6104 switch (load_compare_cert(t, NULL)) {
6105 case CERT_LOCAL:
6106 col_str = XT_COLOR_BLUE;
6107 break;
6108 case CERT_TRUSTED:
6109 col_str = XT_COLOR_GREEN;
6110 break;
6111 case CERT_UNTRUSTED:
6112 col_str = XT_COLOR_YELLOW;
6113 break;
6114 case CERT_BAD:
6115 col_str = XT_COLOR_RED;
6116 break;
6119 gdk_color_parse(col_str, &color);
6120 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6122 if (!strcmp(col_str, XT_COLOR_WHITE))
6123 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6124 else
6125 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6127 col_str = NULL;
6128 done:
6129 return (FALSE /* kill thread */);
6132 void
6133 show_ca_status(struct tab *t, const char *uri)
6135 GdkColor color;
6136 gchar *col_str = XT_COLOR_WHITE;
6138 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6139 ssl_strict_certs, ssl_ca_file, uri);
6141 if (t == NULL)
6142 return;
6144 if (uri == NULL)
6145 goto done;
6146 if (ssl_ca_file == NULL) {
6147 if (g_str_has_prefix(uri, "http://"))
6148 goto done;
6149 if (g_str_has_prefix(uri, "https://")) {
6150 col_str = XT_COLOR_RED;
6151 goto done;
6153 return;
6155 if (g_str_has_prefix(uri, "http://") ||
6156 !g_str_has_prefix(uri, "https://"))
6157 goto done;
6159 /* thread the coloring of the address bar */
6160 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6161 color_address_bar, t, NULL);
6162 return;
6164 done:
6165 if (col_str) {
6166 gdk_color_parse(col_str, &color);
6167 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6169 if (!strcmp(col_str, XT_COLOR_WHITE))
6170 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6171 else
6172 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6176 void
6177 free_favicon(struct tab *t)
6179 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6180 __func__, t->icon_download, t->icon_request);
6182 if (t->icon_request)
6183 g_object_unref(t->icon_request);
6184 if (t->icon_dest_uri)
6185 g_free(t->icon_dest_uri);
6187 t->icon_request = NULL;
6188 t->icon_dest_uri = NULL;
6191 void
6192 xt_icon_from_name(struct tab *t, gchar *name)
6194 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6195 GTK_ENTRY_ICON_PRIMARY, "text-html");
6196 if (show_url == 0)
6197 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6198 GTK_ENTRY_ICON_PRIMARY, "text-html");
6199 else
6200 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6201 GTK_ENTRY_ICON_PRIMARY, NULL);
6204 void
6205 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6207 GdkPixbuf *pb_scaled;
6209 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6210 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6211 GDK_INTERP_BILINEAR);
6212 else
6213 pb_scaled = pb;
6215 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6216 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6217 if (show_url == 0)
6218 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6219 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6220 else
6221 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6222 GTK_ENTRY_ICON_PRIMARY, NULL);
6224 if (pb_scaled != pb)
6225 g_object_unref(pb_scaled);
6228 void
6229 xt_icon_from_file(struct tab *t, char *file)
6231 GdkPixbuf *pb;
6233 if (g_str_has_prefix(file, "file://"))
6234 file += strlen("file://");
6236 pb = gdk_pixbuf_new_from_file(file, NULL);
6237 if (pb) {
6238 xt_icon_from_pixbuf(t, pb);
6239 g_object_unref(pb);
6240 } else
6241 xt_icon_from_name(t, "text-html");
6244 gboolean
6245 is_valid_icon(char *file)
6247 gboolean valid = 0;
6248 const char *mime_type;
6249 GFileInfo *fi;
6250 GFile *gf;
6252 gf = g_file_new_for_path(file);
6253 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6254 NULL, NULL);
6255 mime_type = g_file_info_get_content_type(fi);
6256 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6257 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6258 g_strcmp0(mime_type, "image/png") == 0 ||
6259 g_strcmp0(mime_type, "image/gif") == 0 ||
6260 g_strcmp0(mime_type, "application/octet-stream") == 0;
6261 g_object_unref(fi);
6262 g_object_unref(gf);
6264 return (valid);
6267 void
6268 set_favicon_from_file(struct tab *t, char *file)
6270 struct stat sb;
6272 if (t == NULL || file == NULL)
6273 return;
6275 if (g_str_has_prefix(file, "file://"))
6276 file += strlen("file://");
6277 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6279 if (!stat(file, &sb)) {
6280 if (sb.st_size == 0 || !is_valid_icon(file)) {
6281 /* corrupt icon so trash it */
6282 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6283 __func__, file);
6284 unlink(file);
6285 /* no need to set icon to default here */
6286 return;
6289 xt_icon_from_file(t, file);
6292 void
6293 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6294 WebKitWebView *wv)
6296 WebKitDownloadStatus status = webkit_download_get_status(download);
6297 struct tab *tt = NULL, *t = NULL;
6300 * find the webview instead of passing in the tab as it could have been
6301 * deleted from underneath us.
6303 TAILQ_FOREACH(tt, &tabs, entry) {
6304 if (tt->wv == wv) {
6305 t = tt;
6306 break;
6309 if (t == NULL)
6310 return;
6312 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6313 __func__, t->tab_id, status);
6315 switch (status) {
6316 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6317 /* -1 */
6318 t->icon_download = NULL;
6319 free_favicon(t);
6320 break;
6321 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6322 /* 0 */
6323 break;
6324 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6325 /* 1 */
6326 break;
6327 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6328 /* 2 */
6329 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6330 __func__, t->tab_id);
6331 t->icon_download = NULL;
6332 free_favicon(t);
6333 break;
6334 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6335 /* 3 */
6337 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6338 __func__, t->icon_dest_uri);
6339 set_favicon_from_file(t, t->icon_dest_uri);
6340 /* these will be freed post callback */
6341 t->icon_request = NULL;
6342 t->icon_download = NULL;
6343 break;
6344 default:
6345 break;
6349 void
6350 abort_favicon_download(struct tab *t)
6352 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6354 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6355 if (t->icon_download) {
6356 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6357 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6358 webkit_download_cancel(t->icon_download);
6359 t->icon_download = NULL;
6360 } else
6361 free_favicon(t);
6362 #endif
6364 xt_icon_from_name(t, "text-html");
6367 void
6368 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6370 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6372 if (uri == NULL || t == NULL)
6373 return;
6375 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6376 /* take icon from WebKitIconDatabase */
6377 GdkPixbuf *pb;
6379 pb = webkit_web_view_get_icon_pixbuf(wv);
6380 if (pb) {
6381 xt_icon_from_pixbuf(t, pb);
6382 g_object_unref(pb);
6383 } else
6384 xt_icon_from_name(t, "text-html");
6385 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6386 /* download icon to cache dir */
6387 gchar *name_hash, file[PATH_MAX];
6388 struct stat sb;
6390 if (t->icon_request) {
6391 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6392 return;
6395 /* check to see if we got the icon in cache */
6396 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6397 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6398 g_free(name_hash);
6400 if (!stat(file, &sb)) {
6401 if (sb.st_size > 0) {
6402 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6403 __func__, file);
6404 set_favicon_from_file(t, file);
6405 return;
6408 /* corrupt icon so trash it */
6409 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6410 __func__, file);
6411 unlink(file);
6414 /* create download for icon */
6415 t->icon_request = webkit_network_request_new(uri);
6416 if (t->icon_request == NULL) {
6417 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6418 __func__, uri);
6419 return;
6422 t->icon_download = webkit_download_new(t->icon_request);
6423 if (t->icon_download == NULL)
6424 return;
6426 /* we have to free icon_dest_uri later */
6427 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6428 webkit_download_set_destination_uri(t->icon_download,
6429 t->icon_dest_uri);
6431 if (webkit_download_get_status(t->icon_download) ==
6432 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6433 g_object_unref(t->icon_request);
6434 g_free(t->icon_dest_uri);
6435 t->icon_request = NULL;
6436 t->icon_dest_uri = NULL;
6437 return;
6440 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6441 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6443 webkit_download_start(t->icon_download);
6444 #endif
6447 void
6448 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6450 const gchar *uri = NULL, *title = NULL;
6451 struct history *h, find;
6452 struct karg a;
6453 GdkColor color;
6455 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6456 webkit_web_view_get_load_status(wview),
6457 get_uri(t) ? get_uri(t) : "NOTHING");
6459 if (t == NULL) {
6460 show_oops(NULL, "notify_load_status_cb invalid parameters");
6461 return;
6464 switch (webkit_web_view_get_load_status(wview)) {
6465 case WEBKIT_LOAD_PROVISIONAL:
6466 /* 0 */
6467 abort_favicon_download(t);
6468 #if GTK_CHECK_VERSION(2, 20, 0)
6469 gtk_widget_show(t->spinner);
6470 gtk_spinner_start(GTK_SPINNER(t->spinner));
6471 #endif
6472 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6474 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6476 /* assume we are a new address */
6477 gdk_color_parse("white", &color);
6478 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6479 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6481 /* take focus if we are visible */
6482 focus_webview(t);
6483 t->focus_wv = 1;
6485 break;
6487 case WEBKIT_LOAD_COMMITTED:
6488 /* 1 */
6489 uri = get_uri(t);
6490 if (uri == NULL)
6491 return;
6492 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6494 if (t->status) {
6495 g_free(t->status);
6496 t->status = NULL;
6498 set_status(t, (char *)uri, XT_STATUS_LOADING);
6500 /* check if js white listing is enabled */
6501 if (enable_cookie_whitelist)
6502 check_and_set_cookie(uri, t);
6503 if (enable_js_whitelist)
6504 check_and_set_js(uri, t);
6506 if (t->styled)
6507 apply_style(t);
6510 /* we know enough to autosave the session */
6511 if (session_autosave) {
6512 a.s = NULL;
6513 save_tabs(t, &a);
6516 show_ca_status(t, uri);
6517 break;
6519 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6520 /* 3 */
6521 break;
6523 case WEBKIT_LOAD_FINISHED:
6524 /* 2 */
6525 uri = get_uri(t);
6526 if (uri == NULL)
6527 return;
6529 if (!strncmp(uri, "http://", strlen("http://")) ||
6530 !strncmp(uri, "https://", strlen("https://")) ||
6531 !strncmp(uri, "file://", strlen("file://"))) {
6532 find.uri = uri;
6533 h = RB_FIND(history_list, &hl, &find);
6534 if (!h) {
6535 title = get_title(t, FALSE);
6536 h = g_malloc(sizeof *h);
6537 h->uri = g_strdup(uri);
6538 h->title = g_strdup(title);
6539 RB_INSERT(history_list, &hl, h);
6540 completion_add_uri(h->uri);
6541 update_history_tabs(NULL);
6545 set_status(t, (char *)uri, XT_STATUS_URI);
6546 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6547 case WEBKIT_LOAD_FAILED:
6548 /* 4 */
6549 #endif
6550 #if GTK_CHECK_VERSION(2, 20, 0)
6551 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6552 gtk_widget_hide(t->spinner);
6553 #endif
6554 default:
6555 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6556 break;
6559 if (t->item)
6560 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6561 else
6562 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6563 webkit_web_view_can_go_back(wview));
6565 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6566 webkit_web_view_can_go_forward(wview));
6569 #if 0
6570 gboolean
6571 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6572 gchar *uri, gpointer web_error,struct tab *t)
6575 * XXX this function is wrong
6576 * it overwrites perfectly good urls with garbage on load errors
6577 * those happen often when popups fail to resolve dns
6579 if (t->tmp_uri)
6580 g_free(t->tmp_uri);
6581 t->tmp_uri = g_strdup(uri);
6582 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6583 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6584 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6585 set_status(t, uri, XT_STATUS_NOTHING);
6587 return (FALSE);
6589 #endif
6591 void
6592 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6594 const gchar *title = NULL, *win_title = NULL;
6596 title = get_title(t, FALSE);
6597 win_title = get_title(t, TRUE);
6598 gtk_label_set_text(GTK_LABEL(t->label), title);
6599 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6600 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6601 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6604 void
6605 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6607 run_script(t, JS_HINTING);
6610 void
6611 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6613 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6614 progress == 100 ? 0 : (double)progress / 100);
6615 if (show_url == 0) {
6616 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6617 progress == 100 ? 0 : (double)progress / 100);
6620 update_statusbar_position(NULL, NULL);
6624 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6625 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6626 WebKitWebPolicyDecision *pd, struct tab *t)
6628 char *uri;
6629 WebKitWebNavigationReason reason;
6630 struct domain *d = NULL;
6632 if (t == NULL) {
6633 show_oops(NULL, "webview_npd_cb invalid parameters");
6634 return (FALSE);
6637 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6638 t->ctrl_click,
6639 webkit_network_request_get_uri(request));
6641 uri = (char *)webkit_network_request_get_uri(request);
6643 /* if this is an xtp url, we don't load anything else */
6644 if (parse_xtp_url(t, uri))
6645 return (TRUE);
6647 if (t->ctrl_click) {
6648 t->ctrl_click = 0;
6649 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6650 webkit_web_policy_decision_ignore(pd);
6651 return (TRUE); /* we made the decission */
6655 * This is a little hairy but it comes down to this:
6656 * when we run in whitelist mode we have to assist the browser in
6657 * opening the URL that it would have opened in a new tab.
6659 reason = webkit_web_navigation_action_get_reason(na);
6660 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6661 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6662 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6663 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6664 load_uri(t, uri);
6665 webkit_web_policy_decision_use(pd);
6666 return (TRUE); /* we made the decision */
6669 return (FALSE);
6672 WebKitWebView *
6673 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6675 struct tab *tt;
6676 struct domain *d = NULL;
6677 const gchar *uri;
6678 WebKitWebView *webview = NULL;
6680 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6681 webkit_web_view_get_uri(wv));
6683 if (tabless) {
6684 /* open in current tab */
6685 webview = t->wv;
6686 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6687 uri = webkit_web_view_get_uri(wv);
6688 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6689 return (NULL);
6691 tt = create_new_tab(NULL, NULL, 1, -1);
6692 webview = tt->wv;
6693 } else if (enable_scripts == 1) {
6694 tt = create_new_tab(NULL, NULL, 1, -1);
6695 webview = tt->wv;
6698 return (webview);
6701 gboolean
6702 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6704 const gchar *uri;
6705 struct domain *d = NULL;
6707 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6709 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6710 uri = webkit_web_view_get_uri(wv);
6711 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6712 return (FALSE);
6714 delete_tab(t);
6715 } else if (enable_scripts == 1)
6716 delete_tab(t);
6718 return (TRUE);
6722 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6724 /* we can not eat the event without throwing gtk off so defer it */
6726 /* catch middle click */
6727 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6728 t->ctrl_click = 1;
6729 goto done;
6732 /* catch ctrl click */
6733 if (e->type == GDK_BUTTON_RELEASE &&
6734 CLEAN(e->state) == GDK_CONTROL_MASK)
6735 t->ctrl_click = 1;
6736 else
6737 t->ctrl_click = 0;
6738 done:
6739 return (XT_CB_PASSTHROUGH);
6743 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6745 struct mime_type *m;
6747 m = find_mime_type(mime_type);
6748 if (m == NULL)
6749 return (1);
6750 if (m->mt_download)
6751 return (1);
6753 switch (fork()) {
6754 case -1:
6755 show_oops(t, "can't fork mime handler");
6756 return (1);
6757 /* NOTREACHED */
6758 case 0:
6759 break;
6760 default:
6761 return (0);
6764 /* child */
6765 execlp(m->mt_action, m->mt_action,
6766 webkit_network_request_get_uri(request), (void *)NULL);
6768 _exit(0);
6770 /* NOTREACHED */
6771 return (0);
6774 const gchar *
6775 get_mime_type(char *file)
6777 const char *mime_type;
6778 GFileInfo *fi;
6779 GFile *gf;
6781 if (g_str_has_prefix(file, "file://"))
6782 file += strlen("file://");
6784 gf = g_file_new_for_path(file);
6785 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6786 NULL, NULL);
6787 mime_type = g_file_info_get_content_type(fi);
6788 g_object_unref(fi);
6789 g_object_unref(gf);
6791 return (mime_type);
6795 run_download_mimehandler(char *mime_type, char *file)
6797 struct mime_type *m;
6799 m = find_mime_type(mime_type);
6800 if (m == NULL)
6801 return (1);
6803 switch (fork()) {
6804 case -1:
6805 show_oops(NULL, "can't fork download mime handler");
6806 return (1);
6807 /* NOTREACHED */
6808 case 0:
6809 break;
6810 default:
6811 return (0);
6814 /* child */
6815 if (g_str_has_prefix(file, "file://"))
6816 file += strlen("file://");
6817 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6819 _exit(0);
6821 /* NOTREACHED */
6822 return (0);
6825 void
6826 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6827 WebKitWebView *wv)
6829 WebKitDownloadStatus status;
6830 const gchar *file = NULL, *mime = NULL;
6832 if (download == NULL)
6833 return;
6834 status = webkit_download_get_status(download);
6835 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6836 return;
6838 file = webkit_download_get_destination_uri(download);
6839 if (file == NULL)
6840 return;
6841 mime = get_mime_type((char *)file);
6842 if (mime == NULL)
6843 return;
6845 run_download_mimehandler((char *)mime, (char *)file);
6849 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6850 WebKitNetworkRequest *request, char *mime_type,
6851 WebKitWebPolicyDecision *decision, struct tab *t)
6853 if (t == NULL) {
6854 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6855 return (FALSE);
6858 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6859 t->tab_id, mime_type);
6861 if (run_mimehandler(t, mime_type, request) == 0) {
6862 webkit_web_policy_decision_ignore(decision);
6863 focus_webview(t);
6864 return (TRUE);
6867 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6868 webkit_web_policy_decision_download(decision);
6869 return (TRUE);
6872 return (FALSE);
6876 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6877 struct tab *t)
6879 struct stat sb;
6880 const gchar *suggested_name;
6881 gchar *filename = NULL;
6882 char *uri = NULL;
6883 struct download *download_entry;
6884 int i, ret = TRUE;
6886 if (wk_download == NULL || t == NULL) {
6887 show_oops(NULL, "%s invalid parameters", __func__);
6888 return (FALSE);
6891 suggested_name = webkit_download_get_suggested_filename(wk_download);
6892 if (suggested_name == NULL)
6893 return (FALSE); /* abort download */
6895 i = 0;
6896 do {
6897 if (filename) {
6898 g_free(filename);
6899 filename = NULL;
6901 if (i) {
6902 g_free(uri);
6903 uri = NULL;
6904 filename = g_strdup_printf("%d%s", i, suggested_name);
6906 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6907 filename : suggested_name);
6908 i++;
6909 } while (!stat(uri + strlen("file://"), &sb));
6911 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6912 "local %s\n", __func__, t->tab_id, filename, uri);
6914 webkit_download_set_destination_uri(wk_download, uri);
6916 if (webkit_download_get_status(wk_download) ==
6917 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6918 show_oops(t, "%s: download failed to start", __func__);
6919 ret = FALSE;
6920 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6921 } else {
6922 /* connect "download first" mime handler */
6923 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6924 G_CALLBACK(download_status_changed_cb), NULL);
6926 download_entry = g_malloc(sizeof(struct download));
6927 download_entry->download = wk_download;
6928 download_entry->tab = t;
6929 download_entry->id = next_download_id++;
6930 RB_INSERT(download_list, &downloads, download_entry);
6931 /* get from history */
6932 g_object_ref(wk_download);
6933 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6934 show_oops(t, "Download of '%s' started...",
6935 basename((char *)webkit_download_get_destination_uri(wk_download)));
6938 if (uri)
6939 g_free(uri);
6941 if (filename)
6942 g_free(filename);
6944 /* sync other download manager tabs */
6945 update_download_tabs(NULL);
6948 * NOTE: never redirect/render the current tab before this
6949 * function returns. This will cause the download to never start.
6951 return (ret); /* start download */
6954 void
6955 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6957 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6959 if (t == NULL) {
6960 show_oops(NULL, "webview_hover_cb");
6961 return;
6964 if (uri)
6965 set_status(t, uri, XT_STATUS_LINK);
6966 else {
6967 if (t->status)
6968 set_status(t, t->status, XT_STATUS_NOTHING);
6973 mark(struct tab *t, struct karg *arg)
6975 char mark;
6976 int index;
6978 mark = arg->s[1];
6979 if ((index = marktoindex(mark)) == -1)
6980 return -1;
6982 if (arg->i == XT_MARK_SET)
6983 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6984 else if (arg->i == XT_MARK_GOTO) {
6985 if (t->mark[index] == XT_INVALID_MARK) {
6986 show_oops(t, "mark '%c' does not exist", mark);
6987 return -1;
6989 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6990 something changes the document size */
6991 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6994 return 0;
6997 void
6998 marks_clear(struct tab *t)
7000 int i;
7002 for (i = 0; i < LENGTH(t->mark); i++)
7003 t->mark[i] = XT_INVALID_MARK;
7007 qmarks_load(void)
7009 char file[PATH_MAX];
7010 char *line = NULL, *p, mark;
7011 int index, i;
7012 FILE *f;
7013 size_t linelen;
7015 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7016 if ((f = fopen(file, "r+")) == NULL) {
7017 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7018 return (1);
7021 for (i = 1; ; i++) {
7022 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7023 if (feof(f) || ferror(f))
7024 break;
7025 if (strlen(line) == 0 || line[0] == '#') {
7026 free(line);
7027 line = NULL;
7028 continue;
7031 p = strtok(line, " \t");
7033 if (p == NULL || strlen(p) != 1 ||
7034 (index = marktoindex(*p)) == -1) {
7035 warnx("corrupt quickmarks file, line %d", i);
7036 break;
7039 mark = *p;
7040 p = strtok(NULL, " \t");
7041 if (qmarks[index] != NULL)
7042 g_free(qmarks[index]);
7043 qmarks[index] = g_strdup(p);
7046 fclose(f);
7048 return (0);
7052 qmarks_save(void)
7054 char file[PATH_MAX];
7055 int i;
7056 FILE *f;
7058 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7059 if ((f = fopen(file, "r+")) == NULL) {
7060 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7061 return (1);
7064 for (i = 0; i < XT_NOMARKS; i++)
7065 if (qmarks[i] != NULL)
7066 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7068 fclose(f);
7070 return (0);
7074 qmark(struct tab *t, struct karg *arg)
7076 char mark;
7077 int index;
7079 mark = arg->s[strlen(arg->s)-1];
7080 index = marktoindex(mark);
7081 if (index == -1)
7082 return (-1);
7084 switch (arg->i) {
7085 case XT_QMARK_SET:
7086 if (qmarks[index] != NULL)
7087 g_free(qmarks[index]);
7089 qmarks_load(); /* sync if multiple instances */
7090 qmarks[index] = g_strdup(get_uri(t));
7091 qmarks_save();
7092 break;
7093 case XT_QMARK_OPEN:
7094 if (qmarks[index] != NULL)
7095 load_uri(t, qmarks[index]);
7096 else {
7097 show_oops(t, "quickmark \"%c\" does not exist",
7098 mark);
7099 return (-1);
7101 break;
7102 case XT_QMARK_TAB:
7103 if (qmarks[index] != NULL)
7104 create_new_tab(qmarks[index], NULL, 1, -1);
7105 else {
7106 show_oops(t, "quickmark \"%c\" does not exist",
7107 mark);
7108 return (-1);
7110 break;
7113 return (0);
7117 go_up(struct tab *t, struct karg *args)
7119 int levels;
7120 char *uri;
7121 char *tmp;
7123 levels = atoi(args->s);
7124 if (levels == 0)
7125 levels = 1;
7127 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7128 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7129 return 1;
7130 tmp += strlen(XT_PROTO_DELIM);
7132 /* if an uri starts with a slash, leave it alone (for file:///) */
7133 if (tmp[0] == '/')
7134 tmp++;
7136 while (levels--) {
7137 char *p;
7139 p = strrchr(tmp, '/');
7140 if (p != NULL)
7141 *p = '\0';
7142 else
7143 break;
7146 load_uri(t, uri);
7147 g_free(uri);
7149 return (0);
7153 gototab(struct tab *t, struct karg *args)
7155 int tab;
7156 struct karg arg = {0, NULL, -1};
7158 tab = atoi(args->s);
7160 arg.i = XT_TAB_NEXT;
7161 arg.precount = tab;
7163 movetab(t, &arg);
7165 return (0);
7169 zoom_amount(struct tab *t, struct karg *arg)
7171 struct karg narg = {0, NULL, -1};
7173 narg.i = atoi(arg->s);
7174 resizetab(t, &narg);
7176 return 0;
7180 flip_colon(struct tab *t, struct karg *arg)
7182 struct karg narg = {0, NULL, -1};
7183 char *p;
7185 if (t == NULL || arg == NULL)
7186 return (1);
7188 p = strstr(arg->s, ":");
7189 if (p == NULL)
7190 return (1);
7191 *p = '\0';
7193 narg.i = ':';
7194 narg.s = arg->s;
7195 command(t, &narg);
7197 return (0);
7200 /* buffer commands receive the regex that triggered them in arg.s */
7201 char bcmd[XT_BUFCMD_SZ];
7202 struct buffercmd {
7203 char *regex;
7204 int precount;
7205 #define XT_PRE_NO (0)
7206 #define XT_PRE_YES (1)
7207 #define XT_PRE_MAYBE (2)
7208 char *cmd;
7209 int (*func)(struct tab *, struct karg *);
7210 int arg;
7211 regex_t cregex;
7212 } buffercmds[] = {
7213 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7214 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7215 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7216 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7217 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7218 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7219 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7220 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7221 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7222 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7223 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7224 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7225 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7226 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7227 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7228 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7229 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7230 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7233 void
7234 buffercmd_init(void)
7236 int i;
7238 for (i = 0; i < LENGTH(buffercmds); i++)
7239 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7240 REG_EXTENDED);
7243 void
7244 buffercmd_abort(struct tab *t)
7246 int i;
7248 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7249 for (i = 0; i < LENGTH(bcmd); i++)
7250 bcmd[i] = '\0';
7252 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7253 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7256 void
7257 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7259 struct karg arg = {0, NULL, -1};
7261 arg.i = cmd->arg;
7262 arg.s = g_strdup(bcmd);
7264 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7265 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7266 cmd->func(t, &arg);
7268 if (arg.s)
7269 g_free(arg.s);
7271 buffercmd_abort(t);
7274 gboolean
7275 buffercmd_addkey(struct tab *t, guint keyval)
7277 int i, c, match ;
7278 char s[XT_BUFCMD_SZ];
7280 if (keyval == GDK_Escape) {
7281 buffercmd_abort(t);
7282 return (XT_CB_HANDLED);
7285 /* key with modifier or non-ascii character */
7286 if (!isascii(keyval))
7287 return (XT_CB_PASSTHROUGH);
7289 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7290 "to buffer \"%s\"\n", keyval, bcmd);
7292 for (i = 0; i < LENGTH(bcmd); i++)
7293 if (bcmd[i] == '\0') {
7294 bcmd[i] = keyval;
7295 break;
7298 /* buffer full, ignore input */
7299 if (i >= LENGTH(bcmd) -1) {
7300 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7301 buffercmd_abort(t);
7302 return (XT_CB_HANDLED);
7305 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7307 /* find exact match */
7308 for (i = 0; i < LENGTH(buffercmds); i++)
7309 if (regexec(&buffercmds[i].cregex, bcmd,
7310 (size_t) 0, NULL, 0) == 0) {
7311 buffercmd_execute(t, &buffercmds[i]);
7312 goto done;
7315 /* find non exact matches to see if we need to abort ot not */
7316 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7317 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7318 c = -1;
7319 s[0] = '\0';
7320 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7321 if (isdigit(bcmd[0])) {
7322 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7323 continue;
7324 } else {
7325 c = 0;
7326 if (sscanf(bcmd, "%s", s) == 0)
7327 continue;
7329 } else if (buffercmds[i].precount == XT_PRE_YES) {
7330 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7331 continue;
7332 } else {
7333 if (sscanf(bcmd, "%s", s) == 0)
7334 continue;
7336 if (c == -1 && buffercmds[i].precount)
7337 continue;
7338 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7339 match++;
7341 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7342 i, match, buffercmds[i].cmd, c, s);
7344 if (match == 0) {
7345 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7346 buffercmd_abort(t);
7349 done:
7350 return (XT_CB_HANDLED);
7353 gboolean
7354 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7356 struct key_binding *k;
7358 /* handle keybindings if buffercmd is empty.
7359 if not empty, allow commands like C-n */
7360 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7361 TAILQ_FOREACH(k, &kbl, entry)
7362 if (e->keyval == k->key
7363 && (entry ? k->use_in_entry : 1)) {
7364 if (k->mask == 0) {
7365 if ((e->state & (CTRL | MOD1)) == 0)
7366 return (cmd_execute(t, k->cmd));
7367 } else if ((e->state & k->mask) == k->mask) {
7368 return (cmd_execute(t, k->cmd));
7372 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7373 return buffercmd_addkey(t, e->keyval);
7375 return (XT_CB_PASSTHROUGH);
7379 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7381 char s[2], buf[128];
7382 const char *errstr = NULL;
7384 /* don't use w directly; use t->whatever instead */
7386 if (t == NULL) {
7387 show_oops(NULL, "wv_keypress_after_cb");
7388 return (XT_CB_PASSTHROUGH);
7391 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7392 e->keyval, e->state, t);
7394 if (t->hints_on) {
7395 /* ESC */
7396 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7397 disable_hints(t);
7398 return (XT_CB_HANDLED);
7401 /* RETURN */
7402 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7403 if (errstr) {
7404 /* we have a string */
7405 } else {
7406 /* we have a number */
7407 snprintf(buf, sizeof buf,
7408 "vimprobable_fire(%s)", t->hint_num);
7409 run_script(t, buf);
7411 disable_hints(t);
7414 /* BACKSPACE */
7415 /* XXX unfuck this */
7416 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7417 if (t->hint_mode == XT_HINT_NUMERICAL) {
7418 /* last input was numerical */
7419 int l;
7420 l = strlen(t->hint_num);
7421 if (l > 0) {
7422 l--;
7423 if (l == 0) {
7424 disable_hints(t);
7425 enable_hints(t);
7426 } else {
7427 t->hint_num[l] = '\0';
7428 goto num;
7431 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7432 /* last input was alphanumerical */
7433 int l;
7434 l = strlen(t->hint_buf);
7435 if (l > 0) {
7436 l--;
7437 if (l == 0) {
7438 disable_hints(t);
7439 enable_hints(t);
7440 } else {
7441 t->hint_buf[l] = '\0';
7442 goto anum;
7445 } else {
7446 /* bogus */
7447 disable_hints(t);
7451 /* numerical input */
7452 if (CLEAN(e->state) == 0 &&
7453 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7454 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7455 snprintf(s, sizeof s, "%c", e->keyval);
7456 strlcat(t->hint_num, s, sizeof t->hint_num);
7457 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7458 t->hint_num);
7459 num:
7460 if (errstr) {
7461 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7462 "invalid link number\n");
7463 disable_hints(t);
7464 } else {
7465 snprintf(buf, sizeof buf,
7466 "vimprobable_update_hints(%s)",
7467 t->hint_num);
7468 t->hint_mode = XT_HINT_NUMERICAL;
7469 run_script(t, buf);
7472 /* empty the counter buffer */
7473 bzero(t->hint_buf, sizeof t->hint_buf);
7474 return (XT_CB_HANDLED);
7477 /* alphanumerical input */
7478 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7479 e->keyval <= GDK_z) ||
7480 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7481 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7482 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7483 e->keyval <= GDK_9) ||
7484 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7485 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7486 snprintf(s, sizeof s, "%c", e->keyval);
7487 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7488 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7489 " %s\n", t->hint_buf);
7490 anum:
7491 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7492 run_script(t, buf);
7494 snprintf(buf, sizeof buf,
7495 "vimprobable_show_hints('%s')", t->hint_buf);
7496 t->hint_mode = XT_HINT_ALPHANUM;
7497 run_script(t, buf);
7499 /* empty the counter buffer */
7500 bzero(t->hint_num, sizeof t->hint_num);
7501 return (XT_CB_HANDLED);
7504 return (XT_CB_HANDLED);
7505 } else {
7506 /* prefix input*/
7507 snprintf(s, sizeof s, "%c", e->keyval);
7508 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7509 cmd_prefix = 10 * cmd_prefix + atoi(s);
7512 return (handle_keypress(t, e, 0));
7516 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7518 hide_oops(t);
7520 /* Hide buffers, if they are visible, with escape. */
7521 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7522 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7523 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7524 hide_buffers(t);
7525 return (XT_CB_HANDLED);
7528 return (XT_CB_PASSTHROUGH);
7531 gboolean
7532 search_continue(struct tab *t)
7534 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7535 gboolean rv = FALSE;
7537 if (c[0] == ':')
7538 goto done;
7539 if (strlen(c) == 1) {
7540 webkit_web_view_unmark_text_matches(t->wv);
7541 goto done;
7544 if (c[0] == '/')
7545 t->search_forward = TRUE;
7546 else if (c[0] == '?')
7547 t->search_forward = FALSE;
7548 else
7549 goto done;
7551 rv = TRUE;
7552 done:
7553 return (rv);
7556 gboolean
7557 search_cb(struct tab *t)
7559 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7560 GdkColor color;
7562 if (search_continue(t) == FALSE)
7563 goto done;
7565 /* search */
7566 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7567 TRUE) == FALSE) {
7568 /* not found, mark red */
7569 gdk_color_parse(XT_COLOR_RED, &color);
7570 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7571 /* unmark and remove selection */
7572 webkit_web_view_unmark_text_matches(t->wv);
7573 /* my kingdom for a way to unselect text in webview */
7574 } else {
7575 /* found, highlight all */
7576 webkit_web_view_unmark_text_matches(t->wv);
7577 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7578 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7579 gdk_color_parse(XT_COLOR_WHITE, &color);
7580 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7582 done:
7583 t->search_id = 0;
7584 return (FALSE);
7588 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7590 const gchar *c = gtk_entry_get_text(w);
7592 if (t == NULL) {
7593 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7594 return (XT_CB_PASSTHROUGH);
7597 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7598 e->keyval, e->state, t);
7600 if (search_continue(t) == FALSE)
7601 goto done;
7603 /* if search length is > 4 then no longer play timeout games */
7604 if (strlen(c) > 4) {
7605 if (t->search_id) {
7606 g_source_remove(t->search_id);
7607 t->search_id = 0;
7609 search_cb(t);
7610 goto done;
7613 /* reestablish a new timer if the user types fast */
7614 if (t->search_id)
7615 g_source_remove(t->search_id);
7616 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7618 done:
7619 return (XT_CB_PASSTHROUGH);
7622 gboolean
7623 match_uri(const gchar *uri, const gchar *key) {
7624 gchar *voffset;
7625 size_t len;
7626 gboolean match = FALSE;
7628 len = strlen(key);
7630 if (!strncmp(key, uri, len))
7631 match = TRUE;
7632 else {
7633 voffset = strstr(uri, "/") + 2;
7634 if (!strncmp(key, voffset, len))
7635 match = TRUE;
7636 else if (g_str_has_prefix(voffset, "www.")) {
7637 voffset = voffset + strlen("www.");
7638 if (!strncmp(key, voffset, len))
7639 match = TRUE;
7643 return (match);
7646 void
7647 cmd_getlist(int id, char *key)
7649 int i, dep, c = 0;
7650 struct history *h;
7652 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7653 RB_FOREACH_REVERSE(h, history_list, &hl)
7654 if (match_uri(h->uri, key)) {
7655 cmd_status.list[c] = (char *)h->uri;
7656 if (++c > 255)
7657 break;
7660 cmd_status.len = c;
7661 return;
7664 dep = (id == -1) ? 0 : cmds[id].level + 1;
7666 for (i = id + 1; i < LENGTH(cmds); i++) {
7667 if (cmds[i].level < dep)
7668 break;
7669 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7670 strlen(key)))
7671 cmd_status.list[c++] = cmds[i].cmd;
7675 cmd_status.len = c;
7678 char *
7679 cmd_getnext(int dir)
7681 cmd_status.index += dir;
7683 if (cmd_status.index < 0)
7684 cmd_status.index = cmd_status.len - 1;
7685 else if (cmd_status.index >= cmd_status.len)
7686 cmd_status.index = 0;
7688 return cmd_status.list[cmd_status.index];
7692 cmd_tokenize(char *s, char *tokens[])
7694 int i = 0;
7695 char *tok, *last;
7696 size_t len = strlen(s);
7697 bool blank;
7699 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7700 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7701 tok = strtok_r(NULL, " ", &last), i++)
7702 tokens[i] = tok;
7704 if (blank && i < 3)
7705 tokens[i++] = "";
7707 return (i);
7710 void
7711 cmd_complete(struct tab *t, char *str, int dir)
7713 GtkEntry *w = GTK_ENTRY(t->cmd);
7714 int i, j, levels, c = 0, dep = 0, parent = -1;
7715 int matchcount = 0;
7716 char *tok, *match, *s = g_strdup(str);
7717 char *tokens[3];
7718 char res[XT_MAX_URL_LENGTH + 32] = ":";
7719 char *sc = s;
7721 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7723 /* copy prefix*/
7724 for (i = 0; isdigit(s[i]); i++)
7725 res[i + 1] = s[i];
7727 for (; isspace(s[i]); i++)
7728 res[i + 1] = s[i];
7730 s += i;
7732 levels = cmd_tokenize(s, tokens);
7734 for (i = 0; i < levels - 1; i++) {
7735 tok = tokens[i];
7736 matchcount = 0;
7737 for (j = c; j < LENGTH(cmds); j++) {
7738 if (cmds[j].level < dep)
7739 break;
7740 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7741 strlen(tok))) {
7742 matchcount++;
7743 c = j + 1;
7744 if (strlen(tok) == strlen(cmds[j].cmd)) {
7745 matchcount = 1;
7746 break;
7751 if (matchcount == 1) {
7752 strlcat(res, tok, sizeof res);
7753 strlcat(res, " ", sizeof res);
7754 dep++;
7755 } else {
7756 g_free(sc);
7757 return;
7760 parent = c - 1;
7763 if (cmd_status.index == -1)
7764 cmd_getlist(parent, tokens[i]);
7766 if (cmd_status.len > 0) {
7767 match = cmd_getnext(dir);
7768 strlcat(res, match, sizeof res);
7769 gtk_entry_set_text(w, res);
7770 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7773 g_free(sc);
7776 gboolean
7777 cmd_execute(struct tab *t, char *str)
7779 struct cmd *cmd = NULL;
7780 char *tok, *last, *s = g_strdup(str), *sc;
7781 char prefixstr[4];
7782 int j, len, c = 0, dep = 0, matchcount = 0;
7783 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7784 struct karg arg = {0, NULL, -1};
7786 sc = s;
7788 /* copy prefix*/
7789 for (j = 0; j<3 && isdigit(s[j]); j++)
7790 prefixstr[j]=s[j];
7792 prefixstr[j]='\0';
7794 s += j;
7795 while (isspace(s[0]))
7796 s++;
7798 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7799 prefix = atoi(prefixstr);
7800 else
7801 s = sc;
7803 for (tok = strtok_r(s, " ", &last); tok;
7804 tok = strtok_r(NULL, " ", &last)) {
7805 matchcount = 0;
7806 for (j = c; j < LENGTH(cmds); j++) {
7807 if (cmds[j].level < dep)
7808 break;
7809 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7810 strlen(tok);
7811 if (cmds[j].level == dep &&
7812 !strncmp(tok, cmds[j].cmd, len)) {
7813 matchcount++;
7814 c = j + 1;
7815 cmd = &cmds[j];
7816 if (len == strlen(cmds[j].cmd)) {
7817 matchcount = 1;
7818 break;
7822 if (matchcount == 1) {
7823 if (cmd->type > 0)
7824 goto execute_cmd;
7825 dep++;
7826 } else {
7827 show_oops(t, "Invalid command: %s", str);
7828 goto done;
7831 execute_cmd:
7832 arg.i = cmd->arg;
7834 if (prefix != -1)
7835 arg.precount = prefix;
7836 else if (cmd_prefix > 0)
7837 arg.precount = cmd_prefix;
7839 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7840 show_oops(t, "No prefix allowed: %s", str);
7841 goto done;
7843 if (cmd->type > 1)
7844 arg.s = last ? g_strdup(last) : g_strdup("");
7845 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7846 arg.precount = atoi(arg.s);
7847 if (arg.precount <= 0) {
7848 if (arg.s[0] == '0')
7849 show_oops(t, "Zero count");
7850 else
7851 show_oops(t, "Trailing characters");
7852 goto done;
7856 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7857 __func__, arg.precount, arg.s);
7859 cmd->func(t, &arg);
7861 rv = XT_CB_HANDLED;
7862 done:
7863 if (j > 0)
7864 cmd_prefix = 0;
7865 g_free(sc);
7866 if (arg.s)
7867 g_free(arg.s);
7869 return (rv);
7873 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7875 if (t == NULL) {
7876 show_oops(NULL, "entry_key_cb invalid parameters");
7877 return (XT_CB_PASSTHROUGH);
7880 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7881 e->keyval, e->state, t);
7883 hide_oops(t);
7885 if (e->keyval == GDK_Escape) {
7886 /* don't use focus_webview(t) because we want to type :cmds */
7887 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7890 return (handle_keypress(t, e, 1));
7894 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7896 int rv = XT_CB_HANDLED;
7897 const gchar *c = gtk_entry_get_text(w);
7899 if (t == NULL) {
7900 show_oops(NULL, "cmd_keypress_cb parameters");
7901 return (XT_CB_PASSTHROUGH);
7904 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7905 e->keyval, e->state, t);
7907 /* sanity */
7908 if (c == NULL)
7909 e->keyval = GDK_Escape;
7910 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7911 e->keyval = GDK_Escape;
7913 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7914 e->keyval != GDK_ISO_Left_Tab)
7915 cmd_status.index = -1;
7917 switch (e->keyval) {
7918 case GDK_Tab:
7919 if (c[0] == ':')
7920 cmd_complete(t, (char *)&c[1], 1);
7921 goto done;
7922 case GDK_ISO_Left_Tab:
7923 if (c[0] == ':')
7924 cmd_complete(t, (char *)&c[1], -1);
7926 goto done;
7927 case GDK_BackSpace:
7928 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7929 break;
7930 /* FALLTHROUGH */
7931 case GDK_Escape:
7932 hide_cmd(t);
7933 focus_webview(t);
7935 /* cancel search */
7936 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7937 webkit_web_view_unmark_text_matches(t->wv);
7938 goto done;
7941 rv = XT_CB_PASSTHROUGH;
7942 done:
7943 return (rv);
7947 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7949 if (t == NULL) {
7950 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7951 return (XT_CB_PASSTHROUGH);
7953 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7955 hide_cmd(t);
7956 hide_oops(t);
7958 if (show_url == 0 || t->focus_wv)
7959 focus_webview(t);
7960 else
7961 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7963 return (XT_CB_PASSTHROUGH);
7966 void
7967 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7969 char *s;
7970 const gchar *c = gtk_entry_get_text(entry);
7972 if (t == NULL) {
7973 show_oops(NULL, "cmd_activate_cb invalid parameters");
7974 return;
7977 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7979 hide_cmd(t);
7981 /* sanity */
7982 if (c == NULL)
7983 goto done;
7984 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7985 goto done;
7986 if (strlen(c) < 2)
7987 goto done;
7988 s = (char *)&c[1];
7990 if (c[0] == '/' || c[0] == '?') {
7991 /* see if there is a timer pending */
7992 if (t->search_id) {
7993 g_source_remove(t->search_id);
7994 t->search_id = 0;
7995 search_cb(t);
7998 if (t->search_text) {
7999 g_free(t->search_text);
8000 t->search_text = NULL;
8003 t->search_text = g_strdup(s);
8004 if (global_search)
8005 g_free(global_search);
8006 global_search = g_strdup(s);
8007 t->search_forward = c[0] == '/';
8009 goto done;
8012 cmd_execute(t, s);
8014 done:
8015 return;
8018 void
8019 backward_cb(GtkWidget *w, struct tab *t)
8021 struct karg a;
8023 if (t == NULL) {
8024 show_oops(NULL, "backward_cb invalid parameters");
8025 return;
8028 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8030 a.i = XT_NAV_BACK;
8031 navaction(t, &a);
8034 void
8035 forward_cb(GtkWidget *w, struct tab *t)
8037 struct karg a;
8039 if (t == NULL) {
8040 show_oops(NULL, "forward_cb invalid parameters");
8041 return;
8044 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8046 a.i = XT_NAV_FORWARD;
8047 navaction(t, &a);
8050 void
8051 home_cb(GtkWidget *w, struct tab *t)
8053 if (t == NULL) {
8054 show_oops(NULL, "home_cb invalid parameters");
8055 return;
8058 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8060 load_uri(t, home);
8063 void
8064 stop_cb(GtkWidget *w, struct tab *t)
8066 WebKitWebFrame *frame;
8068 if (t == NULL) {
8069 show_oops(NULL, "stop_cb invalid parameters");
8070 return;
8073 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8075 frame = webkit_web_view_get_main_frame(t->wv);
8076 if (frame == NULL) {
8077 show_oops(t, "stop_cb: no frame");
8078 return;
8081 webkit_web_frame_stop_loading(frame);
8082 abort_favicon_download(t);
8085 void
8086 setup_webkit(struct tab *t)
8088 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8089 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8090 FALSE, (char *)NULL);
8091 else
8092 warnx("webkit does not have \"enable-dns-prefetching\" property");
8093 g_object_set(G_OBJECT(t->settings),
8094 "user-agent", t->user_agent, (char *)NULL);
8095 g_object_set(G_OBJECT(t->settings),
8096 "enable-scripts", enable_scripts, (char *)NULL);
8097 g_object_set(G_OBJECT(t->settings),
8098 "enable-plugins", enable_plugins, (char *)NULL);
8099 g_object_set(G_OBJECT(t->settings),
8100 "javascript-can-open-windows-automatically", enable_scripts,
8101 (char *)NULL);
8102 g_object_set(G_OBJECT(t->settings),
8103 "enable-html5-database", FALSE, (char *)NULL);
8104 g_object_set(G_OBJECT(t->settings),
8105 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8106 g_object_set(G_OBJECT(t->settings),
8107 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8108 g_object_set(G_OBJECT(t->settings),
8109 "spell_checking_languages", spell_check_languages, (char *)NULL);
8110 g_object_set(G_OBJECT(t->wv),
8111 "full-content-zoom", TRUE, (char *)NULL);
8113 webkit_web_view_set_settings(t->wv, t->settings);
8116 gboolean
8117 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8119 struct tab *ti, *t = NULL;
8120 gdouble view_size, value, max;
8121 gchar *position;
8123 TAILQ_FOREACH(ti, &tabs, entry)
8124 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8125 t = ti;
8126 break;
8129 if (t == NULL)
8130 return FALSE;
8132 if (adjustment == NULL)
8133 adjustment = gtk_scrolled_window_get_vadjustment(
8134 GTK_SCROLLED_WINDOW(t->browser_win));
8136 view_size = gtk_adjustment_get_page_size(adjustment);
8137 value = gtk_adjustment_get_value(adjustment);
8138 max = gtk_adjustment_get_upper(adjustment) - view_size;
8140 if (max == 0)
8141 position = g_strdup("All");
8142 else if (value == max)
8143 position = g_strdup("Bot");
8144 else if (value == 0)
8145 position = g_strdup("Top");
8146 else
8147 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8149 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8150 g_free(position);
8152 return (TRUE);
8155 GtkWidget *
8156 create_browser(struct tab *t)
8158 GtkWidget *w;
8159 gchar *strval;
8160 GtkAdjustment *adjustment;
8162 if (t == NULL) {
8163 show_oops(NULL, "create_browser invalid parameters");
8164 return (NULL);
8167 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8168 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8169 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8170 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8172 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8173 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8174 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8176 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8177 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8179 /* set defaults */
8180 t->settings = webkit_web_settings_new();
8182 if (user_agent == NULL) {
8183 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8184 (char *)NULL);
8185 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8186 g_free(strval);
8187 } else
8188 t->user_agent = g_strdup(user_agent);
8190 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8192 adjustment =
8193 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8194 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8195 G_CALLBACK(update_statusbar_position), NULL);
8197 setup_webkit(t);
8199 return (w);
8202 GtkWidget *
8203 create_window(void)
8205 GtkWidget *w;
8207 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8208 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8209 gtk_widget_set_name(w, "xxxterm");
8210 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8211 g_signal_connect(G_OBJECT(w), "delete_event",
8212 G_CALLBACK (gtk_main_quit), NULL);
8214 return (w);
8217 GtkWidget *
8218 create_kiosk_toolbar(struct tab *t)
8220 GtkWidget *toolbar = NULL, *b;
8222 b = gtk_hbox_new(FALSE, 0);
8223 toolbar = b;
8224 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8226 /* backward button */
8227 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8228 gtk_widget_set_sensitive(t->backward, FALSE);
8229 g_signal_connect(G_OBJECT(t->backward), "clicked",
8230 G_CALLBACK(backward_cb), t);
8231 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8233 /* forward button */
8234 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8235 gtk_widget_set_sensitive(t->forward, FALSE);
8236 g_signal_connect(G_OBJECT(t->forward), "clicked",
8237 G_CALLBACK(forward_cb), t);
8238 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8240 /* home button */
8241 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8242 gtk_widget_set_sensitive(t->gohome, true);
8243 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8244 G_CALLBACK(home_cb), t);
8245 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8247 /* create widgets but don't use them */
8248 t->uri_entry = gtk_entry_new();
8249 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8250 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8251 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8253 return (toolbar);
8256 GtkWidget *
8257 create_toolbar(struct tab *t)
8259 GtkWidget *toolbar = NULL, *b, *eb1;
8261 b = gtk_hbox_new(FALSE, 0);
8262 toolbar = b;
8263 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8265 /* backward button */
8266 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8267 gtk_widget_set_sensitive(t->backward, FALSE);
8268 g_signal_connect(G_OBJECT(t->backward), "clicked",
8269 G_CALLBACK(backward_cb), t);
8270 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8272 /* forward button */
8273 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8274 gtk_widget_set_sensitive(t->forward, FALSE);
8275 g_signal_connect(G_OBJECT(t->forward), "clicked",
8276 G_CALLBACK(forward_cb), t);
8277 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8278 FALSE, 0);
8280 /* stop button */
8281 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8282 gtk_widget_set_sensitive(t->stop, FALSE);
8283 g_signal_connect(G_OBJECT(t->stop), "clicked",
8284 G_CALLBACK(stop_cb), t);
8285 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8286 FALSE, 0);
8288 /* JS button */
8289 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8290 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8291 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8292 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8293 G_CALLBACK(js_toggle_cb), t);
8294 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8296 t->uri_entry = gtk_entry_new();
8297 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8298 G_CALLBACK(activate_uri_entry_cb), t);
8299 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8300 G_CALLBACK(entry_key_cb), t);
8301 completion_add(t);
8302 eb1 = gtk_hbox_new(FALSE, 0);
8303 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8304 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8305 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8307 /* search entry */
8308 if (search_string) {
8309 GtkWidget *eb2;
8310 t->search_entry = gtk_entry_new();
8311 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8312 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8313 G_CALLBACK(activate_search_entry_cb), t);
8314 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8315 G_CALLBACK(entry_key_cb), t);
8316 gtk_widget_set_size_request(t->search_entry, -1, -1);
8317 eb2 = gtk_hbox_new(FALSE, 0);
8318 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8319 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8321 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8324 return (toolbar);
8327 GtkWidget *
8328 create_buffers(struct tab *t)
8330 GtkCellRenderer *renderer;
8331 GtkWidget *view;
8333 view = gtk_tree_view_new();
8335 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8337 renderer = gtk_cell_renderer_text_new();
8338 gtk_tree_view_insert_column_with_attributes
8339 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8341 renderer = gtk_cell_renderer_text_new();
8342 gtk_tree_view_insert_column_with_attributes
8343 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8344 NULL);
8346 gtk_tree_view_set_model
8347 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8349 return view;
8352 void
8353 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8354 GtkTreeViewColumn *col, struct tab *t)
8356 GtkTreeIter iter;
8357 guint id;
8359 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8361 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8362 path)) {
8363 gtk_tree_model_get
8364 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8365 set_current_tab(id - 1);
8368 hide_buffers(t);
8371 /* after tab reordering/creation/removal */
8372 void
8373 recalc_tabs(void)
8375 struct tab *t;
8376 int maxid = 0;
8378 TAILQ_FOREACH(t, &tabs, entry) {
8379 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8380 if (t->tab_id > maxid)
8381 maxid = t->tab_id;
8383 gtk_widget_show(t->tab_elems.sep);
8386 TAILQ_FOREACH(t, &tabs, entry) {
8387 if (t->tab_id == maxid) {
8388 gtk_widget_hide(t->tab_elems.sep);
8389 break;
8394 /* after active tab change */
8395 void
8396 recolor_compact_tabs(void)
8398 struct tab *t;
8399 int curid = 0;
8400 GdkColor color;
8402 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8403 TAILQ_FOREACH(t, &tabs, entry)
8404 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8405 &color);
8407 curid = gtk_notebook_get_current_page(notebook);
8408 TAILQ_FOREACH(t, &tabs, entry)
8409 if (t->tab_id == curid) {
8410 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8411 gtk_widget_modify_fg(t->tab_elems.label,
8412 GTK_STATE_NORMAL, &color);
8413 break;
8417 void
8418 set_current_tab(int page_num)
8420 buffercmd_abort(get_current_tab());
8421 gtk_notebook_set_current_page(notebook, page_num);
8422 recolor_compact_tabs();
8426 undo_close_tab_save(struct tab *t)
8428 int m, n;
8429 const gchar *uri;
8430 struct undo *u1, *u2;
8431 GList *items;
8432 WebKitWebHistoryItem *item;
8434 if ((uri = get_uri(t)) == NULL)
8435 return (1);
8437 u1 = g_malloc0(sizeof(struct undo));
8438 u1->uri = g_strdup(uri);
8440 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8442 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8443 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8444 u1->back = n;
8446 /* forward history */
8447 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8449 while (items) {
8450 item = items->data;
8451 u1->history = g_list_prepend(u1->history,
8452 webkit_web_history_item_copy(item));
8453 items = g_list_next(items);
8456 /* current item */
8457 if (m) {
8458 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8459 u1->history = g_list_prepend(u1->history,
8460 webkit_web_history_item_copy(item));
8463 /* back history */
8464 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8466 while (items) {
8467 item = items->data;
8468 u1->history = g_list_prepend(u1->history,
8469 webkit_web_history_item_copy(item));
8470 items = g_list_next(items);
8473 TAILQ_INSERT_HEAD(&undos, u1, entry);
8475 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8476 u2 = TAILQ_LAST(&undos, undo_tailq);
8477 TAILQ_REMOVE(&undos, u2, entry);
8478 g_free(u2->uri);
8479 g_list_free(u2->history);
8480 g_free(u2);
8481 } else
8482 undo_count++;
8484 return (0);
8487 void
8488 delete_tab(struct tab *t)
8490 struct karg a;
8492 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8494 if (t == NULL)
8495 return;
8497 buffercmd_abort(t);
8498 TAILQ_REMOVE(&tabs, t, entry);
8500 /* Halt all webkit activity. */
8501 abort_favicon_download(t);
8502 webkit_web_view_stop_loading(t->wv);
8504 /* Save the tab, so we can undo the close. */
8505 undo_close_tab_save(t);
8507 if (browser_mode == XT_BM_KIOSK) {
8508 gtk_widget_destroy(t->uri_entry);
8509 gtk_widget_destroy(t->stop);
8510 gtk_widget_destroy(t->js_toggle);
8513 gtk_widget_destroy(t->tab_elems.eventbox);
8514 gtk_widget_destroy(t->vbox);
8516 /* just in case */
8517 if (t->search_id)
8518 g_source_remove(t->search_id);
8520 g_free(t->user_agent);
8521 g_free(t->stylesheet);
8522 g_free(t->tmp_uri);
8523 g_free(t);
8525 if (TAILQ_EMPTY(&tabs)) {
8526 if (browser_mode == XT_BM_KIOSK)
8527 create_new_tab(home, NULL, 1, -1);
8528 else
8529 create_new_tab(NULL, NULL, 1, -1);
8532 /* recreate session */
8533 if (session_autosave) {
8534 a.s = NULL;
8535 save_tabs(t, &a);
8538 recalc_tabs();
8539 recolor_compact_tabs();
8542 void
8543 update_statusbar_zoom(struct tab *t)
8545 gfloat zoom;
8546 char s[16] = { '\0' };
8548 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8549 if ((zoom <= 0.99 || zoom >= 1.01))
8550 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8551 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8554 void
8555 setzoom_webkit(struct tab *t, int adjust)
8557 #define XT_ZOOMPERCENT 0.04
8559 gfloat zoom;
8561 if (t == NULL) {
8562 show_oops(NULL, "setzoom_webkit invalid parameters");
8563 return;
8566 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8567 if (adjust == XT_ZOOM_IN)
8568 zoom += XT_ZOOMPERCENT;
8569 else if (adjust == XT_ZOOM_OUT)
8570 zoom -= XT_ZOOMPERCENT;
8571 else if (adjust > 0)
8572 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8573 else {
8574 show_oops(t, "setzoom_webkit invalid zoom value");
8575 return;
8578 if (zoom < XT_ZOOMPERCENT)
8579 zoom = XT_ZOOMPERCENT;
8580 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8581 update_statusbar_zoom(t);
8584 gboolean
8585 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8587 struct tab *t = (struct tab *) data;
8589 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8591 switch (event->button) {
8592 case 1:
8593 set_current_tab(t->tab_id);
8594 break;
8595 case 2:
8596 delete_tab(t);
8597 break;
8600 return TRUE;
8603 void
8604 append_tab(struct tab *t)
8606 if (t == NULL)
8607 return;
8609 TAILQ_INSERT_TAIL(&tabs, t, entry);
8610 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8613 GtkWidget *
8614 create_sbe(int width)
8616 GtkWidget *sbe;
8618 sbe = gtk_entry_new();
8619 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8620 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8621 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8622 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8623 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8624 gtk_widget_set_size_request(sbe, width, -1);
8626 return sbe;
8629 struct tab *
8630 create_new_tab(char *title, struct undo *u, int focus, int position)
8632 struct tab *t;
8633 int load = 1, id;
8634 GtkWidget *b, *bb;
8635 WebKitWebHistoryItem *item;
8636 GList *items;
8637 GdkColor color;
8638 char *p;
8639 int sbe_p = 0, sbe_b = 0,
8640 sbe_z = 0;
8642 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8644 if (tabless && !TAILQ_EMPTY(&tabs)) {
8645 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8646 return (NULL);
8649 t = g_malloc0(sizeof *t);
8651 if (title == NULL) {
8652 title = "(untitled)";
8653 load = 0;
8656 t->vbox = gtk_vbox_new(FALSE, 0);
8658 /* label + button for tab */
8659 b = gtk_hbox_new(FALSE, 0);
8660 t->tab_content = b;
8662 #if GTK_CHECK_VERSION(2, 20, 0)
8663 t->spinner = gtk_spinner_new();
8664 #endif
8665 t->label = gtk_label_new(title);
8666 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8667 gtk_widget_set_size_request(t->label, 100, 0);
8668 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8669 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8670 gtk_widget_set_size_request(b, 130, 0);
8672 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8673 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8674 #if GTK_CHECK_VERSION(2, 20, 0)
8675 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8676 #endif
8678 /* toolbar */
8679 if (browser_mode == XT_BM_KIOSK) {
8680 t->toolbar = create_kiosk_toolbar(t);
8681 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8683 } else {
8684 t->toolbar = create_toolbar(t);
8685 if (fancy_bar)
8686 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8687 FALSE, 0);
8690 /* marks */
8691 marks_clear(t);
8693 /* browser */
8694 t->browser_win = create_browser(t);
8695 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8697 /* oops message for user feedback */
8698 t->oops = gtk_entry_new();
8699 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8700 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8701 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8702 gdk_color_parse(XT_COLOR_RED, &color);
8703 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8704 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8705 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8707 /* command entry */
8708 t->cmd = gtk_entry_new();
8709 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8710 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8711 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8712 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8714 /* status bar */
8715 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8717 t->sbe.statusbar = gtk_entry_new();
8718 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8719 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8720 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8721 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8723 /* create these widgets only if specified in statusbar_elems */
8725 t->sbe.position = create_sbe(40);
8726 t->sbe.zoom = create_sbe(40);
8727 t->sbe.buffercmd = create_sbe(60);
8729 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8731 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8732 TRUE, FALSE);
8734 /* gtk widgets cannot be added to a box twice. sbe_* variables
8735 make sure of this */
8736 for (p = statusbar_elems; *p != '\0'; p++) {
8737 switch (*p) {
8738 case '|':
8740 GtkWidget *sep = gtk_vseparator_new();
8742 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8743 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8744 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8745 FALSE, FALSE, FALSE);
8746 break;
8748 case 'P':
8749 if (sbe_p) {
8750 warnx("flag \"%c\" specified more than "
8751 "once in statusbar_elems\n", *p);
8752 break;
8754 sbe_p = 1;
8755 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8756 t->sbe.position, FALSE, FALSE, FALSE);
8757 break;
8758 case 'B':
8759 if (sbe_b) {
8760 warnx("flag \"%c\" specified more than "
8761 "once in statusbar_elems\n", *p);
8762 break;
8764 sbe_b = 1;
8765 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8766 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8767 break;
8768 case 'Z':
8769 if (sbe_z) {
8770 warnx("flag \"%c\" specified more than "
8771 "once in statusbar_elems\n", *p);
8772 break;
8774 sbe_z = 1;
8775 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8776 t->sbe.zoom, FALSE, FALSE, FALSE);
8777 break;
8778 default:
8779 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8780 break;
8784 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8786 /* buffer list */
8787 t->buffers = create_buffers(t);
8788 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8790 /* xtp meaning is normal by default */
8791 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8793 /* set empty favicon */
8794 xt_icon_from_name(t, "text-html");
8796 /* and show it all */
8797 gtk_widget_show_all(b);
8798 gtk_widget_show_all(t->vbox);
8800 /* compact tab bar */
8801 t->tab_elems.label = gtk_label_new(title);
8802 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8803 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8804 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8805 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8807 t->tab_elems.eventbox = gtk_event_box_new();
8808 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8809 t->tab_elems.sep = gtk_vseparator_new();
8811 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8812 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8813 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8814 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8815 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8816 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8818 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8819 TRUE, 0);
8820 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8821 FALSE, 0);
8822 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8823 t->tab_elems.box);
8825 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8826 TRUE, 0);
8827 gtk_widget_show_all(t->tab_elems.eventbox);
8829 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8830 append_tab(t);
8831 else {
8832 id = position >= 0 ? position :
8833 gtk_notebook_get_current_page(notebook) + 1;
8834 if (id > gtk_notebook_get_n_pages(notebook))
8835 append_tab(t);
8836 else {
8837 TAILQ_INSERT_TAIL(&tabs, t, entry);
8838 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8839 gtk_box_reorder_child(GTK_BOX(tab_bar),
8840 t->tab_elems.eventbox, id);
8841 recalc_tabs();
8845 #if GTK_CHECK_VERSION(2, 20, 0)
8846 /* turn spinner off if we are a new tab without uri */
8847 if (!load) {
8848 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8849 gtk_widget_hide(t->spinner);
8851 #endif
8852 /* make notebook tabs reorderable */
8853 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8855 /* compact tabs clickable */
8856 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8857 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8859 g_object_connect(G_OBJECT(t->cmd),
8860 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8861 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8862 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8863 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8864 (char *)NULL);
8866 /* reuse wv_button_cb to hide oops */
8867 g_object_connect(G_OBJECT(t->oops),
8868 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8869 (char *)NULL);
8871 g_signal_connect(t->buffers,
8872 "row-activated", G_CALLBACK(row_activated_cb), t);
8873 g_object_connect(G_OBJECT(t->buffers),
8874 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8876 g_object_connect(G_OBJECT(t->wv),
8877 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8878 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8879 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8880 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8881 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8882 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8883 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8884 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8885 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8886 "signal::event", G_CALLBACK(webview_event_cb), t,
8887 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8888 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8889 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8890 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8891 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8892 (char *)NULL);
8893 g_signal_connect(t->wv,
8894 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8896 * XXX this puts invalid url in uri_entry and that is undesirable
8898 #if 0
8899 g_signal_connect(t->wv,
8900 "load-error", G_CALLBACK(notify_load_error_cb), t);
8901 #endif
8902 g_signal_connect(t->wv,
8903 "notify::title", G_CALLBACK(notify_title_cb), t);
8905 /* hijack the unused keys as if we were the browser */
8906 g_object_connect(G_OBJECT(t->toolbar),
8907 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8908 (char *)NULL);
8910 g_signal_connect(G_OBJECT(bb), "button_press_event",
8911 G_CALLBACK(tab_close_cb), t);
8913 /* hide stuff */
8914 hide_cmd(t);
8915 hide_oops(t);
8916 hide_buffers(t);
8917 url_set_visibility();
8918 statusbar_set_visibility();
8920 if (focus) {
8921 set_current_tab(t->tab_id);
8922 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8923 t->tab_id);
8926 if (load) {
8927 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8928 load_uri(t, title);
8929 } else {
8930 if (show_url == 1)
8931 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8932 else
8933 focus_webview(t);
8936 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8937 /* restore the tab's history */
8938 if (u && u->history) {
8939 items = u->history;
8940 while (items) {
8941 item = items->data;
8942 webkit_web_back_forward_list_add_item(t->bfl, item);
8943 items = g_list_next(items);
8946 item = g_list_nth_data(u->history, u->back);
8947 if (item)
8948 webkit_web_view_go_to_back_forward_item(t->wv, item);
8950 g_list_free(items);
8951 g_list_free(u->history);
8952 } else
8953 webkit_web_back_forward_list_clear(t->bfl);
8955 recolor_compact_tabs();
8956 setzoom_webkit(t, XT_ZOOM_NORMAL);
8957 return (t);
8960 void
8961 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8962 gpointer *udata)
8964 struct tab *t;
8965 const gchar *uri;
8967 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8969 if (gtk_notebook_get_current_page(notebook) == -1)
8970 recalc_tabs();
8972 TAILQ_FOREACH(t, &tabs, entry) {
8973 if (t->tab_id == pn) {
8974 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8975 "%d\n", pn);
8977 uri = get_title(t, TRUE);
8978 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8980 hide_cmd(t);
8981 hide_oops(t);
8983 if (t->focus_wv) {
8984 /* can't use focus_webview here */
8985 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8991 void
8992 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8993 gpointer *udata)
8995 struct tab *t = NULL, *tt;
8997 recalc_tabs();
8999 TAILQ_FOREACH(tt, &tabs, entry)
9000 if (tt->tab_id == pn) {
9001 t = tt;
9002 break;
9005 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9007 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9008 t->tab_id);
9011 void
9012 menuitem_response(struct tab *t)
9014 gtk_notebook_set_current_page(notebook, t->tab_id);
9017 gboolean
9018 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9020 GtkWidget *menu, *menu_items;
9021 GdkEventButton *bevent;
9022 const gchar *uri;
9023 struct tab *ti;
9025 if (event->type == GDK_BUTTON_PRESS) {
9026 bevent = (GdkEventButton *) event;
9027 menu = gtk_menu_new();
9029 TAILQ_FOREACH(ti, &tabs, entry) {
9030 if ((uri = get_uri(ti)) == NULL)
9031 /* XXX make sure there is something to print */
9032 /* XXX add gui pages in here to look purdy */
9033 uri = "(untitled)";
9034 menu_items = gtk_menu_item_new_with_label(uri);
9035 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9036 gtk_widget_show(menu_items);
9038 g_signal_connect_swapped((menu_items),
9039 "activate", G_CALLBACK(menuitem_response),
9040 (gpointer)ti);
9043 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9044 bevent->button, bevent->time);
9046 /* unref object so it'll free itself when popped down */
9047 #if !GTK_CHECK_VERSION(3, 0, 0)
9048 /* XXX does not need unref with gtk+3? */
9049 g_object_ref_sink(menu);
9050 g_object_unref(menu);
9051 #endif
9053 return (TRUE /* eat event */);
9056 return (FALSE /* propagate */);
9060 icon_size_map(int icon_size)
9062 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9063 icon_size > GTK_ICON_SIZE_DIALOG)
9064 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9066 return (icon_size);
9069 GtkWidget *
9070 create_button(char *name, char *stockid, int size)
9072 GtkWidget *button, *image;
9073 gchar *rcstring;
9074 int gtk_icon_size;
9076 rcstring = g_strdup_printf(
9077 "style \"%s-style\"\n"
9078 "{\n"
9079 " GtkWidget::focus-padding = 0\n"
9080 " GtkWidget::focus-line-width = 0\n"
9081 " xthickness = 0\n"
9082 " ythickness = 0\n"
9083 "}\n"
9084 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9085 gtk_rc_parse_string(rcstring);
9086 g_free(rcstring);
9087 button = gtk_button_new();
9088 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9089 gtk_icon_size = icon_size_map(size ? size : icon_size);
9091 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9092 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9093 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9094 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9095 gtk_widget_set_name(button, name);
9096 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9098 return (button);
9101 void
9102 button_set_stockid(GtkWidget *button, char *stockid)
9104 GtkWidget *image;
9106 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9107 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9108 gtk_button_set_image(GTK_BUTTON(button), image);
9111 void
9112 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9114 gchar *p = NULL;
9115 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9116 gint len;
9118 if (xterm_workaround == 0)
9119 return;
9122 * xterm doesn't play nice with clipboards because it clears the
9123 * primary when clicked. We rely on primary being set to properly
9124 * handle middle mouse button clicks (paste). So when someone clears
9125 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9126 * other application behavior (as in DON'T clear primary).
9129 p = gtk_clipboard_wait_for_text(primary);
9130 if (p == NULL) {
9131 if (gdk_property_get(gdk_get_default_root_window(),
9132 atom,
9133 gdk_atom_intern("STRING", FALSE),
9135 1024 * 1024 /* picked out of my butt */,
9136 FALSE,
9137 NULL,
9138 NULL,
9139 &len,
9140 (guchar **)&p)) {
9141 /* yes sir, we need to NUL the string */
9142 p[len] = '\0';
9143 gtk_clipboard_set_text(primary, p, -1);
9147 if (p)
9148 g_free(p);
9151 void
9152 create_canvas(void)
9154 GtkWidget *vbox;
9155 GList *l = NULL;
9156 GdkPixbuf *pb;
9157 char file[PATH_MAX];
9158 int i;
9160 vbox = gtk_vbox_new(FALSE, 0);
9161 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9162 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9163 #if !GTK_CHECK_VERSION(3, 0, 0)
9164 /* XXX seems to be needed with gtk+2 */
9165 gtk_notebook_set_tab_hborder(notebook, 0);
9166 gtk_notebook_set_tab_vborder(notebook, 0);
9167 #endif
9168 gtk_notebook_set_scrollable(notebook, TRUE);
9169 gtk_notebook_set_show_border(notebook, FALSE);
9170 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9172 abtn = gtk_button_new();
9173 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9174 gtk_widget_set_size_request(arrow, -1, -1);
9175 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9176 gtk_widget_set_size_request(abtn, -1, 20);
9178 #if GTK_CHECK_VERSION(2, 20, 0)
9179 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9180 #endif
9181 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9183 /* compact tab bar */
9184 tab_bar = gtk_hbox_new(TRUE, 0);
9186 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9187 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9188 gtk_widget_set_size_request(vbox, -1, -1);
9190 g_object_connect(G_OBJECT(notebook),
9191 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9192 (char *)NULL);
9193 g_object_connect(G_OBJECT(notebook),
9194 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9195 NULL, (char *)NULL);
9196 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9197 G_CALLBACK(arrow_cb), NULL);
9199 main_window = create_window();
9200 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9202 /* icons */
9203 for (i = 0; i < LENGTH(icons); i++) {
9204 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9205 pb = gdk_pixbuf_new_from_file(file, NULL);
9206 l = g_list_append(l, pb);
9208 gtk_window_set_default_icon_list(l);
9210 /* clipboard work around */
9211 if (xterm_workaround)
9212 g_signal_connect(
9213 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9214 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9216 gtk_widget_show_all(abtn);
9217 gtk_widget_show_all(main_window);
9218 notebook_tab_set_visibility();
9221 void
9222 set_hook(void **hook, char *name)
9224 if (hook == NULL)
9225 errx(1, "set_hook");
9227 if (*hook == NULL) {
9228 *hook = dlsym(RTLD_NEXT, name);
9229 if (*hook == NULL)
9230 errx(1, "can't hook %s", name);
9234 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9235 gboolean
9236 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9238 g_return_val_if_fail(cookie1, FALSE);
9239 g_return_val_if_fail(cookie2, FALSE);
9241 return (!strcmp (cookie1->name, cookie2->name) &&
9242 !strcmp (cookie1->value, cookie2->value) &&
9243 !strcmp (cookie1->path, cookie2->path) &&
9244 !strcmp (cookie1->domain, cookie2->domain));
9247 void
9248 transfer_cookies(void)
9250 GSList *cf;
9251 SoupCookie *sc, *pc;
9253 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9255 for (;cf; cf = cf->next) {
9256 pc = cf->data;
9257 sc = soup_cookie_copy(pc);
9258 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9261 soup_cookies_free(cf);
9264 void
9265 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9267 GSList *cf;
9268 SoupCookie *ci;
9270 print_cookie("soup_cookie_jar_delete_cookie", c);
9272 if (cookies_enabled == 0)
9273 return;
9275 if (jar == NULL || c == NULL)
9276 return;
9278 /* find and remove from persistent jar */
9279 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9281 for (;cf; cf = cf->next) {
9282 ci = cf->data;
9283 if (soup_cookie_equal(ci, c)) {
9284 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9285 break;
9289 soup_cookies_free(cf);
9291 /* delete from session jar */
9292 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9295 void
9296 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9298 struct domain *d = NULL;
9299 SoupCookie *c;
9300 FILE *r_cookie_f;
9302 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9303 jar, p_cookiejar, s_cookiejar);
9305 if (cookies_enabled == 0)
9306 return;
9308 /* see if we are up and running */
9309 if (p_cookiejar == NULL) {
9310 _soup_cookie_jar_add_cookie(jar, cookie);
9311 return;
9313 /* disallow p_cookiejar adds, shouldn't happen */
9314 if (jar == p_cookiejar)
9315 return;
9317 /* sanity */
9318 if (jar == NULL || cookie == NULL)
9319 return;
9321 if (enable_cookie_whitelist &&
9322 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9323 blocked_cookies++;
9324 DNPRINTF(XT_D_COOKIE,
9325 "soup_cookie_jar_add_cookie: reject %s\n",
9326 cookie->domain);
9327 if (save_rejected_cookies) {
9328 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9329 show_oops(NULL, "can't open reject cookie file");
9330 return;
9332 fseek(r_cookie_f, 0, SEEK_END);
9333 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9334 cookie->http_only ? "#HttpOnly_" : "",
9335 cookie->domain,
9336 *cookie->domain == '.' ? "TRUE" : "FALSE",
9337 cookie->path,
9338 cookie->secure ? "TRUE" : "FALSE",
9339 cookie->expires ?
9340 (gulong)soup_date_to_time_t(cookie->expires) :
9342 cookie->name,
9343 cookie->value);
9344 fflush(r_cookie_f);
9345 fclose(r_cookie_f);
9347 if (!allow_volatile_cookies)
9348 return;
9351 if (cookie->expires == NULL && session_timeout) {
9352 soup_cookie_set_expires(cookie,
9353 soup_date_new_from_now(session_timeout));
9354 print_cookie("modified add cookie", cookie);
9357 /* see if we are white listed for persistence */
9358 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9359 /* add to persistent jar */
9360 c = soup_cookie_copy(cookie);
9361 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9362 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9365 /* add to session jar */
9366 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9367 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9370 void
9371 setup_cookies(void)
9373 char file[PATH_MAX];
9375 set_hook((void *)&_soup_cookie_jar_add_cookie,
9376 "soup_cookie_jar_add_cookie");
9377 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9378 "soup_cookie_jar_delete_cookie");
9380 if (cookies_enabled == 0)
9381 return;
9384 * the following code is intricate due to overriding several libsoup
9385 * functions.
9386 * do not alter order of these operations.
9389 /* rejected cookies */
9390 if (save_rejected_cookies)
9391 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9392 XT_REJECT_FILE);
9394 /* persistent cookies */
9395 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9396 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9398 /* session cookies */
9399 s_cookiejar = soup_cookie_jar_new();
9400 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9401 cookie_policy, (void *)NULL);
9402 transfer_cookies();
9404 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9407 void
9408 setup_proxy(char *uri)
9410 SoupURI *suri;
9412 if (proxy_uri) {
9413 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9414 soup_uri_free(proxy_uri);
9415 proxy_uri = NULL;
9417 if (http_proxy) {
9418 if (http_proxy != uri) {
9419 g_free(http_proxy);
9420 http_proxy = NULL;
9424 if (uri) {
9425 http_proxy = g_strdup(uri);
9426 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9427 suri = soup_uri_new(http_proxy);
9428 if (!(suri == NULL || !SOUP_URI_VALID_FOR_HTTP(suri)))
9429 g_object_set(session, "proxy-uri", proxy_uri,
9430 (char *)NULL);
9431 if (suri)
9432 soup_uri_free(suri);
9437 set_http_proxy(char *proxy)
9439 SoupURI *uri;
9441 if (proxy == NULL)
9442 return (1);
9444 /* see if we need to clear it instead */
9445 if (strlen(proxy) == 0) {
9446 setup_proxy(NULL);
9447 return (0);
9450 uri = soup_uri_new(proxy);
9451 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9452 return (1);
9454 setup_proxy(proxy);
9456 soup_uri_free(uri);
9458 return (0);
9462 send_cmd_to_socket(char *cmd)
9464 int s, len, rv = 1;
9465 struct sockaddr_un sa;
9467 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9468 warnx("%s: socket", __func__);
9469 return (rv);
9472 sa.sun_family = AF_UNIX;
9473 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9474 work_dir, XT_SOCKET_FILE);
9475 len = SUN_LEN(&sa);
9477 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9478 warnx("%s: connect", __func__);
9479 goto done;
9482 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9483 warnx("%s: send", __func__);
9484 goto done;
9487 rv = 0;
9488 done:
9489 close(s);
9490 return (rv);
9493 gboolean
9494 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9496 int s, n;
9497 char str[XT_MAX_URL_LENGTH];
9498 socklen_t t = sizeof(struct sockaddr_un);
9499 struct sockaddr_un sa;
9500 struct passwd *p;
9501 uid_t uid;
9502 gid_t gid;
9503 struct tab *tt;
9504 gint fd = g_io_channel_unix_get_fd(source);
9506 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9507 warn("accept");
9508 return (FALSE);
9511 if (getpeereid(s, &uid, &gid) == -1) {
9512 warn("getpeereid");
9513 return (FALSE);
9515 if (uid != getuid() || gid != getgid()) {
9516 warnx("unauthorized user");
9517 return (FALSE);
9520 p = getpwuid(uid);
9521 if (p == NULL) {
9522 warnx("not a valid user");
9523 return (FALSE);
9526 n = recv(s, str, sizeof(str), 0);
9527 if (n <= 0)
9528 return (TRUE);
9530 tt = TAILQ_LAST(&tabs, tab_list);
9531 cmd_execute(tt, str);
9532 return (TRUE);
9536 is_running(void)
9538 int s, len, rv = 1;
9539 struct sockaddr_un sa;
9541 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9542 warn("is_running: socket");
9543 return (-1);
9546 sa.sun_family = AF_UNIX;
9547 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9548 work_dir, XT_SOCKET_FILE);
9549 len = SUN_LEN(&sa);
9551 /* connect to see if there is a listener */
9552 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9553 rv = 0; /* not running */
9554 else
9555 rv = 1; /* already running */
9557 close(s);
9559 return (rv);
9563 build_socket(void)
9565 int s, len;
9566 struct sockaddr_un sa;
9568 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9569 warn("build_socket: socket");
9570 return (-1);
9573 sa.sun_family = AF_UNIX;
9574 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9575 work_dir, XT_SOCKET_FILE);
9576 len = SUN_LEN(&sa);
9578 /* connect to see if there is a listener */
9579 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9580 /* no listener so we will */
9581 unlink(sa.sun_path);
9583 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9584 warn("build_socket: bind");
9585 goto done;
9588 if (listen(s, 1) == -1) {
9589 warn("build_socket: listen");
9590 goto done;
9593 return (s);
9596 done:
9597 close(s);
9598 return (-1);
9601 gboolean
9602 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9603 GtkTreeIter *iter, struct tab *t)
9605 gchar *value;
9607 gtk_tree_model_get(model, iter, 0, &value, -1);
9608 load_uri(t, value);
9609 g_free(value);
9611 return (FALSE);
9614 gboolean
9615 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9616 GtkTreeIter *iter, struct tab *t)
9618 gchar *value;
9620 gtk_tree_model_get(model, iter, 0, &value, -1);
9621 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9622 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9623 g_free(value);
9625 return (TRUE);
9628 void
9629 completion_add_uri(const gchar *uri)
9631 GtkTreeIter iter;
9633 /* add uri to list_store */
9634 gtk_list_store_append(completion_model, &iter);
9635 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9638 gboolean
9639 completion_match(GtkEntryCompletion *completion, const gchar *key,
9640 GtkTreeIter *iter, gpointer user_data)
9642 gchar *value;
9643 gboolean match = FALSE;
9645 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9646 -1);
9648 if (value == NULL)
9649 return FALSE;
9651 match = match_uri(value, key);
9653 g_free(value);
9654 return (match);
9657 void
9658 completion_add(struct tab *t)
9660 /* enable completion for tab */
9661 t->completion = gtk_entry_completion_new();
9662 gtk_entry_completion_set_text_column(t->completion, 0);
9663 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9664 gtk_entry_completion_set_model(t->completion,
9665 GTK_TREE_MODEL(completion_model));
9666 gtk_entry_completion_set_match_func(t->completion, completion_match,
9667 NULL, NULL);
9668 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9669 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9670 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9671 G_CALLBACK(completion_select_cb), t);
9672 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9673 G_CALLBACK(completion_hover_cb), t);
9676 void
9677 xxx_dir(char *dir)
9679 struct stat sb;
9681 if (stat(dir, &sb)) {
9682 if (mkdir(dir, S_IRWXU) == -1)
9683 err(1, "mkdir %s", dir);
9684 if (stat(dir, &sb))
9685 err(1, "stat %s", dir);
9687 if (S_ISDIR(sb.st_mode) == 0)
9688 errx(1, "%s not a dir", dir);
9689 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9690 warnx("fixing invalid permissions on %s", dir);
9691 if (chmod(dir, S_IRWXU) == -1)
9692 err(1, "chmod %s", dir);
9696 void
9697 usage(void)
9699 fprintf(stderr,
9700 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9701 exit(0);
9706 main(int argc, char *argv[])
9708 struct stat sb;
9709 int c, s, optn = 0, opte = 0, focus = 1;
9710 char conf[PATH_MAX] = { '\0' };
9711 char file[PATH_MAX];
9712 char *env_proxy = NULL;
9713 char *cmd = NULL;
9714 FILE *f = NULL;
9715 struct karg a;
9716 struct sigaction sact;
9717 GIOChannel *channel;
9718 struct rlimit rlp;
9720 start_argv = argv;
9722 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9724 RB_INIT(&hl);
9725 RB_INIT(&js_wl);
9726 RB_INIT(&downloads);
9728 TAILQ_INIT(&tabs);
9729 TAILQ_INIT(&mtl);
9730 TAILQ_INIT(&aliases);
9731 TAILQ_INIT(&undos);
9732 TAILQ_INIT(&kbl);
9733 TAILQ_INIT(&spl);
9735 /* fiddle with ulimits */
9736 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9737 warn("getrlimit");
9738 else {
9739 /* just use them all */
9740 rlp.rlim_cur = rlp.rlim_max;
9741 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9742 warn("setrlimit");
9743 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9744 warn("getrlimit");
9745 else if (rlp.rlim_cur <= 256)
9746 startpage_add("%s requires at least 256 file "
9747 "descriptors, currently it has up to %d available",
9748 __progname, rlp.rlim_cur);
9751 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9752 switch (c) {
9753 case 'S':
9754 show_url = 0;
9755 break;
9756 case 'T':
9757 show_tabs = 0;
9758 break;
9759 case 'V':
9760 errx(0 , "Version: %s", version);
9761 break;
9762 case 'f':
9763 strlcpy(conf, optarg, sizeof(conf));
9764 break;
9765 case 's':
9766 strlcpy(named_session, optarg, sizeof(named_session));
9767 break;
9768 case 't':
9769 tabless = 1;
9770 break;
9771 case 'n':
9772 optn = 1;
9773 break;
9774 case 'e':
9775 opte = 1;
9776 break;
9777 default:
9778 usage();
9779 /* NOTREACHED */
9782 argc -= optind;
9783 argv += optind;
9785 init_keybindings();
9787 gnutls_global_init();
9789 /* generate session keys for xtp pages */
9790 generate_xtp_session_key(&dl_session_key);
9791 generate_xtp_session_key(&hl_session_key);
9792 generate_xtp_session_key(&cl_session_key);
9793 generate_xtp_session_key(&fl_session_key);
9795 /* prepare gtk */
9796 if (!g_thread_supported()) {
9797 g_thread_init(NULL);
9798 gdk_threads_init();
9799 gdk_threads_enter();
9801 gtk_init(&argc, &argv);
9803 /* signals */
9804 bzero(&sact, sizeof(sact));
9805 sigemptyset(&sact.sa_mask);
9806 sact.sa_handler = sigchild;
9807 sact.sa_flags = SA_NOCLDSTOP;
9808 sigaction(SIGCHLD, &sact, NULL);
9810 /* set download dir */
9811 pwd = getpwuid(getuid());
9812 if (pwd == NULL)
9813 errx(1, "invalid user %d", getuid());
9814 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9816 /* compile buffer command regexes */
9817 buffercmd_init();
9819 /* set default string settings */
9820 home = g_strdup("https://www.cyphertite.com");
9821 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9822 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9823 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9824 cmd_font_name = g_strdup("monospace normal 9");
9825 oops_font_name = g_strdup("monospace normal 9");
9826 statusbar_font_name = g_strdup("monospace normal 9");
9827 tabbar_font_name = g_strdup("monospace normal 9");
9828 statusbar_elems = g_strdup("BP");
9830 /* read config file */
9831 if (strlen(conf) == 0)
9832 snprintf(conf, sizeof conf, "%s/.%s",
9833 pwd->pw_dir, XT_CONF_FILE);
9834 config_parse(conf, 0);
9836 /* init fonts */
9837 cmd_font = pango_font_description_from_string(cmd_font_name);
9838 oops_font = pango_font_description_from_string(oops_font_name);
9839 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9840 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9842 /* working directory */
9843 if (strlen(work_dir) == 0)
9844 snprintf(work_dir, sizeof work_dir, "%s/%s",
9845 pwd->pw_dir, XT_DIR);
9846 xxx_dir(work_dir);
9848 /* icon cache dir */
9849 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9850 xxx_dir(cache_dir);
9852 /* certs dir */
9853 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9854 xxx_dir(certs_dir);
9856 /* sessions dir */
9857 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9858 work_dir, XT_SESSIONS_DIR);
9859 xxx_dir(sessions_dir);
9861 /* runtime settings that can override config file */
9862 if (runtime_settings[0] != '\0')
9863 config_parse(runtime_settings, 1);
9865 /* download dir */
9866 if (!strcmp(download_dir, pwd->pw_dir))
9867 strlcat(download_dir, "/downloads", sizeof download_dir);
9868 xxx_dir(download_dir);
9870 /* favorites file */
9871 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9872 if (stat(file, &sb)) {
9873 warnx("favorites file doesn't exist, creating it");
9874 if ((f = fopen(file, "w")) == NULL)
9875 err(1, "favorites");
9876 fclose(f);
9879 /* quickmarks file */
9880 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9881 if (stat(file, &sb)) {
9882 warnx("quickmarks file doesn't exist, creating it");
9883 if ((f = fopen(file, "w")) == NULL)
9884 err(1, "quickmarks");
9885 fclose(f);
9888 /* cookies */
9889 session = webkit_get_default_session();
9890 setup_cookies();
9892 /* certs */
9893 if (ssl_ca_file) {
9894 if (stat(ssl_ca_file, &sb)) {
9895 warnx("no CA file: %s", ssl_ca_file);
9896 g_free(ssl_ca_file);
9897 ssl_ca_file = NULL;
9898 } else
9899 g_object_set(session,
9900 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9901 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9902 (void *)NULL);
9905 /* proxy */
9906 env_proxy = getenv("http_proxy");
9907 if (env_proxy)
9908 setup_proxy(env_proxy);
9909 else
9910 setup_proxy(http_proxy);
9912 if (opte) {
9913 send_cmd_to_socket(argv[0]);
9914 exit(0);
9917 /* set some connection parameters */
9918 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9919 g_object_set(session, "max-conns-per-host", max_host_connections,
9920 (char *)NULL);
9922 /* see if there is already an xxxterm running */
9923 if (single_instance && is_running()) {
9924 optn = 1;
9925 warnx("already running");
9928 if (optn) {
9929 while (argc) {
9930 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9931 send_cmd_to_socket(cmd);
9932 if (cmd)
9933 g_free(cmd);
9935 argc--;
9936 argv++;
9938 exit(0);
9941 /* uri completion */
9942 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9944 /* buffers */
9945 buffers_store = gtk_list_store_new
9946 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9948 qmarks_load();
9950 /* go graphical */
9951 create_canvas();
9952 notebook_tab_set_visibility();
9954 if (save_global_history)
9955 restore_global_history();
9957 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9958 restore_saved_tabs();
9959 else {
9960 a.s = named_session;
9961 a.i = XT_SES_DONOTHING;
9962 open_tabs(NULL, &a);
9965 /* see if we have an exception */
9966 if (!TAILQ_EMPTY(&spl)) {
9967 create_new_tab("about:startpage", NULL, focus, -1);
9968 focus = 0;
9971 while (argc) {
9972 create_new_tab(argv[0], NULL, focus, -1);
9973 focus = 0;
9975 argc--;
9976 argv++;
9979 if (TAILQ_EMPTY(&tabs))
9980 create_new_tab(home, NULL, 1, -1);
9982 if (enable_socket)
9983 if ((s = build_socket()) != -1) {
9984 channel = g_io_channel_unix_new(s);
9985 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9988 gtk_main();
9990 if (!g_thread_supported()) {
9991 gdk_threads_leave();
9994 gnutls_global_deinit();
9996 return (0);