Add -lgcrypt
[xxxterm/127001.git] / xxxterm.c
blob26325bc77092465b13b3b37e99c451cdda928816
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 <pwd.h>
35 #include <regex.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <dirent.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 #include <bsd/stdlib.h>
49 # if !defined(sane_libbsd_headers)
50 void arc4random_buf(void *, size_t);
51 # endif
52 #elif defined(__FreeBSD__)
53 #include <libutil.h>
54 #include "freebsd/util.h"
55 #include <sys/tree.h>
56 #else /* OpenBSD */
57 #include <util.h>
58 #include <sys/tree.h>
59 #endif
60 #include <sys/queue.h>
61 #include <sys/resource.h>
62 #include <sys/socket.h>
63 #include <sys/stat.h>
64 #include <sys/time.h>
65 #include <sys/un.h>
67 #include <gtk/gtk.h>
68 #include <gdk/gdkkeysyms.h>
70 #if GTK_CHECK_VERSION(3,0,0)
71 /* we still use GDK_* instead of GDK_KEY_* */
72 #include <gdk/gdkkeysyms-compat.h>
73 #endif
75 #include <webkit/webkit.h>
76 #include <libsoup/soup.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/gnutls.h>
79 #include <gnutls/x509.h>
81 #include "version.h"
82 #include "javascript.h"
84 /* comment if you don't want to use threads */
85 #define USE_THREADS
87 #ifdef USE_THREADS
88 #include <gcrypt.h>
89 #include <pthread.h>
90 GCRY_THREAD_OPTION_PTHREAD_IMPL;
91 #endif
95 javascript.h borrowed from vimprobable2 under the following license:
97 Copyright (c) 2009 Leon Winter
98 Copyright (c) 2009 Hannes Schueller
99 Copyright (c) 2009 Matto Fransen
101 Permission is hereby granted, free of charge, to any person obtaining a copy
102 of this software and associated documentation files (the "Software"), to deal
103 in the Software without restriction, including without limitation the rights
104 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
105 copies of the Software, and to permit persons to whom the Software is
106 furnished to do so, subject to the following conditions:
108 The above copyright notice and this permission notice shall be included in
109 all copies or substantial portions of the Software.
111 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
112 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
113 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
117 THE SOFTWARE.
120 static char *version = XXXTERM_VERSION;
122 /* hooked functions */
123 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
124 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
125 SoupCookie *);
127 /*#define XT_DEBUG*/
128 #ifdef XT_DEBUG
129 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
130 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
131 #define XT_D_MOVE 0x0001
132 #define XT_D_KEY 0x0002
133 #define XT_D_TAB 0x0004
134 #define XT_D_URL 0x0008
135 #define XT_D_CMD 0x0010
136 #define XT_D_NAV 0x0020
137 #define XT_D_DOWNLOAD 0x0040
138 #define XT_D_CONFIG 0x0080
139 #define XT_D_JS 0x0100
140 #define XT_D_FAVORITE 0x0200
141 #define XT_D_PRINTING 0x0400
142 #define XT_D_COOKIE 0x0800
143 #define XT_D_KEYBINDING 0x1000
144 #define XT_D_CLIP 0x2000
145 #define XT_D_BUFFERCMD 0x4000
146 u_int32_t swm_debug = 0
147 | XT_D_MOVE
148 | XT_D_KEY
149 | XT_D_TAB
150 | XT_D_URL
151 | XT_D_CMD
152 | XT_D_NAV
153 | XT_D_DOWNLOAD
154 | XT_D_CONFIG
155 | XT_D_JS
156 | XT_D_FAVORITE
157 | XT_D_PRINTING
158 | XT_D_COOKIE
159 | XT_D_KEYBINDING
160 | XT_D_CLIP
161 | XT_D_BUFFERCMD
163 #else
164 #define DPRINTF(x...)
165 #define DNPRINTF(n,x...)
166 #endif
168 #define LENGTH(x) (sizeof x / sizeof x[0])
169 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
170 ~(GDK_BUTTON1_MASK) & \
171 ~(GDK_BUTTON2_MASK) & \
172 ~(GDK_BUTTON3_MASK) & \
173 ~(GDK_BUTTON4_MASK) & \
174 ~(GDK_BUTTON5_MASK))
176 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
178 char *icons[] = {
179 "xxxtermicon16.png",
180 "xxxtermicon32.png",
181 "xxxtermicon48.png",
182 "xxxtermicon64.png",
183 "xxxtermicon128.png"
186 struct tab {
187 TAILQ_ENTRY(tab) entry;
188 GtkWidget *vbox;
189 GtkWidget *tab_content;
190 struct {
191 GtkWidget *label;
192 GtkWidget *eventbox;
193 GtkWidget *box;
194 GtkWidget *sep;
195 } tab_elems;
196 GtkWidget *label;
197 GtkWidget *spinner;
198 GtkWidget *uri_entry;
199 GtkWidget *search_entry;
200 GtkWidget *toolbar;
201 GtkWidget *browser_win;
202 GtkWidget *statusbar_box;
203 struct {
204 GtkWidget *statusbar;
205 GtkWidget *buffercmd;
206 GtkWidget *zoom;
207 GtkWidget *position;
208 } sbe;
209 GtkWidget *cmd;
210 GtkWidget *buffers;
211 GtkWidget *oops;
212 GtkWidget *backward;
213 GtkWidget *forward;
214 GtkWidget *stop;
215 GtkWidget *gohome;
216 GtkWidget *js_toggle;
217 GtkEntryCompletion *completion;
218 guint tab_id;
219 WebKitWebView *wv;
221 WebKitWebHistoryItem *item;
222 WebKitWebBackForwardList *bfl;
224 /* favicon */
225 WebKitNetworkRequest *icon_request;
226 WebKitDownload *icon_download;
227 gchar *icon_dest_uri;
229 /* adjustments for browser */
230 GtkScrollbar *sb_h;
231 GtkScrollbar *sb_v;
232 GtkAdjustment *adjust_h;
233 GtkAdjustment *adjust_v;
235 /* flags */
236 int focus_wv;
237 int ctrl_click;
238 gchar *status;
239 int xtp_meaning; /* identifies dls/favorites */
240 gchar *tmp_uri;
241 int popup; /* 1 if cmd_entry has popup visible */
242 #ifdef USE_THREADS
243 /* https thread stuff */
244 GThread *thread;
245 #endif
246 /* hints */
247 int hints_on;
248 int hint_mode;
249 #define XT_HINT_NONE (0)
250 #define XT_HINT_NUMERICAL (1)
251 #define XT_HINT_ALPHANUM (2)
252 char hint_buf[128];
253 char hint_num[128];
255 /* custom stylesheet */
256 int styled;
257 char *stylesheet;
259 /* search */
260 char *search_text;
261 int search_forward;
262 guint search_id;
264 /* settings */
265 WebKitWebSettings *settings;
266 gchar *user_agent;
268 /* marks */
269 double mark[XT_NOMARKS];
271 TAILQ_HEAD(tab_list, tab);
273 struct history {
274 RB_ENTRY(history) entry;
275 const gchar *uri;
276 const gchar *title;
278 RB_HEAD(history_list, history);
280 struct session {
281 TAILQ_ENTRY(session) entry;
282 const gchar *name;
284 TAILQ_HEAD(session_list, session);
286 struct download {
287 RB_ENTRY(download) entry;
288 int id;
289 WebKitDownload *download;
290 struct tab *tab;
292 RB_HEAD(download_list, download);
294 struct domain {
295 RB_ENTRY(domain) entry;
296 gchar *d;
297 int handy; /* app use */
299 RB_HEAD(domain_list, domain);
301 struct undo {
302 TAILQ_ENTRY(undo) entry;
303 gchar *uri;
304 GList *history;
305 int back; /* Keeps track of how many back
306 * history items there are. */
308 TAILQ_HEAD(undo_tailq, undo);
310 struct sp {
311 char *line;
312 TAILQ_ENTRY(sp) entry;
314 TAILQ_HEAD(sp_list, sp);
316 struct command_entry {
317 char *line;
318 TAILQ_ENTRY(command_entry) entry;
320 TAILQ_HEAD(command_list, command_entry);
322 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
323 int next_download_id = 1;
325 struct karg {
326 int i;
327 char *s;
328 int precount;
331 /* defines */
332 #define XT_NAME ("XXXTerm")
333 #define XT_DIR (".xxxterm")
334 #define XT_CACHE_DIR ("cache")
335 #define XT_CERT_DIR ("certs/")
336 #define XT_SESSIONS_DIR ("sessions/")
337 #define XT_CONF_FILE ("xxxterm.conf")
338 #define XT_FAVS_FILE ("favorites")
339 #define XT_QMARKS_FILE ("quickmarks")
340 #define XT_SAVED_TABS_FILE ("main_session")
341 #define XT_RESTART_TABS_FILE ("restart_tabs")
342 #define XT_SOCKET_FILE ("socket")
343 #define XT_HISTORY_FILE ("history")
344 #define XT_REJECT_FILE ("rejected.txt")
345 #define XT_COOKIE_FILE ("cookies.txt")
346 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
347 #define XT_SEARCH_FILE ("search_history")
348 #define XT_COMMAND_FILE ("command_history")
349 #define XT_CB_HANDLED (TRUE)
350 #define XT_CB_PASSTHROUGH (FALSE)
351 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
352 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
353 #define XT_DLMAN_REFRESH "10"
354 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
355 "td{overflow: hidden;" \
356 " padding: 2px 2px 2px 2px;" \
357 " border: 1px solid black;" \
358 " vertical-align:top;" \
359 " word-wrap: break-word}\n" \
360 "tr:hover{background: #ffff99}\n" \
361 "th{background-color: #cccccc;" \
362 " border: 1px solid black}\n" \
363 "table{width: 100%%;" \
364 " border: 1px black solid;" \
365 " border-collapse:collapse}\n" \
366 ".progress-outer{" \
367 "border: 1px solid black;" \
368 " height: 8px;" \
369 " width: 90%%}\n" \
370 ".progress-inner{float: left;" \
371 " height: 8px;" \
372 " background: green}\n" \
373 ".dlstatus{font-size: small;" \
374 " text-align: center}\n" \
375 "</style>\n"
376 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
377 #define XT_MAX_UNDO_CLOSE_TAB (32)
378 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
379 #define XT_PRINT_EXTRA_MARGIN 10
380 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
381 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
383 /* colors */
384 #define XT_COLOR_RED "#cc0000"
385 #define XT_COLOR_YELLOW "#ffff66"
386 #define XT_COLOR_BLUE "lightblue"
387 #define XT_COLOR_GREEN "#99ff66"
388 #define XT_COLOR_WHITE "white"
389 #define XT_COLOR_BLACK "black"
391 #define XT_COLOR_CT_BACKGROUND "#000000"
392 #define XT_COLOR_CT_INACTIVE "#dddddd"
393 #define XT_COLOR_CT_ACTIVE "#bbbb00"
394 #define XT_COLOR_CT_SEPARATOR "#555555"
396 #define XT_COLOR_SB_SEPARATOR "#555555"
398 #define XT_PROTO_DELIM "://"
401 * xxxterm "protocol" (xtp)
402 * We use this for managing stuff like downloads and favorites. They
403 * make magical HTML pages in memory which have xxxt:// links in order
404 * to communicate with xxxterm's internals. These links take the format:
405 * xxxt://class/session_key/action/arg
407 * Don't begin xtp class/actions as 0. atoi returns that on error.
409 * Typically we have not put addition of items in this framework, as
410 * adding items is either done via an ex-command or via a keybinding instead.
413 #define XT_XTP_STR "xxxt://"
415 /* XTP classes (xxxt://<class>) */
416 #define XT_XTP_INVALID 0 /* invalid */
417 #define XT_XTP_DL 1 /* downloads */
418 #define XT_XTP_HL 2 /* history */
419 #define XT_XTP_CL 3 /* cookies */
420 #define XT_XTP_FL 4 /* favorites */
422 /* XTP download actions */
423 #define XT_XTP_DL_LIST 1
424 #define XT_XTP_DL_CANCEL 2
425 #define XT_XTP_DL_REMOVE 3
427 /* XTP history actions */
428 #define XT_XTP_HL_LIST 1
429 #define XT_XTP_HL_REMOVE 2
431 /* XTP cookie actions */
432 #define XT_XTP_CL_LIST 1
433 #define XT_XTP_CL_REMOVE 2
435 /* XTP cookie actions */
436 #define XT_XTP_FL_LIST 1
437 #define XT_XTP_FL_REMOVE 2
439 /* actions */
440 #define XT_MOVE_INVALID (0)
441 #define XT_MOVE_DOWN (1)
442 #define XT_MOVE_UP (2)
443 #define XT_MOVE_BOTTOM (3)
444 #define XT_MOVE_TOP (4)
445 #define XT_MOVE_PAGEDOWN (5)
446 #define XT_MOVE_PAGEUP (6)
447 #define XT_MOVE_HALFDOWN (7)
448 #define XT_MOVE_HALFUP (8)
449 #define XT_MOVE_LEFT (9)
450 #define XT_MOVE_FARLEFT (10)
451 #define XT_MOVE_RIGHT (11)
452 #define XT_MOVE_FARRIGHT (12)
453 #define XT_MOVE_PERCENT (13)
455 #define XT_QMARK_SET (0)
456 #define XT_QMARK_OPEN (1)
457 #define XT_QMARK_TAB (2)
459 #define XT_MARK_SET (0)
460 #define XT_MARK_GOTO (1)
462 #define XT_TAB_LAST (-4)
463 #define XT_TAB_FIRST (-3)
464 #define XT_TAB_PREV (-2)
465 #define XT_TAB_NEXT (-1)
466 #define XT_TAB_INVALID (0)
467 #define XT_TAB_NEW (1)
468 #define XT_TAB_DELETE (2)
469 #define XT_TAB_DELQUIT (3)
470 #define XT_TAB_OPEN (4)
471 #define XT_TAB_UNDO_CLOSE (5)
472 #define XT_TAB_SHOW (6)
473 #define XT_TAB_HIDE (7)
474 #define XT_TAB_NEXTSTYLE (8)
476 #define XT_NAV_INVALID (0)
477 #define XT_NAV_BACK (1)
478 #define XT_NAV_FORWARD (2)
479 #define XT_NAV_RELOAD (3)
481 #define XT_FOCUS_INVALID (0)
482 #define XT_FOCUS_URI (1)
483 #define XT_FOCUS_SEARCH (2)
485 #define XT_SEARCH_INVALID (0)
486 #define XT_SEARCH_NEXT (1)
487 #define XT_SEARCH_PREV (2)
489 #define XT_PASTE_CURRENT_TAB (0)
490 #define XT_PASTE_NEW_TAB (1)
492 #define XT_ZOOM_IN (-1)
493 #define XT_ZOOM_OUT (-2)
494 #define XT_ZOOM_NORMAL (100)
496 #define XT_URL_SHOW (1)
497 #define XT_URL_HIDE (2)
499 #define XT_WL_TOGGLE (1<<0)
500 #define XT_WL_ENABLE (1<<1)
501 #define XT_WL_DISABLE (1<<2)
502 #define XT_WL_FQDN (1<<3) /* default */
503 #define XT_WL_TOPLEVEL (1<<4)
504 #define XT_WL_PERSISTENT (1<<5)
505 #define XT_WL_SESSION (1<<6)
506 #define XT_WL_RELOAD (1<<7)
508 #define XT_SHOW (1<<7)
509 #define XT_DELETE (1<<8)
510 #define XT_SAVE (1<<9)
511 #define XT_OPEN (1<<10)
513 #define XT_CMD_OPEN (0)
514 #define XT_CMD_OPEN_CURRENT (1)
515 #define XT_CMD_TABNEW (2)
516 #define XT_CMD_TABNEW_CURRENT (3)
518 #define XT_STATUS_NOTHING (0)
519 #define XT_STATUS_LINK (1)
520 #define XT_STATUS_URI (2)
521 #define XT_STATUS_LOADING (3)
523 #define XT_SES_DONOTHING (0)
524 #define XT_SES_CLOSETABS (1)
526 #define XT_BM_NORMAL (0)
527 #define XT_BM_WHITELIST (1)
528 #define XT_BM_KIOSK (2)
530 #define XT_PREFIX (1<<0)
531 #define XT_USERARG (1<<1)
532 #define XT_URLARG (1<<2)
533 #define XT_INTARG (1<<3)
534 #define XT_SESSARG (1<<4)
535 #define XT_SETARG (1<<5)
537 #define XT_TABS_NORMAL 0
538 #define XT_TABS_COMPACT 1
540 #define XT_BUFCMD_SZ (8)
542 /* mime types */
543 struct mime_type {
544 char *mt_type;
545 char *mt_action;
546 int mt_default;
547 int mt_download;
548 TAILQ_ENTRY(mime_type) entry;
550 TAILQ_HEAD(mime_type_list, mime_type);
552 /* uri aliases */
553 struct alias {
554 char *a_name;
555 char *a_uri;
556 TAILQ_ENTRY(alias) entry;
558 TAILQ_HEAD(alias_list, alias);
560 /* settings that require restart */
561 int tabless = 0; /* allow only 1 tab */
562 int enable_socket = 0;
563 int single_instance = 0; /* only allow one xxxterm to run */
564 int fancy_bar = 1; /* fancy toolbar */
565 int browser_mode = XT_BM_NORMAL;
566 int enable_localstorage = 1;
567 char *statusbar_elems = NULL;
569 /* runtime settings */
570 int show_tabs = 1; /* show tabs on notebook */
571 int tab_style = XT_TABS_NORMAL; /* tab bar style */
572 int show_url = 1; /* show url toolbar on notebook */
573 int show_statusbar = 0; /* vimperator style status bar */
574 int ctrl_click_focus = 0; /* ctrl click gets focus */
575 int cookies_enabled = 1; /* enable cookies */
576 int read_only_cookies = 0; /* enable to not write cookies */
577 int enable_scripts = 1;
578 int enable_plugins = 0;
579 gfloat default_zoom_level = 1.0;
580 char default_script[PATH_MAX];
581 int window_height = 768;
582 int window_width = 1024;
583 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
584 int refresh_interval = 10; /* download refresh interval */
585 int enable_cookie_whitelist = 0;
586 int enable_js_whitelist = 0;
587 int session_timeout = 3600; /* cookie session timeout */
588 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
589 char *ssl_ca_file = NULL;
590 char *resource_dir = NULL;
591 gboolean ssl_strict_certs = FALSE;
592 int append_next = 1; /* append tab after current tab */
593 char *home = NULL;
594 char *search_string = NULL;
595 char *http_proxy = NULL;
596 char download_dir[PATH_MAX];
597 char runtime_settings[PATH_MAX]; /* override of settings */
598 int allow_volatile_cookies = 0;
599 int save_global_history = 0; /* save global history to disk */
600 char *user_agent = NULL;
601 int save_rejected_cookies = 0;
602 int session_autosave = 0;
603 int guess_search = 0;
604 int dns_prefetch = FALSE;
605 gint max_connections = 25;
606 gint max_host_connections = 5;
607 gint enable_spell_checking = 0;
608 char *spell_check_languages = NULL;
609 int xterm_workaround = 0;
610 char *url_regex = NULL;
611 int history_autosave = 0;
612 char search_file[PATH_MAX];
613 char command_file[PATH_MAX];
614 char *encoding = NULL;
616 char *cmd_font_name = NULL;
617 char *oops_font_name = NULL;
618 char *statusbar_font_name = NULL;
619 char *tabbar_font_name = NULL;
620 PangoFontDescription *cmd_font;
621 PangoFontDescription *oops_font;
622 PangoFontDescription *statusbar_font;
623 PangoFontDescription *tabbar_font;
624 char *qmarks[XT_NOMARKS];
626 int btn_down; /* M1 down in any wv */
627 regex_t url_re; /* guess_search regex */
629 struct settings;
630 struct key_binding;
631 int set_browser_mode(struct settings *, char *);
632 int set_cookie_policy(struct settings *, char *);
633 int set_download_dir(struct settings *, char *);
634 int set_default_script(struct settings *, char *);
635 int set_runtime_dir(struct settings *, char *);
636 int set_tab_style(struct settings *, char *);
637 int set_work_dir(struct settings *, char *);
638 int add_alias(struct settings *, char *);
639 int add_mime_type(struct settings *, char *);
640 int add_cookie_wl(struct settings *, char *);
641 int add_js_wl(struct settings *, char *);
642 int add_kb(struct settings *, char *);
643 void button_set_stockid(GtkWidget *, char *);
644 GtkWidget * create_button(char *, char *, int);
646 char *get_browser_mode(struct settings *);
647 char *get_cookie_policy(struct settings *);
648 char *get_download_dir(struct settings *);
649 char *get_default_script(struct settings *);
650 char *get_runtime_dir(struct settings *);
651 char *get_tab_style(struct settings *);
652 char *get_work_dir(struct settings *);
653 void startpage_add(const char *, ...);
655 void walk_alias(struct settings *, void (*)(struct settings *,
656 char *, void *), void *);
657 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
658 char *, void *), void *);
659 void walk_js_wl(struct settings *, void (*)(struct settings *,
660 char *, void *), void *);
661 void walk_kb(struct settings *, void (*)(struct settings *, char *,
662 void *), void *);
663 void walk_mime_type(struct settings *, void (*)(struct settings *,
664 char *, void *), void *);
666 void recalc_tabs(void);
667 void recolor_compact_tabs(void);
668 void set_current_tab(int page_num);
669 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
670 void marks_clear(struct tab *t);
672 int set_http_proxy(char *);
674 struct special {
675 int (*set)(struct settings *, char *);
676 char *(*get)(struct settings *);
677 void (*walk)(struct settings *,
678 void (*cb)(struct settings *, char *, void *),
679 void *);
682 struct special s_browser_mode = {
683 set_browser_mode,
684 get_browser_mode,
685 NULL
688 struct special s_cookie = {
689 set_cookie_policy,
690 get_cookie_policy,
691 NULL
694 struct special s_alias = {
695 add_alias,
696 NULL,
697 walk_alias
700 struct special s_mime = {
701 add_mime_type,
702 NULL,
703 walk_mime_type
706 struct special s_js = {
707 add_js_wl,
708 NULL,
709 walk_js_wl
712 struct special s_kb = {
713 add_kb,
714 NULL,
715 walk_kb
718 struct special s_cookie_wl = {
719 add_cookie_wl,
720 NULL,
721 walk_cookie_wl
724 struct special s_default_script = {
725 set_default_script,
726 get_default_script,
727 NULL
730 struct special s_download_dir = {
731 set_download_dir,
732 get_download_dir,
733 NULL
736 struct special s_work_dir = {
737 set_work_dir,
738 get_work_dir,
739 NULL
742 struct special s_tab_style = {
743 set_tab_style,
744 get_tab_style,
745 NULL
748 struct settings {
749 char *name;
750 int type;
751 #define XT_S_INVALID (0)
752 #define XT_S_INT (1)
753 #define XT_S_STR (2)
754 #define XT_S_FLOAT (3)
755 uint32_t flags;
756 #define XT_SF_RESTART (1<<0)
757 #define XT_SF_RUNTIME (1<<1)
758 int *ival;
759 char **sval;
760 struct special *s;
761 gfloat *fval;
762 int (*activate)(char *);
763 } rs[] = {
764 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
765 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
766 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
767 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
768 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
769 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
770 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
771 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
772 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
773 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
774 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
775 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
776 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
777 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
778 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
779 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
780 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
781 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
782 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
783 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
784 { "home", XT_S_STR, 0, NULL, &home, NULL },
785 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
786 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
787 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
788 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
789 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
790 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
791 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
792 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
793 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
794 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
795 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
796 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
797 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
798 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
799 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
800 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
801 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
802 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
803 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
804 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
805 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
806 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
807 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
808 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
809 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
810 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
811 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
813 /* font settings */
814 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
815 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
816 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
817 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
819 /* runtime settings */
820 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
821 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
822 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
823 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
824 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
827 int about(struct tab *, struct karg *);
828 int blank(struct tab *, struct karg *);
829 int ca_cmd(struct tab *, struct karg *);
830 int cookie_show_wl(struct tab *, struct karg *);
831 int js_show_wl(struct tab *, struct karg *);
832 int help(struct tab *, struct karg *);
833 int set(struct tab *, struct karg *);
834 int stats(struct tab *, struct karg *);
835 int marco(struct tab *, struct karg *);
836 int startpage(struct tab *, struct karg *);
837 const char * marco_message(int *);
838 int xtp_page_cl(struct tab *, struct karg *);
839 int xtp_page_dl(struct tab *, struct karg *);
840 int xtp_page_fl(struct tab *, struct karg *);
841 int xtp_page_hl(struct tab *, struct karg *);
842 void xt_icon_from_file(struct tab *, char *);
843 const gchar *get_uri(struct tab *);
844 const gchar *get_title(struct tab *, bool);
846 #define XT_URI_ABOUT ("about:")
847 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
848 #define XT_URI_ABOUT_ABOUT ("about")
849 #define XT_URI_ABOUT_BLANK ("blank")
850 #define XT_URI_ABOUT_CERTS ("certs")
851 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
852 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
853 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
854 #define XT_URI_ABOUT_FAVORITES ("favorites")
855 #define XT_URI_ABOUT_HELP ("help")
856 #define XT_URI_ABOUT_HISTORY ("history")
857 #define XT_URI_ABOUT_JSWL ("jswl")
858 #define XT_URI_ABOUT_SET ("set")
859 #define XT_URI_ABOUT_STATS ("stats")
860 #define XT_URI_ABOUT_MARCO ("marco")
861 #define XT_URI_ABOUT_STARTPAGE ("startpage")
863 struct about_type {
864 char *name;
865 int (*func)(struct tab *, struct karg *);
866 } about_list[] = {
867 { XT_URI_ABOUT_ABOUT, about },
868 { XT_URI_ABOUT_BLANK, blank },
869 { XT_URI_ABOUT_CERTS, ca_cmd },
870 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
871 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
872 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
873 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
874 { XT_URI_ABOUT_HELP, help },
875 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
876 { XT_URI_ABOUT_JSWL, js_show_wl },
877 { XT_URI_ABOUT_SET, set },
878 { XT_URI_ABOUT_STATS, stats },
879 { XT_URI_ABOUT_MARCO, marco },
880 { XT_URI_ABOUT_STARTPAGE, startpage },
883 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
884 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
885 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
886 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
887 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
888 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
889 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
891 /* globals */
892 extern char *__progname;
893 char **start_argv;
894 struct passwd *pwd;
895 GtkWidget *main_window;
896 GtkNotebook *notebook;
897 GtkWidget *tab_bar;
898 GtkWidget *arrow, *abtn;
899 struct tab_list tabs;
900 struct history_list hl;
901 struct session_list sessions;
902 struct download_list downloads;
903 struct domain_list c_wl;
904 struct domain_list js_wl;
905 struct undo_tailq undos;
906 struct keybinding_list kbl;
907 struct sp_list spl;
908 struct command_list chl;
909 struct command_list shl;
910 struct command_entry *history_at;
911 struct command_entry *search_at;
912 int undo_count;
913 int updating_dl_tabs = 0;
914 int updating_hl_tabs = 0;
915 int updating_cl_tabs = 0;
916 int updating_fl_tabs = 0;
917 int cmd_history_count = 0;
918 int search_history_count = 0;
919 char *global_search;
920 uint64_t blocked_cookies = 0;
921 char named_session[PATH_MAX];
922 GtkListStore *completion_model;
923 GtkListStore *buffers_store;
925 void xxx_dir(char *);
926 int icon_size_map(int);
927 void completion_add(struct tab *);
928 void completion_add_uri(const gchar *);
929 void show_oops(struct tab *, const char *, ...);
931 void
932 history_delete(struct command_list *l, int *counter)
934 struct command_entry *c;
936 if (l == NULL || counter == NULL)
937 return;
939 c = TAILQ_LAST(l, command_list);
940 if (c == NULL)
941 return;
943 TAILQ_REMOVE(l, c, entry);
944 *counter -= 1;
945 g_free(c->line);
946 g_free(c);
949 void
950 history_add(struct command_list *list, char *file, char *l, int *counter)
952 struct command_entry *c;
953 FILE *f;
955 if (list == NULL || l == NULL || counter == NULL)
956 return;
958 /* don't add the same line */
959 c = TAILQ_FIRST(list);
960 if (c)
961 if (!strcmp(c->line + 1 /* skip space */, l))
962 return;
964 c = g_malloc0(sizeof *c);
965 c->line = g_strdup_printf(" %s", l);
967 *counter += 1;
968 TAILQ_INSERT_HEAD(list, c, entry);
970 if (*counter > 1000)
971 history_delete(list, counter);
973 if (history_autosave && file) {
974 f = fopen(file, "w");
975 if (f == NULL) {
976 show_oops(NULL, "couldn't write history %s", file);
977 return;
980 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
981 c->line[0] = ' ';
982 fprintf(f, "%s\n", c->line);
985 fclose(f);
990 history_read(struct command_list *list, char *file, int *counter)
992 FILE *f;
993 char *s, line[65536];
995 if (list == NULL || file == NULL)
996 return (1);
998 f = fopen(file, "r");
999 if (f == NULL) {
1000 startpage_add("couldn't open history file %s", file);
1001 return (1);
1004 for (;;) {
1005 s = fgets(line, sizeof line, f);
1006 if (s == NULL || feof(f) || ferror(f))
1007 break;
1008 if ((s = strchr(line, '\n')) == NULL) {
1009 startpage_add("invalid history file %s", file);
1010 fclose(f);
1011 return (1);
1013 *s = '\0';
1015 history_add(list, NULL, line + 1, counter);
1018 fclose(f);
1020 return (0);
1023 /* marks and quickmarks array storage.
1024 * first a-z, then A-Z, then 0-9 */
1025 char
1026 indextomark(int i)
1028 if (i < 0)
1029 return (0);
1031 if (i >= 0 && i <= 'z' - 'a')
1032 return 'a' + i;
1034 i -= 'z' - 'a' + 1;
1035 if (i >= 0 && i <= 'Z' - 'A')
1036 return 'A' + i;
1038 i -= 'Z' - 'A' + 1;
1039 if (i >= 10)
1040 return (0);
1042 return i + '0';
1046 marktoindex(char m)
1048 int ret = 0;
1050 if (m >= 'a' && m <= 'z')
1051 return ret + m - 'a';
1053 ret += 'z' - 'a' + 1;
1054 if (m >= 'A' && m <= 'Z')
1055 return ret + m - 'A';
1057 ret += 'Z' - 'A' + 1;
1058 if (m >= '0' && m <= '9')
1059 return ret + m - '0';
1061 return (-1);
1065 void
1066 sigchild(int sig)
1068 int saved_errno, status;
1069 pid_t pid;
1071 saved_errno = errno;
1073 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1074 if (pid == -1) {
1075 if (errno == EINTR)
1076 continue;
1077 if (errno != ECHILD) {
1079 clog_warn("sigchild: waitpid:");
1082 break;
1085 if (WIFEXITED(status)) {
1086 if (WEXITSTATUS(status) != 0) {
1088 clog_warnx("sigchild: child exit status: %d",
1089 WEXITSTATUS(status));
1092 } else {
1094 clog_warnx("sigchild: child is terminated abnormally");
1099 errno = saved_errno;
1103 is_g_object_setting(GObject *o, char *str)
1105 guint n_props = 0, i;
1106 GParamSpec **proplist;
1108 if (! G_IS_OBJECT(o))
1109 return (0);
1111 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1112 &n_props);
1114 for (i=0; i < n_props; i++) {
1115 if (! strcmp(proplist[i]->name, str))
1116 return (1);
1118 return (0);
1121 gchar *
1122 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1124 gchar *r;
1126 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1127 "<head>\n"
1128 "<title>%s</title>\n"
1129 "%s"
1130 "%s"
1131 "</head>\n"
1132 "<body>\n"
1133 "<h1>%s</h1>\n"
1134 "%s\n</body>\n"
1135 "</html>",
1136 title,
1137 addstyles ? XT_PAGE_STYLE : "",
1138 head,
1139 title,
1140 body);
1142 return r;
1146 * Display a web page from a HTML string in memory, rather than from a URL
1148 void
1149 load_webkit_string(struct tab *t, const char *str, gchar *title)
1151 char file[PATH_MAX];
1152 int i;
1154 /* we set this to indicate we want to manually do navaction */
1155 if (t->bfl)
1156 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1158 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1159 if (title) {
1160 /* set t->xtp_meaning */
1161 for (i = 0; i < LENGTH(about_list); i++)
1162 if (!strcmp(title, about_list[i].name)) {
1163 t->xtp_meaning = i;
1164 break;
1167 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1168 "file://");
1169 #if GTK_CHECK_VERSION(2, 20, 0)
1170 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1171 gtk_widget_hide(t->spinner);
1172 #endif
1173 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1174 xt_icon_from_file(t, file);
1178 struct tab *
1179 get_current_tab(void)
1181 struct tab *t;
1183 TAILQ_FOREACH(t, &tabs, entry) {
1184 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1185 return (t);
1188 warnx("%s: no current tab", __func__);
1190 return (NULL);
1193 void
1194 set_status(struct tab *t, gchar *s, int status)
1196 gchar *type = NULL;
1198 if (s == NULL)
1199 return;
1201 switch (status) {
1202 case XT_STATUS_LOADING:
1203 type = g_strdup_printf("Loading: %s", s);
1204 s = type;
1205 break;
1206 case XT_STATUS_LINK:
1207 type = g_strdup_printf("Link: %s", s);
1208 if (!t->status)
1209 t->status = g_strdup(gtk_entry_get_text(
1210 GTK_ENTRY(t->sbe.statusbar)));
1211 s = type;
1212 break;
1213 case XT_STATUS_URI:
1214 type = g_strdup_printf("%s", s);
1215 if (!t->status) {
1216 t->status = g_strdup(type);
1218 s = type;
1219 if (!t->status)
1220 t->status = g_strdup(s);
1221 break;
1222 case XT_STATUS_NOTHING:
1223 /* FALL THROUGH */
1224 default:
1225 break;
1227 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1228 if (type)
1229 g_free(type);
1232 void
1233 hide_cmd(struct tab *t)
1235 history_at = NULL; /* just in case */
1236 search_at = NULL; /* just in case */
1237 gtk_widget_hide(t->cmd);
1240 void
1241 show_cmd(struct tab *t)
1243 history_at = NULL;
1244 search_at = NULL;
1245 gtk_widget_hide(t->oops);
1246 gtk_widget_show(t->cmd);
1249 void
1250 hide_buffers(struct tab *t)
1252 gtk_widget_hide(t->buffers);
1253 gtk_list_store_clear(buffers_store);
1256 enum {
1257 COL_ID = 0,
1258 COL_TITLE,
1259 NUM_COLS
1263 sort_tabs_by_page_num(struct tab ***stabs)
1265 int num_tabs = 0;
1266 struct tab *t;
1268 num_tabs = gtk_notebook_get_n_pages(notebook);
1270 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1272 TAILQ_FOREACH(t, &tabs, entry)
1273 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1275 return (num_tabs);
1278 void
1279 buffers_make_list(void)
1281 int i, num_tabs;
1282 const gchar *title = NULL;
1283 GtkTreeIter iter;
1284 struct tab **stabs = NULL;
1286 num_tabs = sort_tabs_by_page_num(&stabs);
1288 for (i = 0; i < num_tabs; i++)
1289 if (stabs[i]) {
1290 gtk_list_store_append(buffers_store, &iter);
1291 title = get_title(stabs[i], FALSE);
1292 gtk_list_store_set(buffers_store, &iter,
1293 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1294 * rather than 0. */
1295 COL_TITLE, title,
1296 -1);
1299 g_free(stabs);
1302 void
1303 show_buffers(struct tab *t)
1305 buffers_make_list();
1306 gtk_widget_show(t->buffers);
1307 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1310 void
1311 toggle_buffers(struct tab *t)
1313 if (gtk_widget_get_visible(t->buffers))
1314 hide_buffers(t);
1315 else
1316 show_buffers(t);
1320 buffers(struct tab *t, struct karg *args)
1322 show_buffers(t);
1324 return (0);
1327 void
1328 hide_oops(struct tab *t)
1330 gtk_widget_hide(t->oops);
1333 void
1334 show_oops(struct tab *at, const char *fmt, ...)
1336 va_list ap;
1337 char *msg = NULL;
1338 struct tab *t = NULL;
1340 if (fmt == NULL)
1341 return;
1343 if (at == NULL) {
1344 if ((t = get_current_tab()) == NULL)
1345 return;
1346 } else
1347 t = at;
1349 va_start(ap, fmt);
1350 if (vasprintf(&msg, fmt, ap) == -1)
1351 errx(1, "show_oops failed");
1352 va_end(ap);
1354 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1355 gtk_widget_hide(t->cmd);
1356 gtk_widget_show(t->oops);
1358 if (msg)
1359 free(msg);
1362 char *
1363 get_as_string(struct settings *s)
1365 char *r = NULL;
1367 if (s == NULL)
1368 return (NULL);
1370 if (s->s) {
1371 if (s->s->get)
1372 r = s->s->get(s);
1373 else
1374 warnx("get_as_string skip %s\n", s->name);
1375 } else if (s->type == XT_S_INT)
1376 r = g_strdup_printf("%d", *s->ival);
1377 else if (s->type == XT_S_STR)
1378 r = g_strdup(*s->sval);
1379 else if (s->type == XT_S_FLOAT)
1380 r = g_strdup_printf("%f", *s->fval);
1381 else
1382 r = g_strdup_printf("INVALID TYPE");
1384 return (r);
1387 void
1388 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1390 int i;
1391 char *s;
1393 for (i = 0; i < LENGTH(rs); i++) {
1394 if (rs[i].s && rs[i].s->walk)
1395 rs[i].s->walk(&rs[i], cb, cb_args);
1396 else {
1397 s = get_as_string(&rs[i]);
1398 cb(&rs[i], s, cb_args);
1399 g_free(s);
1405 set_browser_mode(struct settings *s, char *val)
1407 if (!strcmp(val, "whitelist")) {
1408 browser_mode = XT_BM_WHITELIST;
1409 allow_volatile_cookies = 0;
1410 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1411 cookies_enabled = 1;
1412 enable_cookie_whitelist = 1;
1413 read_only_cookies = 0;
1414 save_rejected_cookies = 0;
1415 session_timeout = 3600;
1416 enable_scripts = 0;
1417 enable_js_whitelist = 1;
1418 enable_localstorage = 0;
1419 } else if (!strcmp(val, "normal")) {
1420 browser_mode = XT_BM_NORMAL;
1421 allow_volatile_cookies = 0;
1422 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1423 cookies_enabled = 1;
1424 enable_cookie_whitelist = 0;
1425 read_only_cookies = 0;
1426 save_rejected_cookies = 0;
1427 session_timeout = 3600;
1428 enable_scripts = 1;
1429 enable_js_whitelist = 0;
1430 enable_localstorage = 1;
1431 } else if (!strcmp(val, "kiosk")) {
1432 browser_mode = XT_BM_KIOSK;
1433 allow_volatile_cookies = 0;
1434 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1435 cookies_enabled = 1;
1436 enable_cookie_whitelist = 0;
1437 read_only_cookies = 0;
1438 save_rejected_cookies = 0;
1439 session_timeout = 3600;
1440 enable_scripts = 1;
1441 enable_js_whitelist = 0;
1442 enable_localstorage = 1;
1443 show_tabs = 0;
1444 tabless = 1;
1445 } else
1446 return (1);
1448 return (0);
1451 char *
1452 get_browser_mode(struct settings *s)
1454 char *r = NULL;
1456 if (browser_mode == XT_BM_WHITELIST)
1457 r = g_strdup("whitelist");
1458 else if (browser_mode == XT_BM_NORMAL)
1459 r = g_strdup("normal");
1460 else if (browser_mode == XT_BM_KIOSK)
1461 r = g_strdup("kiosk");
1462 else
1463 return (NULL);
1465 return (r);
1469 set_cookie_policy(struct settings *s, char *val)
1471 if (!strcmp(val, "no3rdparty"))
1472 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1473 else if (!strcmp(val, "accept"))
1474 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1475 else if (!strcmp(val, "reject"))
1476 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1477 else
1478 return (1);
1480 return (0);
1483 char *
1484 get_cookie_policy(struct settings *s)
1486 char *r = NULL;
1488 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1489 r = g_strdup("no3rdparty");
1490 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1491 r = g_strdup("accept");
1492 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1493 r = g_strdup("reject");
1494 else
1495 return (NULL);
1497 return (r);
1500 char *
1501 get_default_script(struct settings *s)
1503 if (default_script[0] == '\0')
1504 return (0);
1505 return (g_strdup(default_script));
1509 set_default_script(struct settings *s, char *val)
1511 if (val[0] == '~')
1512 snprintf(default_script, sizeof default_script, "%s/%s",
1513 pwd->pw_dir, &val[1]);
1514 else
1515 strlcpy(default_script, val, sizeof default_script);
1517 return (0);
1520 char *
1521 get_download_dir(struct settings *s)
1523 if (download_dir[0] == '\0')
1524 return (0);
1525 return (g_strdup(download_dir));
1529 set_download_dir(struct settings *s, char *val)
1531 if (val[0] == '~')
1532 snprintf(download_dir, sizeof download_dir, "%s/%s",
1533 pwd->pw_dir, &val[1]);
1534 else
1535 strlcpy(download_dir, val, sizeof download_dir);
1537 return (0);
1541 * Session IDs.
1542 * We use these to prevent people putting xxxt:// URLs on
1543 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1545 #define XT_XTP_SES_KEY_SZ 8
1546 #define XT_XTP_SES_KEY_HEX_FMT \
1547 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1548 char *dl_session_key; /* downloads */
1549 char *hl_session_key; /* history list */
1550 char *cl_session_key; /* cookie list */
1551 char *fl_session_key; /* favorites list */
1553 char work_dir[PATH_MAX];
1554 char certs_dir[PATH_MAX];
1555 char cache_dir[PATH_MAX];
1556 char sessions_dir[PATH_MAX];
1557 char cookie_file[PATH_MAX];
1558 SoupURI *proxy_uri = NULL;
1559 SoupSession *session;
1560 SoupCookieJar *s_cookiejar;
1561 SoupCookieJar *p_cookiejar;
1562 char rc_fname[PATH_MAX];
1564 struct mime_type_list mtl;
1565 struct alias_list aliases;
1567 /* protos */
1568 struct tab *create_new_tab(char *, struct undo *, int, int);
1569 void delete_tab(struct tab *);
1570 void setzoom_webkit(struct tab *, int);
1571 int run_script(struct tab *, char *);
1572 int download_rb_cmp(struct download *, struct download *);
1573 gboolean cmd_execute(struct tab *t, char *str);
1576 history_rb_cmp(struct history *h1, struct history *h2)
1578 return (strcmp(h1->uri, h2->uri));
1580 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1583 domain_rb_cmp(struct domain *d1, struct domain *d2)
1585 return (strcmp(d1->d, d2->d));
1587 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1589 char *
1590 get_work_dir(struct settings *s)
1592 if (work_dir[0] == '\0')
1593 return (0);
1594 return (g_strdup(work_dir));
1598 set_work_dir(struct settings *s, char *val)
1600 if (val[0] == '~')
1601 snprintf(work_dir, sizeof work_dir, "%s/%s",
1602 pwd->pw_dir, &val[1]);
1603 else
1604 strlcpy(work_dir, val, sizeof work_dir);
1606 return (0);
1609 char *
1610 get_tab_style(struct settings *s)
1612 if (tab_style == XT_TABS_NORMAL)
1613 return (g_strdup("normal"));
1614 else
1615 return (g_strdup("compact"));
1619 set_tab_style(struct settings *s, char *val)
1621 if (!strcmp(val, "normal"))
1622 tab_style = XT_TABS_NORMAL;
1623 else if (!strcmp(val, "compact"))
1624 tab_style = XT_TABS_COMPACT;
1625 else
1626 return (1);
1628 return (0);
1632 * generate a session key to secure xtp commands.
1633 * pass in a ptr to the key in question and it will
1634 * be modified in place.
1636 void
1637 generate_xtp_session_key(char **key)
1639 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1641 /* free old key */
1642 if (*key)
1643 g_free(*key);
1645 /* make a new one */
1646 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1647 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1648 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1649 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1651 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1655 * validate a xtp session key.
1656 * return (1) if OK
1659 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1661 if (strcmp(trusted, untrusted) != 0) {
1662 show_oops(t, "%s: xtp session key mismatch possible spoof",
1663 __func__);
1664 return (0);
1667 return (1);
1671 download_rb_cmp(struct download *e1, struct download *e2)
1673 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1675 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1677 struct valid_url_types {
1678 char *type;
1679 } vut[] = {
1680 { "http://" },
1681 { "https://" },
1682 { "ftp://" },
1683 { "file://" },
1684 { XT_XTP_STR },
1688 valid_url_type(char *url)
1690 int i;
1692 for (i = 0; i < LENGTH(vut); i++)
1693 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1694 return (0);
1696 return (1);
1699 void
1700 print_cookie(char *msg, SoupCookie *c)
1702 if (c == NULL)
1703 return;
1705 if (msg)
1706 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1707 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1708 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1709 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1710 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1711 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1712 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1713 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1714 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1715 DNPRINTF(XT_D_COOKIE, "====================================\n");
1718 void
1719 walk_alias(struct settings *s,
1720 void (*cb)(struct settings *, char *, void *), void *cb_args)
1722 struct alias *a;
1723 char *str;
1725 if (s == NULL || cb == NULL) {
1726 show_oops(NULL, "walk_alias invalid parameters");
1727 return;
1730 TAILQ_FOREACH(a, &aliases, entry) {
1731 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1732 cb(s, str, cb_args);
1733 g_free(str);
1737 char *
1738 match_alias(char *url_in)
1740 struct alias *a;
1741 char *arg;
1742 char *url_out = NULL, *search, *enc_arg;
1744 search = g_strdup(url_in);
1745 arg = search;
1746 if (strsep(&arg, " \t") == NULL) {
1747 show_oops(NULL, "match_alias: NULL URL");
1748 goto done;
1751 TAILQ_FOREACH(a, &aliases, entry) {
1752 if (!strcmp(search, a->a_name))
1753 break;
1756 if (a != NULL) {
1757 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1758 a->a_name);
1759 if (arg != NULL) {
1760 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1761 url_out = g_strdup_printf(a->a_uri, enc_arg);
1762 g_free(enc_arg);
1763 } else
1764 url_out = g_strdup_printf(a->a_uri, "");
1766 done:
1767 g_free(search);
1768 return (url_out);
1771 char *
1772 guess_url_type(char *url_in)
1774 struct stat sb;
1775 char *url_out = NULL, *enc_search = NULL;
1776 int i;
1778 /* substitute aliases */
1779 url_out = match_alias(url_in);
1780 if (url_out != NULL)
1781 return (url_out);
1783 /* see if we are an about page */
1784 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1785 for (i = 0; i < LENGTH(about_list); i++)
1786 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1787 about_list[i].name)) {
1788 url_out = g_strdup(url_in);
1789 goto done;
1792 if (guess_search && url_regex &&
1793 !(g_str_has_prefix(url_in, "http://") ||
1794 g_str_has_prefix(url_in, "https://"))) {
1795 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1796 /* invalid URI so search instead */
1797 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1798 url_out = g_strdup_printf(search_string, enc_search);
1799 g_free(enc_search);
1800 goto done;
1804 /* XXX not sure about this heuristic */
1805 if (stat(url_in, &sb) == 0)
1806 url_out = g_strdup_printf("file://%s", url_in);
1807 else
1808 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1809 done:
1810 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1812 return (url_out);
1815 void
1816 load_uri(struct tab *t, gchar *uri)
1818 struct karg args;
1819 gchar *newuri = NULL;
1820 int i;
1822 if (uri == NULL)
1823 return;
1825 /* Strip leading spaces. */
1826 while (*uri && isspace(*uri))
1827 uri++;
1829 if (strlen(uri) == 0) {
1830 blank(t, NULL);
1831 return;
1834 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1836 if (valid_url_type(uri)) {
1837 newuri = guess_url_type(uri);
1838 uri = newuri;
1841 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1842 for (i = 0; i < LENGTH(about_list); i++)
1843 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1844 bzero(&args, sizeof args);
1845 about_list[i].func(t, &args);
1846 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1847 FALSE);
1848 goto done;
1850 show_oops(t, "invalid about page");
1851 goto done;
1854 set_status(t, (char *)uri, XT_STATUS_LOADING);
1855 marks_clear(t);
1856 webkit_web_view_load_uri(t->wv, uri);
1857 done:
1858 if (newuri)
1859 g_free(newuri);
1862 const gchar *
1863 get_uri(struct tab *t)
1865 const gchar *uri = NULL;
1867 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1868 return NULL;
1869 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1870 uri = webkit_web_view_get_uri(t->wv);
1871 } else {
1872 /* use tmp_uri to make sure it is g_freed */
1873 if (t->tmp_uri)
1874 g_free(t->tmp_uri);
1875 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1876 about_list[t->xtp_meaning].name);
1877 uri = t->tmp_uri;
1879 return uri;
1882 const gchar *
1883 get_title(struct tab *t, bool window)
1885 const gchar *set = NULL, *title = NULL;
1886 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1888 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1889 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1890 goto notitle;
1892 title = webkit_web_view_get_title(t->wv);
1893 if ((set = title ? title : get_uri(t)))
1894 return set;
1896 notitle:
1897 set = window ? XT_NAME : "(untitled)";
1899 return set;
1903 add_alias(struct settings *s, char *line)
1905 char *l, *alias;
1906 struct alias *a = NULL;
1908 if (s == NULL || line == NULL) {
1909 show_oops(NULL, "add_alias invalid parameters");
1910 return (1);
1913 l = line;
1914 a = g_malloc(sizeof(*a));
1916 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1917 show_oops(NULL, "add_alias: incomplete alias definition");
1918 goto bad;
1920 if (strlen(alias) == 0 || strlen(l) == 0) {
1921 show_oops(NULL, "add_alias: invalid alias definition");
1922 goto bad;
1925 a->a_name = g_strdup(alias);
1926 a->a_uri = g_strdup(l);
1928 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1930 TAILQ_INSERT_TAIL(&aliases, a, entry);
1932 return (0);
1933 bad:
1934 if (a)
1935 g_free(a);
1936 return (1);
1940 add_mime_type(struct settings *s, char *line)
1942 char *mime_type;
1943 char *l;
1944 struct mime_type *m = NULL;
1945 int downloadfirst = 0;
1947 /* XXX this could be smarter */
1949 if (line == NULL || strlen(line) == 0) {
1950 show_oops(NULL, "add_mime_type invalid parameters");
1951 return (1);
1954 l = line;
1955 if (*l == '@') {
1956 downloadfirst = 1;
1957 l++;
1959 m = g_malloc(sizeof(*m));
1961 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1962 show_oops(NULL, "add_mime_type: invalid mime_type");
1963 goto bad;
1965 if (mime_type[strlen(mime_type) - 1] == '*') {
1966 mime_type[strlen(mime_type) - 1] = '\0';
1967 m->mt_default = 1;
1968 } else
1969 m->mt_default = 0;
1971 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1972 show_oops(NULL, "add_mime_type: invalid mime_type");
1973 goto bad;
1976 m->mt_type = g_strdup(mime_type);
1977 m->mt_action = g_strdup(l);
1978 m->mt_download = downloadfirst;
1980 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1981 m->mt_type, m->mt_action, m->mt_default);
1983 TAILQ_INSERT_TAIL(&mtl, m, entry);
1985 return (0);
1986 bad:
1987 if (m)
1988 g_free(m);
1989 return (1);
1992 struct mime_type *
1993 find_mime_type(char *mime_type)
1995 struct mime_type *m, *def = NULL, *rv = NULL;
1997 TAILQ_FOREACH(m, &mtl, entry) {
1998 if (m->mt_default &&
1999 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
2000 def = m;
2002 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
2003 rv = m;
2004 break;
2008 if (rv == NULL)
2009 rv = def;
2011 return (rv);
2014 void
2015 walk_mime_type(struct settings *s,
2016 void (*cb)(struct settings *, char *, void *), void *cb_args)
2018 struct mime_type *m;
2019 char *str;
2021 if (s == NULL || cb == NULL) {
2022 show_oops(NULL, "walk_mime_type invalid parameters");
2023 return;
2026 TAILQ_FOREACH(m, &mtl, entry) {
2027 str = g_strdup_printf("%s%s --> %s",
2028 m->mt_type,
2029 m->mt_default ? "*" : "",
2030 m->mt_action);
2031 cb(s, str, cb_args);
2032 g_free(str);
2036 void
2037 wl_add(char *str, struct domain_list *wl, int handy)
2039 struct domain *d;
2040 int add_dot = 0;
2041 char *p;
2043 if (str == NULL || wl == NULL || strlen(str) < 2)
2044 return;
2046 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2048 /* treat *.moo.com the same as .moo.com */
2049 if (str[0] == '*' && str[1] == '.')
2050 str = &str[1];
2051 else if (str[0] == '.')
2052 str = &str[0];
2053 else
2054 add_dot = 1;
2056 /* slice off port number */
2057 p = g_strrstr(str, ":");
2058 if (p)
2059 *p = '\0';
2061 d = g_malloc(sizeof *d);
2062 if (add_dot)
2063 d->d = g_strdup_printf(".%s", str);
2064 else
2065 d->d = g_strdup(str);
2066 d->handy = handy;
2068 if (RB_INSERT(domain_list, wl, d))
2069 goto unwind;
2071 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2072 return;
2073 unwind:
2074 if (d) {
2075 if (d->d)
2076 g_free(d->d);
2077 g_free(d);
2082 add_cookie_wl(struct settings *s, char *entry)
2084 wl_add(entry, &c_wl, 1);
2085 return (0);
2088 void
2089 walk_cookie_wl(struct settings *s,
2090 void (*cb)(struct settings *, char *, void *), void *cb_args)
2092 struct domain *d;
2094 if (s == NULL || cb == NULL) {
2095 show_oops(NULL, "walk_cookie_wl invalid parameters");
2096 return;
2099 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2100 cb(s, d->d, cb_args);
2103 void
2104 walk_js_wl(struct settings *s,
2105 void (*cb)(struct settings *, char *, void *), void *cb_args)
2107 struct domain *d;
2109 if (s == NULL || cb == NULL) {
2110 show_oops(NULL, "walk_js_wl invalid parameters");
2111 return;
2114 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2115 cb(s, d->d, cb_args);
2119 add_js_wl(struct settings *s, char *entry)
2121 wl_add(entry, &js_wl, 1 /* persistent */);
2122 return (0);
2125 struct domain *
2126 wl_find(const gchar *search, struct domain_list *wl)
2128 int i;
2129 struct domain *d = NULL, dfind;
2130 gchar *s = NULL;
2132 if (search == NULL || wl == NULL)
2133 return (NULL);
2134 if (strlen(search) < 2)
2135 return (NULL);
2137 if (search[0] != '.')
2138 s = g_strdup_printf(".%s", search);
2139 else
2140 s = g_strdup(search);
2142 for (i = strlen(s) - 1; i >= 0; i--) {
2143 if (s[i] == '.') {
2144 dfind.d = &s[i];
2145 d = RB_FIND(domain_list, wl, &dfind);
2146 if (d)
2147 goto done;
2151 done:
2152 if (s)
2153 g_free(s);
2155 return (d);
2158 struct domain *
2159 wl_find_uri(const gchar *s, struct domain_list *wl)
2161 int i;
2162 char *ss;
2163 struct domain *r;
2165 if (s == NULL || wl == NULL)
2166 return (NULL);
2168 if (!strncmp(s, "http://", strlen("http://")))
2169 s = &s[strlen("http://")];
2170 else if (!strncmp(s, "https://", strlen("https://")))
2171 s = &s[strlen("https://")];
2173 if (strlen(s) < 2)
2174 return (NULL);
2176 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2177 /* chop string at first slash */
2178 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2179 ss = g_strdup(s);
2180 ss[i] = '\0';
2181 r = wl_find(ss, wl);
2182 g_free(ss);
2183 return (r);
2186 return (NULL);
2190 settings_add(char *var, char *val)
2192 int i, rv, *p;
2193 gfloat *f;
2194 char **s;
2196 /* get settings */
2197 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2198 if (strcmp(var, rs[i].name))
2199 continue;
2201 if (rs[i].s) {
2202 if (rs[i].s->set(&rs[i], val))
2203 errx(1, "invalid value for %s: %s", var, val);
2204 rv = 1;
2205 break;
2206 } else
2207 switch (rs[i].type) {
2208 case XT_S_INT:
2209 p = rs[i].ival;
2210 *p = atoi(val);
2211 rv = 1;
2212 break;
2213 case XT_S_STR:
2214 s = rs[i].sval;
2215 if (s == NULL)
2216 errx(1, "invalid sval for %s",
2217 rs[i].name);
2218 if (*s)
2219 g_free(*s);
2220 *s = g_strdup(val);
2221 rv = 1;
2222 break;
2223 case XT_S_FLOAT:
2224 f = rs[i].fval;
2225 *f = atof(val);
2226 rv = 1;
2227 break;
2228 case XT_S_INVALID:
2229 default:
2230 errx(1, "invalid type for %s", var);
2232 break;
2234 return (rv);
2237 #define WS "\n= \t"
2238 void
2239 config_parse(char *filename, int runtime)
2241 FILE *config, *f;
2242 char *line, *cp, *var, *val;
2243 size_t len, lineno = 0;
2244 int handled;
2245 char file[PATH_MAX];
2246 struct stat sb;
2248 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2250 if (filename == NULL)
2251 return;
2253 if (runtime && runtime_settings[0] != '\0') {
2254 snprintf(file, sizeof file, "%s/%s",
2255 work_dir, runtime_settings);
2256 if (stat(file, &sb)) {
2257 warnx("runtime file doesn't exist, creating it");
2258 if ((f = fopen(file, "w")) == NULL)
2259 err(1, "runtime");
2260 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2261 fclose(f);
2263 } else
2264 strlcpy(file, filename, sizeof file);
2266 if ((config = fopen(file, "r")) == NULL) {
2267 warn("config_parse: cannot open %s", filename);
2268 return;
2271 for (;;) {
2272 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2273 if (feof(config) || ferror(config))
2274 break;
2276 cp = line;
2277 cp += (long)strspn(cp, WS);
2278 if (cp[0] == '\0') {
2279 /* empty line */
2280 free(line);
2281 continue;
2284 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2285 startpage_add("invalid configuration file entry: %s",
2286 line);
2288 cp += (long)strspn(cp, WS);
2290 if ((val = strsep(&cp, "\0")) == NULL)
2291 break;
2293 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2294 handled = settings_add(var, val);
2295 if (handled == 0)
2296 startpage_add("invalid configuration file entry: %s=%s",
2297 var, val);
2299 free(line);
2302 fclose(config);
2305 char *
2306 js_ref_to_string(JSContextRef context, JSValueRef ref)
2308 char *s = NULL;
2309 size_t l;
2310 JSStringRef jsref;
2312 jsref = JSValueToStringCopy(context, ref, NULL);
2313 if (jsref == NULL)
2314 return (NULL);
2316 l = JSStringGetMaximumUTF8CStringSize(jsref);
2317 s = g_malloc(l);
2318 if (s)
2319 JSStringGetUTF8CString(jsref, s, l);
2320 JSStringRelease(jsref);
2322 return (s);
2325 void
2326 disable_hints(struct tab *t)
2328 bzero(t->hint_buf, sizeof t->hint_buf);
2329 bzero(t->hint_num, sizeof t->hint_num);
2330 run_script(t, "vimprobable_clear()");
2331 t->hints_on = 0;
2332 t->hint_mode = XT_HINT_NONE;
2335 void
2336 enable_hints(struct tab *t)
2338 bzero(t->hint_buf, sizeof t->hint_buf);
2339 run_script(t, "vimprobable_show_hints()");
2340 t->hints_on = 1;
2341 t->hint_mode = XT_HINT_NONE;
2344 #define XT_JS_OPEN ("open;")
2345 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2346 #define XT_JS_FIRE ("fire;")
2347 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2348 #define XT_JS_FOUND ("found;")
2349 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2352 run_script(struct tab *t, char *s)
2354 JSGlobalContextRef ctx;
2355 WebKitWebFrame *frame;
2356 JSStringRef str;
2357 JSValueRef val, exception;
2358 char *es, buf[128];
2360 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2361 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2363 frame = webkit_web_view_get_main_frame(t->wv);
2364 ctx = webkit_web_frame_get_global_context(frame);
2366 str = JSStringCreateWithUTF8CString(s);
2367 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2368 NULL, 0, &exception);
2369 JSStringRelease(str);
2371 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2372 if (val == NULL) {
2373 es = js_ref_to_string(ctx, exception);
2374 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2375 g_free(es);
2376 return (1);
2377 } else {
2378 es = js_ref_to_string(ctx, val);
2379 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2381 /* handle return value right here */
2382 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2383 disable_hints(t);
2384 marks_clear(t);
2385 load_uri(t, &es[XT_JS_OPEN_LEN]);
2388 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2389 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2390 &es[XT_JS_FIRE_LEN]);
2391 run_script(t, buf);
2392 disable_hints(t);
2395 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2396 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2397 disable_hints(t);
2400 g_free(es);
2403 return (0);
2407 hint(struct tab *t, struct karg *args)
2410 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2412 if (t->hints_on == 0)
2413 enable_hints(t);
2414 else
2415 disable_hints(t);
2417 return (0);
2420 void
2421 apply_style(struct tab *t)
2423 g_object_set(G_OBJECT(t->settings),
2424 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2428 userstyle(struct tab *t, struct karg *args)
2430 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2432 if (t->styled) {
2433 t->styled = 0;
2434 g_object_set(G_OBJECT(t->settings),
2435 "user-stylesheet-uri", NULL, (char *)NULL);
2436 } else {
2437 t->styled = 1;
2438 apply_style(t);
2440 return (0);
2444 * Doesn't work fully, due to the following bug:
2445 * https://bugs.webkit.org/show_bug.cgi?id=51747
2448 restore_global_history(void)
2450 char file[PATH_MAX];
2451 FILE *f;
2452 struct history *h;
2453 gchar *uri;
2454 gchar *title;
2455 const char delim[3] = {'\\', '\\', '\0'};
2457 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2459 if ((f = fopen(file, "r")) == NULL) {
2460 warnx("%s: fopen", __func__);
2461 return (1);
2464 for (;;) {
2465 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2466 if (feof(f) || ferror(f))
2467 break;
2469 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2470 if (feof(f) || ferror(f)) {
2471 free(uri);
2472 warnx("%s: broken history file\n", __func__);
2473 return (1);
2476 if (uri && strlen(uri) && title && strlen(title)) {
2477 webkit_web_history_item_new_with_data(uri, title);
2478 h = g_malloc(sizeof(struct history));
2479 h->uri = g_strdup(uri);
2480 h->title = g_strdup(title);
2481 RB_INSERT(history_list, &hl, h);
2482 completion_add_uri(h->uri);
2483 } else {
2484 warnx("%s: failed to restore history\n", __func__);
2485 free(uri);
2486 free(title);
2487 return (1);
2490 free(uri);
2491 free(title);
2492 uri = NULL;
2493 title = NULL;
2496 return (0);
2500 save_global_history_to_disk(struct tab *t)
2502 char file[PATH_MAX];
2503 FILE *f;
2504 struct history *h;
2506 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2508 if ((f = fopen(file, "w")) == NULL) {
2509 show_oops(t, "%s: global history file: %s",
2510 __func__, strerror(errno));
2511 return (1);
2514 RB_FOREACH_REVERSE(h, history_list, &hl) {
2515 if (h->uri && h->title)
2516 fprintf(f, "%s\n%s\n", h->uri, h->title);
2519 fclose(f);
2521 return (0);
2525 quit(struct tab *t, struct karg *args)
2527 if (save_global_history)
2528 save_global_history_to_disk(t);
2530 gtk_main_quit();
2532 return (1);
2535 void
2536 restore_sessions_list(void)
2538 DIR *sdir = NULL;
2539 struct dirent *dp = NULL;
2540 struct session *s;
2542 sdir = opendir(sessions_dir);
2543 if (sdir) {
2544 while ((dp = readdir(sdir)) != NULL)
2545 if (dp->d_type == DT_REG) {
2546 s = g_malloc(sizeof(struct session));
2547 s->name = g_strdup(dp->d_name);
2548 TAILQ_INSERT_TAIL(&sessions, s, entry);
2550 closedir(sdir);
2555 open_tabs(struct tab *t, struct karg *a)
2557 char file[PATH_MAX];
2558 FILE *f = NULL;
2559 char *uri = NULL;
2560 int rv = 1;
2561 struct tab *ti, *tt;
2563 if (a == NULL)
2564 goto done;
2566 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2567 if ((f = fopen(file, "r")) == NULL)
2568 goto done;
2570 ti = TAILQ_LAST(&tabs, tab_list);
2572 for (;;) {
2573 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2574 if (feof(f) || ferror(f))
2575 break;
2577 /* retrieve session name */
2578 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2579 strlcpy(named_session,
2580 &uri[strlen(XT_SAVE_SESSION_ID)],
2581 sizeof named_session);
2582 continue;
2585 if (uri && strlen(uri))
2586 create_new_tab(uri, NULL, 1, -1);
2588 free(uri);
2589 uri = NULL;
2592 /* close open tabs */
2593 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2594 for (;;) {
2595 tt = TAILQ_FIRST(&tabs);
2596 if (tt != ti) {
2597 delete_tab(tt);
2598 continue;
2600 delete_tab(tt);
2601 break;
2603 recalc_tabs();
2606 rv = 0;
2607 done:
2608 if (f)
2609 fclose(f);
2611 return (rv);
2615 restore_saved_tabs(void)
2617 char file[PATH_MAX];
2618 int unlink_file = 0;
2619 struct stat sb;
2620 struct karg a;
2621 int rv = 0;
2623 snprintf(file, sizeof file, "%s/%s",
2624 sessions_dir, XT_RESTART_TABS_FILE);
2625 if (stat(file, &sb) == -1)
2626 a.s = XT_SAVED_TABS_FILE;
2627 else {
2628 unlink_file = 1;
2629 a.s = XT_RESTART_TABS_FILE;
2632 a.i = XT_SES_DONOTHING;
2633 rv = open_tabs(NULL, &a);
2635 if (unlink_file)
2636 unlink(file);
2638 return (rv);
2642 save_tabs(struct tab *t, struct karg *a)
2644 char file[PATH_MAX];
2645 FILE *f;
2646 int num_tabs = 0, i;
2647 struct tab **stabs = NULL;
2649 if (a == NULL)
2650 return (1);
2651 if (a->s == NULL)
2652 snprintf(file, sizeof file, "%s/%s",
2653 sessions_dir, named_session);
2654 else
2655 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2657 if ((f = fopen(file, "w")) == NULL) {
2658 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2659 return (1);
2662 /* save session name */
2663 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2665 /* Save tabs, in the order they are arranged in the notebook. */
2666 num_tabs = sort_tabs_by_page_num(&stabs);
2668 for (i = 0; i < num_tabs; i++)
2669 if (stabs[i]) {
2670 if (get_uri(stabs[i]) != NULL)
2671 fprintf(f, "%s\n", get_uri(stabs[i]));
2672 else if (gtk_entry_get_text(GTK_ENTRY(
2673 stabs[i]->uri_entry)))
2674 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2675 stabs[i]->uri_entry)));
2678 g_free(stabs);
2680 /* try and make sure this gets to disk NOW. XXX Backup first? */
2681 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2682 show_oops(t, "May not have managed to save session: %s",
2683 strerror(errno));
2686 fclose(f);
2688 return (0);
2692 save_tabs_and_quit(struct tab *t, struct karg *args)
2694 struct karg a;
2696 a.s = NULL;
2697 save_tabs(t, &a);
2698 quit(t, NULL);
2700 return (1);
2704 run_page_script(struct tab *t, struct karg *args)
2706 const gchar *uri;
2707 char *tmp, script[PATH_MAX];
2709 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2710 if (tmp[0] == '\0') {
2711 show_oops(t, "no script specified");
2712 return (1);
2715 if ((uri = get_uri(t)) == NULL) {
2716 show_oops(t, "tab is empty, not running script");
2717 return (1);
2720 if (tmp[0] == '~')
2721 snprintf(script, sizeof script, "%s/%s",
2722 pwd->pw_dir, &tmp[1]);
2723 else
2724 strlcpy(script, tmp, sizeof script);
2726 switch (fork()) {
2727 case -1:
2728 show_oops(t, "can't fork to run script");
2729 return (1);
2730 /* NOTREACHED */
2731 case 0:
2732 break;
2733 default:
2734 return (0);
2737 /* child */
2738 execlp(script, script, uri, (void *)NULL);
2740 _exit(0);
2742 /* NOTREACHED */
2744 return (0);
2748 yank_uri(struct tab *t, struct karg *args)
2750 const gchar *uri;
2751 GtkClipboard *clipboard;
2753 if ((uri = get_uri(t)) == NULL)
2754 return (1);
2756 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2757 gtk_clipboard_set_text(clipboard, uri, -1);
2759 return (0);
2763 paste_uri(struct tab *t, struct karg *args)
2765 GtkClipboard *clipboard;
2766 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2767 gint len;
2768 gchar *p = NULL, *uri;
2770 /* try primary clipboard first */
2771 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2772 p = gtk_clipboard_wait_for_text(clipboard);
2774 /* if it failed get whatever text is in cut_buffer0 */
2775 if (p == NULL && xterm_workaround)
2776 if (gdk_property_get(gdk_get_default_root_window(),
2777 atom,
2778 gdk_atom_intern("STRING", FALSE),
2780 1024 * 1024 /* picked out of my butt */,
2781 FALSE,
2782 NULL,
2783 NULL,
2784 &len,
2785 (guchar **)&p)) {
2786 /* yes sir, we need to NUL the string */
2787 p[len] = '\0';
2790 if (p) {
2791 uri = p;
2792 while (*uri && isspace(*uri))
2793 uri++;
2794 if (strlen(uri) == 0) {
2795 show_oops(t, "empty paste buffer");
2796 goto done;
2798 if (guess_search == 0 && valid_url_type(uri)) {
2799 /* we can be clever and paste this in search box */
2800 show_oops(t, "not a valid URL");
2801 goto done;
2804 if (args->i == XT_PASTE_CURRENT_TAB)
2805 load_uri(t, uri);
2806 else if (args->i == XT_PASTE_NEW_TAB)
2807 create_new_tab(uri, NULL, 1, -1);
2810 done:
2811 if (p)
2812 g_free(p);
2814 return (0);
2817 gchar *
2818 find_domain(const gchar *s, int toplevel)
2820 SoupURI *uri;
2821 gchar *ret, *p;
2823 if (s == NULL)
2824 return (NULL);
2826 uri = soup_uri_new(s);
2828 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2829 return (NULL);
2832 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2833 if ((p = strrchr(uri->host, '.')) != NULL) {
2834 while(--p >= uri->host && *p != '.');
2835 p++;
2836 } else
2837 p = uri->host;
2838 } else
2839 p = uri->host;
2841 ret = g_strdup_printf(".%s", p);
2843 soup_uri_free(uri);
2845 return ret;
2849 toggle_cwl(struct tab *t, struct karg *args)
2851 struct domain *d;
2852 const gchar *uri;
2853 char *dom = NULL;
2854 int es;
2856 if (args == NULL)
2857 return (1);
2859 uri = get_uri(t);
2860 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2862 if (uri == NULL || dom == NULL ||
2863 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2864 show_oops(t, "Can't toggle domain in cookie white list");
2865 goto done;
2867 d = wl_find(dom, &c_wl);
2869 if (d == NULL)
2870 es = 0;
2871 else
2872 es = 1;
2874 if (args->i & XT_WL_TOGGLE)
2875 es = !es;
2876 else if ((args->i & XT_WL_ENABLE) && es != 1)
2877 es = 1;
2878 else if ((args->i & XT_WL_DISABLE) && es != 0)
2879 es = 0;
2881 if (es)
2882 /* enable cookies for domain */
2883 wl_add(dom, &c_wl, 0);
2884 else
2885 /* disable cookies for domain */
2886 RB_REMOVE(domain_list, &c_wl, d);
2888 if (args->i & XT_WL_RELOAD)
2889 webkit_web_view_reload(t->wv);
2891 done:
2892 g_free(dom);
2893 return (0);
2897 toggle_js(struct tab *t, struct karg *args)
2899 int es;
2900 const gchar *uri;
2901 struct domain *d;
2902 char *dom = NULL;
2904 if (args == NULL)
2905 return (1);
2907 g_object_get(G_OBJECT(t->settings),
2908 "enable-scripts", &es, (char *)NULL);
2909 if (args->i & XT_WL_TOGGLE)
2910 es = !es;
2911 else if ((args->i & XT_WL_ENABLE) && es != 1)
2912 es = 1;
2913 else if ((args->i & XT_WL_DISABLE) && es != 0)
2914 es = 0;
2915 else
2916 return (1);
2918 uri = get_uri(t);
2919 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2921 if (uri == NULL || dom == NULL ||
2922 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2923 show_oops(t, "Can't toggle domain in JavaScript white list");
2924 goto done;
2927 if (es) {
2928 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2929 wl_add(dom, &js_wl, 0 /* session */);
2930 } else {
2931 d = wl_find(dom, &js_wl);
2932 if (d)
2933 RB_REMOVE(domain_list, &js_wl, d);
2934 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2936 g_object_set(G_OBJECT(t->settings),
2937 "enable-scripts", es, (char *)NULL);
2938 g_object_set(G_OBJECT(t->settings),
2939 "javascript-can-open-windows-automatically", es, (char *)NULL);
2940 webkit_web_view_set_settings(t->wv, t->settings);
2942 if (args->i & XT_WL_RELOAD)
2943 webkit_web_view_reload(t->wv);
2944 done:
2945 if (dom)
2946 g_free(dom);
2947 return (0);
2950 void
2951 js_toggle_cb(GtkWidget *w, struct tab *t)
2953 struct karg a;
2955 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2956 toggle_cwl(t, &a);
2958 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2959 toggle_js(t, &a);
2963 toggle_src(struct tab *t, struct karg *args)
2965 gboolean mode;
2967 if (t == NULL)
2968 return (0);
2970 mode = webkit_web_view_get_view_source_mode(t->wv);
2971 webkit_web_view_set_view_source_mode(t->wv, !mode);
2972 webkit_web_view_reload(t->wv);
2974 return (0);
2977 void
2978 focus_webview(struct tab *t)
2980 if (t == NULL)
2981 return;
2983 /* only grab focus if we are visible */
2984 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2985 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2989 focus(struct tab *t, struct karg *args)
2991 if (t == NULL || args == NULL)
2992 return (1);
2994 if (show_url == 0)
2995 return (0);
2997 if (args->i == XT_FOCUS_URI)
2998 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2999 else if (args->i == XT_FOCUS_SEARCH)
3000 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
3002 return (0);
3006 stats(struct tab *t, struct karg *args)
3008 char *page, *body, *s, line[64 * 1024];
3009 uint64_t line_count = 0;
3010 FILE *r_cookie_f;
3012 if (t == NULL)
3013 show_oops(NULL, "stats invalid parameters");
3015 line[0] = '\0';
3016 if (save_rejected_cookies) {
3017 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3018 for (;;) {
3019 s = fgets(line, sizeof line, r_cookie_f);
3020 if (s == NULL || feof(r_cookie_f) ||
3021 ferror(r_cookie_f))
3022 break;
3023 line_count++;
3025 fclose(r_cookie_f);
3026 snprintf(line, sizeof line,
3027 "<br/>Cookies blocked(*) total: %llu", line_count);
3028 } else
3029 show_oops(t, "Can't open blocked cookies file: %s",
3030 strerror(errno));
3033 body = g_strdup_printf(
3034 "Cookies blocked(*) this session: %llu"
3035 "%s"
3036 "<p><small><b>*</b> results vary based on settings</small></p>",
3037 blocked_cookies,
3038 line);
3040 page = get_html_page("Statistics", body, "", 0);
3041 g_free(body);
3043 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3044 g_free(page);
3046 return (0);
3050 marco(struct tab *t, struct karg *args)
3052 char *page, line[64 * 1024];
3053 int len;
3055 if (t == NULL)
3056 show_oops(NULL, "marco invalid parameters");
3058 line[0] = '\0';
3059 snprintf(line, sizeof line, "%s", marco_message(&len));
3061 page = get_html_page("Marco Sez...", line, "", 0);
3063 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3064 g_free(page);
3066 return (0);
3070 blank(struct tab *t, struct karg *args)
3072 if (t == NULL)
3073 show_oops(NULL, "blank invalid parameters");
3075 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3077 return (0);
3081 about(struct tab *t, struct karg *args)
3083 char *page, *body;
3085 if (t == NULL)
3086 show_oops(NULL, "about invalid parameters");
3088 body = g_strdup_printf("<b>Version: %s</b>"
3089 #ifdef XXXTERM_BUILDSTR
3090 "<br><b>Build: %s</b>"
3091 #endif
3092 "<p>"
3093 "Authors:"
3094 "<ul>"
3095 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3096 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3097 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3098 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3099 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3100 "</ul>"
3101 "Copyrights and licenses can be found on the XXXTerm "
3102 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3103 "</p>",
3104 #ifdef XXXTERM_BUILDSTR
3105 version, XXXTERM_BUILDSTR
3106 #else
3107 version
3108 #endif
3111 page = get_html_page("About", body, "", 0);
3112 g_free(body);
3114 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3115 g_free(page);
3117 return (0);
3121 help(struct tab *t, struct karg *args)
3123 char *page, *head, *body;
3125 if (t == NULL)
3126 show_oops(NULL, "help invalid parameters");
3128 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3129 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3130 "</head>\n";
3131 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3132 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3133 "cgi-bin/man-cgi?xxxterm</a>";
3135 page = get_html_page(XT_NAME, body, head, FALSE);
3137 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3138 g_free(page);
3140 return (0);
3144 startpage(struct tab *t, struct karg *args)
3146 char *page, *body, *b;
3147 struct sp *s;
3149 if (t == NULL)
3150 show_oops(NULL, "startpage invalid parameters");
3152 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3154 TAILQ_FOREACH(s, &spl, entry) {
3155 b = body;
3156 body = g_strdup_printf("%s%s<br>", body, s->line);
3157 g_free(b);
3160 page = get_html_page("Startup Exception", body, "", 0);
3161 g_free(body);
3163 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3164 g_free(page);
3166 return (0);
3169 void
3170 startpage_add(const char *fmt, ...)
3172 va_list ap;
3173 char *msg;
3174 struct sp *s;
3176 if (fmt == NULL)
3177 return;
3179 va_start(ap, fmt);
3180 if (vasprintf(&msg, fmt, ap) == -1)
3181 errx(1, "startpage_add failed");
3182 va_end(ap);
3184 s = g_malloc0(sizeof *s);
3185 s->line = msg;
3187 TAILQ_INSERT_TAIL(&spl, s, entry);
3191 * update all favorite tabs apart from one. Pass NULL if
3192 * you want to update all.
3194 void
3195 update_favorite_tabs(struct tab *apart_from)
3197 struct tab *t;
3198 if (!updating_fl_tabs) {
3199 updating_fl_tabs = 1; /* stop infinite recursion */
3200 TAILQ_FOREACH(t, &tabs, entry)
3201 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3202 && (t != apart_from))
3203 xtp_page_fl(t, NULL);
3204 updating_fl_tabs = 0;
3208 /* show a list of favorites (bookmarks) */
3210 xtp_page_fl(struct tab *t, struct karg *args)
3212 char file[PATH_MAX];
3213 FILE *f;
3214 char *uri = NULL, *title = NULL;
3215 size_t len, lineno = 0;
3216 int i, failed = 0;
3217 char *body, *tmp, *page = NULL;
3218 const char delim[3] = {'\\', '\\', '\0'};
3220 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3222 if (t == NULL)
3223 warn("%s: bad param", __func__);
3225 /* new session key */
3226 if (!updating_fl_tabs)
3227 generate_xtp_session_key(&fl_session_key);
3229 /* open favorites */
3230 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3231 if ((f = fopen(file, "r")) == NULL) {
3232 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3233 return (1);
3236 /* body */
3237 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3238 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3239 "<th style='width: 40px'>Rm</th></tr>\n");
3241 for (i = 1;;) {
3242 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3243 break;
3244 if (strlen(title) == 0 || title[0] == '#') {
3245 free(title);
3246 title = NULL;
3247 continue;
3250 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3251 if (feof(f) || ferror(f)) {
3252 show_oops(t, "favorites file corrupt");
3253 failed = 1;
3254 break;
3257 tmp = body;
3258 body = g_strdup_printf("%s<tr>"
3259 "<td>%d</td>"
3260 "<td><a href='%s'>%s</a></td>"
3261 "<td style='text-align: center'>"
3262 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3263 "</tr>\n",
3264 body, i, uri, title,
3265 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3267 g_free(tmp);
3269 free(uri);
3270 uri = NULL;
3271 free(title);
3272 title = NULL;
3273 i++;
3275 fclose(f);
3277 /* if none, say so */
3278 if (i == 1) {
3279 tmp = body;
3280 body = g_strdup_printf("%s<tr>"
3281 "<td colspan='3' style='text-align: center'>"
3282 "No favorites - To add one use the 'favadd' command."
3283 "</td></tr>", body);
3284 g_free(tmp);
3287 tmp = body;
3288 body = g_strdup_printf("%s</table>", body);
3289 g_free(tmp);
3291 if (uri)
3292 free(uri);
3293 if (title)
3294 free(title);
3296 /* render */
3297 if (!failed) {
3298 page = get_html_page("Favorites", body, "", 1);
3299 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3300 g_free(page);
3303 update_favorite_tabs(t);
3305 if (body)
3306 g_free(body);
3308 return (failed);
3311 void
3312 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3313 size_t cert_count, char *title)
3315 gnutls_datum_t cinfo;
3316 char *tmp, *body;
3317 int i;
3319 body = g_strdup("");
3321 for (i = 0; i < cert_count; i++) {
3322 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3323 &cinfo))
3324 return;
3326 tmp = body;
3327 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3328 body, i, cinfo.data);
3329 gnutls_free(cinfo.data);
3330 g_free(tmp);
3333 tmp = get_html_page(title, body, "", 0);
3334 g_free(body);
3336 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3337 g_free(tmp);
3341 ca_cmd(struct tab *t, struct karg *args)
3343 FILE *f = NULL;
3344 int rv = 1, certs = 0, certs_read;
3345 struct stat sb;
3346 gnutls_datum_t dt;
3347 gnutls_x509_crt_t *c = NULL;
3348 char *certs_buf = NULL, *s;
3350 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3351 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3352 return (1);
3355 if (fstat(fileno(f), &sb) == -1) {
3356 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3357 goto done;
3360 certs_buf = g_malloc(sb.st_size + 1);
3361 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3362 show_oops(t, "Can't read CA file: %s", strerror(errno));
3363 goto done;
3365 certs_buf[sb.st_size] = '\0';
3367 s = certs_buf;
3368 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3369 certs++;
3370 s += strlen("BEGIN CERTIFICATE");
3373 bzero(&dt, sizeof dt);
3374 dt.data = (unsigned char *)certs_buf;
3375 dt.size = sb.st_size;
3376 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3377 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3378 GNUTLS_X509_FMT_PEM, 0);
3379 if (certs_read <= 0) {
3380 show_oops(t, "No cert(s) available");
3381 goto done;
3383 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3384 done:
3385 if (c)
3386 g_free(c);
3387 if (certs_buf)
3388 g_free(certs_buf);
3389 if (f)
3390 fclose(f);
3392 return (rv);
3396 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3397 size_t domain_sz)
3399 SoupURI *su = NULL;
3400 struct addrinfo hints, *res = NULL, *ai;
3401 int rv = -1, s = -1, on, error;
3402 char port[8];
3403 static gchar myerror[256]; /* this is not thread safe */
3405 myerror[0] = '\0';
3406 *error_str = myerror;
3407 if (uri && !g_str_has_prefix(uri, "https://")) {
3408 *error_str = "invalid URI";
3409 goto done;
3412 su = soup_uri_new(uri);
3413 if (su == NULL) {
3414 *error_str = "invalid soup URI";
3415 goto done;
3417 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3418 *error_str = "invalid HTTPS URI";
3419 goto done;
3422 snprintf(port, sizeof port, "%d", su->port);
3423 bzero(&hints, sizeof(struct addrinfo));
3424 hints.ai_flags = AI_CANONNAME;
3425 hints.ai_family = AF_UNSPEC;
3426 hints.ai_socktype = SOCK_STREAM;
3428 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3429 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3430 gai_strerror(errno));
3431 goto done;
3434 for (ai = res; ai; ai = ai->ai_next) {
3435 if (s != -1) {
3436 close(s);
3437 s = -1;
3440 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3441 continue;
3442 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3443 if (s == -1)
3444 continue;
3445 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3446 sizeof(on)) == -1)
3447 continue;
3448 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3449 break;
3451 if (s == -1) {
3452 snprintf(myerror, sizeof myerror,
3453 "could not obtain certificates from: %s",
3454 su->host);
3455 goto done;
3458 if (domain)
3459 strlcpy(domain, su->host, domain_sz);
3460 rv = s;
3461 done:
3462 if (su)
3463 soup_uri_free(su);
3464 if (res)
3465 freeaddrinfo(res);
3466 if (rv == -1 && s != -1)
3467 close(s);
3469 return (rv);
3473 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3475 if (gsession)
3476 gnutls_deinit(gsession);
3477 if (xcred)
3478 gnutls_certificate_free_credentials(xcred);
3480 return (0);
3484 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3485 gnutls_certificate_credentials_t *xc)
3487 gnutls_certificate_credentials_t xcred;
3488 gnutls_session_t gsession;
3489 int rv = 1;
3490 static gchar myerror[1024]; /* this is not thread safe */
3492 if (gs == NULL || xc == NULL)
3493 goto done;
3495 myerror[0] = '\0';
3496 *gs = NULL;
3497 *xc = NULL;
3499 gnutls_certificate_allocate_credentials(&xcred);
3500 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3501 GNUTLS_X509_FMT_PEM);
3503 gnutls_init(&gsession, GNUTLS_CLIENT);
3504 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3505 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3506 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3507 if ((rv = gnutls_handshake(gsession)) < 0) {
3508 snprintf(myerror, sizeof myerror,
3509 "gnutls_handshake failed %d fatal %d %s",
3511 gnutls_error_is_fatal(rv),
3512 gnutls_strerror_name(rv));
3513 stop_tls(gsession, xcred);
3514 goto done;
3517 gnutls_credentials_type_t cred;
3518 cred = gnutls_auth_get_type(gsession);
3519 if (cred != GNUTLS_CRD_CERTIFICATE) {
3520 snprintf(myerror, sizeof myerror,
3521 "gnutls_auth_get_type failed %d",
3522 (int)cred);
3523 stop_tls(gsession, xcred);
3524 goto done;
3527 *gs = gsession;
3528 *xc = xcred;
3529 rv = 0;
3530 done:
3531 *error_str = myerror;
3532 return (rv);
3536 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3537 size_t *cert_count)
3539 unsigned int len;
3540 const gnutls_datum_t *cl;
3541 gnutls_x509_crt_t *all_certs;
3542 int i, rv = 1;
3544 if (certs == NULL || cert_count == NULL)
3545 goto done;
3546 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3547 goto done;
3548 cl = gnutls_certificate_get_peers(gsession, &len);
3549 if (len == 0)
3550 goto done;
3552 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3553 for (i = 0; i < len; i++) {
3554 gnutls_x509_crt_init(&all_certs[i]);
3555 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3556 GNUTLS_X509_FMT_PEM < 0)) {
3557 g_free(all_certs);
3558 goto done;
3562 *certs = all_certs;
3563 *cert_count = len;
3564 rv = 0;
3565 done:
3566 return (rv);
3569 void
3570 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3572 int i;
3574 for (i = 0; i < cert_count; i++)
3575 gnutls_x509_crt_deinit(certs[i]);
3576 g_free(certs);
3579 void
3580 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3582 GdkColor c_text, c_base;
3584 gdk_color_parse(text, &c_text);
3585 gdk_color_parse(base, &c_base);
3587 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3588 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3589 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3590 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3592 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3593 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3594 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3595 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3598 void
3599 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3600 size_t cert_count, char *domain)
3602 size_t cert_buf_sz;
3603 char cert_buf[64 * 1024], file[PATH_MAX];
3604 int i;
3605 FILE *f;
3606 GdkColor color;
3608 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3609 return;
3611 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3612 if ((f = fopen(file, "w")) == NULL) {
3613 show_oops(t, "Can't create cert file %s %s",
3614 file, strerror(errno));
3615 return;
3618 for (i = 0; i < cert_count; i++) {
3619 cert_buf_sz = sizeof cert_buf;
3620 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3621 cert_buf, &cert_buf_sz)) {
3622 show_oops(t, "gnutls_x509_crt_export failed");
3623 goto done;
3625 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3626 show_oops(t, "Can't write certs: %s", strerror(errno));
3627 goto done;
3631 /* not the best spot but oh well */
3632 gdk_color_parse("lightblue", &color);
3633 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3634 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3635 done:
3636 fclose(f);
3639 enum cert_trust {
3640 CERT_LOCAL,
3641 CERT_TRUSTED,
3642 CERT_UNTRUSTED,
3643 CERT_BAD
3646 enum cert_trust
3647 load_compare_cert(const gchar *uri, const gchar **error_str)
3649 char domain[8182], file[PATH_MAX];
3650 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3651 int s = -1, i;
3652 unsigned int error = 0;
3653 FILE *f = NULL;
3654 size_t cert_buf_sz, cert_count;
3655 enum cert_trust rv = CERT_UNTRUSTED;
3656 static gchar serr[80]; /* this isn't thread safe */
3657 gnutls_session_t gsession;
3658 gnutls_x509_crt_t *certs;
3659 gnutls_certificate_credentials_t xcred;
3661 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3663 serr[0] = '\0';
3664 *error_str = serr;
3665 if ((s = connect_socket_from_uri(uri, error_str, domain,
3666 sizeof domain)) == -1)
3667 return (rv);
3669 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3671 /* go ssl/tls */
3672 if (start_tls(error_str, s, &gsession, &xcred))
3673 goto done;
3674 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3676 /* verify certs in case cert file doesn't exist */
3677 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3678 GNUTLS_E_SUCCESS) {
3679 *error_str = "Invalid certificates";
3680 goto done;
3683 /* get certs */
3684 if (get_connection_certs(gsession, &certs, &cert_count)) {
3685 *error_str = "Can't get connection certificates";
3686 goto done;
3689 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3690 if ((f = fopen(file, "r")) == NULL) {
3691 if (!error)
3692 rv = CERT_TRUSTED;
3693 goto freeit;
3696 for (i = 0; i < cert_count; i++) {
3697 cert_buf_sz = sizeof cert_buf;
3698 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3699 cert_buf, &cert_buf_sz)) {
3700 goto freeit;
3702 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3703 rv = CERT_BAD; /* critical */
3704 goto freeit;
3706 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3707 rv = CERT_BAD; /* critical */
3708 goto freeit;
3710 rv = CERT_LOCAL;
3713 freeit:
3714 if (f)
3715 fclose(f);
3716 free_connection_certs(certs, cert_count);
3717 done:
3718 /* we close the socket first for speed */
3719 if (s != -1)
3720 close(s);
3722 /* only complain if we didn't save it locally */
3723 if (error && rv != CERT_LOCAL) {
3724 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3725 if (error & GNUTLS_CERT_INVALID)
3726 strlcat(serr, "invalid, ", sizeof serr);
3727 if (error & GNUTLS_CERT_REVOKED)
3728 strlcat(serr, "revoked, ", sizeof serr);
3729 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3730 strlcat(serr, "signer not found, ", sizeof serr);
3731 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3732 strlcat(serr, "not signed by CA, ", sizeof serr);
3733 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3734 strlcat(serr, "insecure algorithm, ", sizeof serr);
3735 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3736 strlcat(serr, "not activated, ", sizeof serr);
3737 if (error & GNUTLS_CERT_EXPIRED)
3738 strlcat(serr, "expired, ", sizeof serr);
3739 for (i = strlen(serr) - 1; i > 0; i--)
3740 if (serr[i] == ',') {
3741 serr[i] = '\0';
3742 break;
3744 *error_str = serr;
3747 stop_tls(gsession, xcred);
3749 return (rv);
3753 cert_cmd(struct tab *t, struct karg *args)
3755 const gchar *uri, *error_str = NULL;
3756 char domain[8182];
3757 int s = -1;
3758 size_t cert_count;
3759 gnutls_session_t gsession;
3760 gnutls_x509_crt_t *certs;
3761 gnutls_certificate_credentials_t xcred;
3763 if (t == NULL)
3764 return (1);
3766 if (ssl_ca_file == NULL) {
3767 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3768 return (1);
3771 if ((uri = get_uri(t)) == NULL) {
3772 show_oops(t, "Invalid URI");
3773 return (1);
3776 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3777 sizeof domain)) == -1) {
3778 show_oops(t, "%s", error_str);
3779 return (1);
3782 /* go ssl/tls */
3783 if (start_tls(&error_str, s, &gsession, &xcred))
3784 goto done;
3786 /* get certs */
3787 if (get_connection_certs(gsession, &certs, &cert_count)) {
3788 show_oops(t, "get_connection_certs failed");
3789 goto done;
3792 if (args->i & XT_SHOW)
3793 show_certs(t, certs, cert_count, "Certificate Chain");
3794 else if (args->i & XT_SAVE)
3795 save_certs(t, certs, cert_count, domain);
3797 free_connection_certs(certs, cert_count);
3798 done:
3799 /* we close the socket first for speed */
3800 if (s != -1)
3801 close(s);
3802 stop_tls(gsession, xcred);
3803 if (error_str)
3804 show_oops(t, "%s", error_str);
3805 return (0);
3809 remove_cookie(int index)
3811 int i, rv = 1;
3812 GSList *cf;
3813 SoupCookie *c;
3815 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3817 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3819 for (i = 1; cf; cf = cf->next, i++) {
3820 if (i != index)
3821 continue;
3822 c = cf->data;
3823 print_cookie("remove cookie", c);
3824 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3825 rv = 0;
3826 break;
3829 soup_cookies_free(cf);
3831 return (rv);
3835 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3837 struct domain *d;
3838 char *tmp, *body;
3840 body = g_strdup("");
3842 /* p list */
3843 if (args->i & XT_WL_PERSISTENT) {
3844 tmp = body;
3845 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3846 g_free(tmp);
3847 RB_FOREACH(d, domain_list, wl) {
3848 if (d->handy == 0)
3849 continue;
3850 tmp = body;
3851 body = g_strdup_printf("%s%s<br/>", body, d->d);
3852 g_free(tmp);
3856 /* s list */
3857 if (args->i & XT_WL_SESSION) {
3858 tmp = body;
3859 body = g_strdup_printf("%s<h2>Session</h2>", body);
3860 g_free(tmp);
3861 RB_FOREACH(d, domain_list, wl) {
3862 if (d->handy == 1)
3863 continue;
3864 tmp = body;
3865 body = g_strdup_printf("%s%s<br/>", body, d->d);
3866 g_free(tmp);
3870 tmp = get_html_page(title, body, "", 0);
3871 g_free(body);
3872 if (wl == &js_wl)
3873 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3874 else
3875 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3876 g_free(tmp);
3877 return (0);
3881 wl_save(struct tab *t, struct karg *args, int js)
3883 char file[PATH_MAX];
3884 FILE *f;
3885 char *line = NULL, *lt = NULL, *dom = NULL;
3886 size_t linelen;
3887 const gchar *uri;
3888 struct karg a;
3889 struct domain *d;
3890 GSList *cf;
3891 SoupCookie *ci, *c;
3893 if (t == NULL || args == NULL)
3894 return (1);
3896 if (runtime_settings[0] == '\0')
3897 return (1);
3899 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3900 if ((f = fopen(file, "r+")) == NULL)
3901 return (1);
3903 uri = get_uri(t);
3904 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3905 if (uri == NULL || dom == NULL ||
3906 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3907 show_oops(t, "Can't add domain to %s white list",
3908 js ? "JavaScript" : "cookie");
3909 goto done;
3912 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3914 while (!feof(f)) {
3915 line = fparseln(f, &linelen, NULL, NULL, 0);
3916 if (line == NULL)
3917 continue;
3918 if (!strcmp(line, lt))
3919 goto done;
3920 free(line);
3921 line = NULL;
3924 fprintf(f, "%s\n", lt);
3926 a.i = XT_WL_ENABLE;
3927 a.i |= args->i;
3928 if (js) {
3929 d = wl_find(dom, &js_wl);
3930 if (!d) {
3931 settings_add("js_wl", dom);
3932 d = wl_find(dom, &js_wl);
3934 toggle_js(t, &a);
3935 } else {
3936 d = wl_find(dom, &c_wl);
3937 if (!d) {
3938 settings_add("cookie_wl", dom);
3939 d = wl_find(dom, &c_wl);
3941 toggle_cwl(t, &a);
3943 /* find and add to persistent jar */
3944 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3945 for (;cf; cf = cf->next) {
3946 ci = cf->data;
3947 if (!strcmp(dom, ci->domain) ||
3948 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3949 c = soup_cookie_copy(ci);
3950 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3953 soup_cookies_free(cf);
3955 if (d)
3956 d->handy = 1;
3958 done:
3959 if (line)
3960 free(line);
3961 if (dom)
3962 g_free(dom);
3963 if (lt)
3964 g_free(lt);
3965 fclose(f);
3967 return (0);
3971 js_show_wl(struct tab *t, struct karg *args)
3973 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3974 wl_show(t, args, "JavaScript White List", &js_wl);
3976 return (0);
3980 cookie_show_wl(struct tab *t, struct karg *args)
3982 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3983 wl_show(t, args, "Cookie White List", &c_wl);
3985 return (0);
3989 cookie_cmd(struct tab *t, struct karg *args)
3991 if (args->i & XT_SHOW)
3992 wl_show(t, args, "Cookie White List", &c_wl);
3993 else if (args->i & XT_WL_TOGGLE) {
3994 args->i |= XT_WL_RELOAD;
3995 toggle_cwl(t, args);
3996 } else if (args->i & XT_SAVE) {
3997 args->i |= XT_WL_RELOAD;
3998 wl_save(t, args, 0);
3999 } else if (args->i & XT_DELETE)
4000 show_oops(t, "'cookie delete' currently unimplemented");
4002 return (0);
4006 js_cmd(struct tab *t, struct karg *args)
4008 if (args->i & XT_SHOW)
4009 wl_show(t, args, "JavaScript White List", &js_wl);
4010 else if (args->i & XT_SAVE) {
4011 args->i |= XT_WL_RELOAD;
4012 wl_save(t, args, 1);
4013 } else if (args->i & XT_WL_TOGGLE) {
4014 args->i |= XT_WL_RELOAD;
4015 toggle_js(t, args);
4016 } else if (args->i & XT_DELETE)
4017 show_oops(t, "'js delete' currently unimplemented");
4019 return (0);
4023 toplevel_cmd(struct tab *t, struct karg *args)
4025 js_toggle_cb(t->js_toggle, t);
4027 return (0);
4031 add_favorite(struct tab *t, struct karg *args)
4033 char file[PATH_MAX];
4034 FILE *f;
4035 char *line = NULL;
4036 size_t urilen, linelen;
4037 const gchar *uri, *title;
4039 if (t == NULL)
4040 return (1);
4042 /* don't allow adding of xtp pages to favorites */
4043 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4044 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4045 return (1);
4048 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4049 if ((f = fopen(file, "r+")) == NULL) {
4050 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4051 return (1);
4054 title = get_title(t, FALSE);
4055 uri = get_uri(t);
4057 if (title == NULL || uri == NULL) {
4058 show_oops(t, "can't add page to favorites");
4059 goto done;
4062 urilen = strlen(uri);
4064 for (;;) {
4065 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4066 if (feof(f) || ferror(f))
4067 break;
4069 if (linelen == urilen && !strcmp(line, uri))
4070 goto done;
4072 free(line);
4073 line = NULL;
4076 fprintf(f, "\n%s\n%s", title, uri);
4077 done:
4078 if (line)
4079 free(line);
4080 fclose(f);
4082 update_favorite_tabs(NULL);
4084 return (0);
4088 can_go_back_for_real(struct tab *t)
4090 int i;
4091 WebKitWebHistoryItem *item;
4092 const gchar *uri;
4094 if (t == NULL)
4095 return (FALSE);
4097 /* rely on webkit to make sure we can go backward when on an about page */
4098 uri = get_uri(t);
4099 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4100 return (webkit_web_view_can_go_back(t->wv));
4102 /* the back/forwars list is stupid so help determine if we can go back */
4103 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4104 item != NULL;
4105 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4106 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4107 return (TRUE);
4110 return (FALSE);
4114 can_go_forward_for_real(struct tab *t)
4116 int i;
4117 WebKitWebHistoryItem *item;
4118 const gchar *uri;
4120 if (t == NULL)
4121 return (FALSE);
4123 /* rely on webkit to make sure we can go forward when on an about page */
4124 uri = get_uri(t);
4125 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4126 return (webkit_web_view_can_go_forward(t->wv));
4128 /* the back/forwars list is stupid so help selecting a different item */
4129 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4130 item != NULL;
4131 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4132 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4133 return (TRUE);
4136 return (FALSE);
4139 void
4140 go_back_for_real(struct tab *t)
4142 int i;
4143 WebKitWebHistoryItem *item;
4144 const gchar *uri;
4146 if (t == NULL)
4147 return;
4149 uri = get_uri(t);
4150 if (uri == NULL) {
4151 webkit_web_view_go_back(t->wv);
4152 return;
4154 /* the back/forwars list is stupid so help selecting a different item */
4155 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4156 item != NULL;
4157 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4158 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4159 webkit_web_view_go_to_back_forward_item(t->wv, item);
4160 break;
4165 void
4166 go_forward_for_real(struct tab *t)
4168 int i;
4169 WebKitWebHistoryItem *item;
4170 const gchar *uri;
4172 if (t == NULL)
4173 return;
4175 uri = get_uri(t);
4176 if (uri == NULL) {
4177 webkit_web_view_go_forward(t->wv);
4178 return;
4180 /* the back/forwars list is stupid so help selecting a different item */
4181 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4182 item != NULL;
4183 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4184 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4185 webkit_web_view_go_to_back_forward_item(t->wv, item);
4186 break;
4192 navaction(struct tab *t, struct karg *args)
4194 WebKitWebHistoryItem *item;
4195 WebKitWebFrame *frame;
4197 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4198 t->tab_id, args->i);
4200 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4201 if (t->item) {
4202 if (args->i == XT_NAV_BACK)
4203 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4204 else
4205 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4206 if (item == NULL)
4207 return (XT_CB_PASSTHROUGH);
4208 webkit_web_view_go_to_back_forward_item(t->wv, item);
4209 t->item = NULL;
4210 return (XT_CB_PASSTHROUGH);
4213 switch (args->i) {
4214 case XT_NAV_BACK:
4215 marks_clear(t);
4216 go_back_for_real(t);
4217 break;
4218 case XT_NAV_FORWARD:
4219 marks_clear(t);
4220 go_forward_for_real(t);
4221 break;
4222 case XT_NAV_RELOAD:
4223 frame = webkit_web_view_get_main_frame(t->wv);
4224 webkit_web_frame_reload(frame);
4225 break;
4227 return (XT_CB_PASSTHROUGH);
4231 move(struct tab *t, struct karg *args)
4233 GtkAdjustment *adjust;
4234 double pi, si, pos, ps, upper, lower, max;
4235 double percent;
4237 switch (args->i) {
4238 case XT_MOVE_DOWN:
4239 case XT_MOVE_UP:
4240 case XT_MOVE_BOTTOM:
4241 case XT_MOVE_TOP:
4242 case XT_MOVE_PAGEDOWN:
4243 case XT_MOVE_PAGEUP:
4244 case XT_MOVE_HALFDOWN:
4245 case XT_MOVE_HALFUP:
4246 case XT_MOVE_PERCENT:
4247 adjust = t->adjust_v;
4248 break;
4249 default:
4250 adjust = t->adjust_h;
4251 break;
4254 pos = gtk_adjustment_get_value(adjust);
4255 ps = gtk_adjustment_get_page_size(adjust);
4256 upper = gtk_adjustment_get_upper(adjust);
4257 lower = gtk_adjustment_get_lower(adjust);
4258 si = gtk_adjustment_get_step_increment(adjust);
4259 pi = gtk_adjustment_get_page_increment(adjust);
4260 max = upper - ps;
4262 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4263 "max %f si %f pi %f\n",
4264 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4265 pos, ps, upper, lower, max, si, pi);
4267 switch (args->i) {
4268 case XT_MOVE_DOWN:
4269 case XT_MOVE_RIGHT:
4270 pos += si;
4271 gtk_adjustment_set_value(adjust, MIN(pos, max));
4272 break;
4273 case XT_MOVE_UP:
4274 case XT_MOVE_LEFT:
4275 pos -= si;
4276 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4277 break;
4278 case XT_MOVE_BOTTOM:
4279 case XT_MOVE_FARRIGHT:
4280 gtk_adjustment_set_value(adjust, max);
4281 break;
4282 case XT_MOVE_TOP:
4283 case XT_MOVE_FARLEFT:
4284 gtk_adjustment_set_value(adjust, lower);
4285 break;
4286 case XT_MOVE_PAGEDOWN:
4287 pos += pi;
4288 gtk_adjustment_set_value(adjust, MIN(pos, max));
4289 break;
4290 case XT_MOVE_PAGEUP:
4291 pos -= pi;
4292 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4293 break;
4294 case XT_MOVE_HALFDOWN:
4295 pos += pi / 2;
4296 gtk_adjustment_set_value(adjust, MIN(pos, max));
4297 break;
4298 case XT_MOVE_HALFUP:
4299 pos -= pi / 2;
4300 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4301 break;
4302 case XT_MOVE_PERCENT:
4303 percent = atoi(args->s) / 100.0;
4304 pos = max * percent;
4305 if (pos < 0.0 || pos > max)
4306 break;
4307 gtk_adjustment_set_value(adjust, pos);
4308 break;
4309 default:
4310 return (XT_CB_PASSTHROUGH);
4313 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4315 return (XT_CB_HANDLED);
4318 void
4319 url_set_visibility(void)
4321 struct tab *t;
4323 TAILQ_FOREACH(t, &tabs, entry)
4324 if (show_url == 0) {
4325 gtk_widget_hide(t->toolbar);
4326 focus_webview(t);
4327 } else
4328 gtk_widget_show(t->toolbar);
4331 void
4332 notebook_tab_set_visibility(void)
4334 if (show_tabs == 0) {
4335 gtk_widget_hide(tab_bar);
4336 gtk_notebook_set_show_tabs(notebook, FALSE);
4337 } else {
4338 if (tab_style == XT_TABS_NORMAL) {
4339 gtk_widget_hide(tab_bar);
4340 gtk_notebook_set_show_tabs(notebook, TRUE);
4341 } else if (tab_style == XT_TABS_COMPACT) {
4342 gtk_widget_show(tab_bar);
4343 gtk_notebook_set_show_tabs(notebook, FALSE);
4348 void
4349 statusbar_set_visibility(void)
4351 struct tab *t;
4353 TAILQ_FOREACH(t, &tabs, entry)
4354 if (show_statusbar == 0) {
4355 gtk_widget_hide(t->statusbar_box);
4356 focus_webview(t);
4357 } else
4358 gtk_widget_show(t->statusbar_box);
4361 void
4362 url_set(struct tab *t, int enable_url_entry)
4364 GdkPixbuf *pixbuf;
4365 int progress;
4367 show_url = enable_url_entry;
4369 if (enable_url_entry) {
4370 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4371 GTK_ENTRY_ICON_PRIMARY, NULL);
4372 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4373 } else {
4374 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4375 GTK_ENTRY_ICON_PRIMARY);
4376 progress =
4377 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4378 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4379 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4380 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4381 progress);
4386 fullscreen(struct tab *t, struct karg *args)
4388 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4390 if (t == NULL)
4391 return (XT_CB_PASSTHROUGH);
4393 if (show_url == 0) {
4394 url_set(t, 1);
4395 show_tabs = 1;
4396 } else {
4397 url_set(t, 0);
4398 show_tabs = 0;
4401 url_set_visibility();
4402 notebook_tab_set_visibility();
4404 return (XT_CB_HANDLED);
4408 statustoggle(struct tab *t, struct karg *args)
4410 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4412 if (show_statusbar == 1) {
4413 show_statusbar = 0;
4414 statusbar_set_visibility();
4415 } else if (show_statusbar == 0) {
4416 show_statusbar = 1;
4417 statusbar_set_visibility();
4419 return (XT_CB_HANDLED);
4423 urlaction(struct tab *t, struct karg *args)
4425 int rv = XT_CB_HANDLED;
4427 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4429 if (t == NULL)
4430 return (XT_CB_PASSTHROUGH);
4432 switch (args->i) {
4433 case XT_URL_SHOW:
4434 if (show_url == 0) {
4435 url_set(t, 1);
4436 url_set_visibility();
4438 break;
4439 case XT_URL_HIDE:
4440 if (show_url == 1) {
4441 url_set(t, 0);
4442 url_set_visibility();
4444 break;
4446 return (rv);
4450 tabaction(struct tab *t, struct karg *args)
4452 int rv = XT_CB_HANDLED;
4453 char *url = args->s;
4454 struct undo *u;
4455 struct tab *tt;
4457 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4459 if (t == NULL)
4460 return (XT_CB_PASSTHROUGH);
4462 switch (args->i) {
4463 case XT_TAB_NEW:
4464 if (strlen(url) > 0)
4465 create_new_tab(url, NULL, 1, args->precount);
4466 else
4467 create_new_tab(NULL, NULL, 1, args->precount);
4468 break;
4469 case XT_TAB_DELETE:
4470 if (args->precount < 0)
4471 delete_tab(t);
4472 else
4473 TAILQ_FOREACH(tt, &tabs, entry)
4474 if (tt->tab_id == args->precount - 1) {
4475 delete_tab(tt);
4476 break;
4478 break;
4479 case XT_TAB_DELQUIT:
4480 if (gtk_notebook_get_n_pages(notebook) > 1)
4481 delete_tab(t);
4482 else
4483 quit(t, args);
4484 break;
4485 case XT_TAB_OPEN:
4486 if (strlen(url) > 0)
4488 else {
4489 rv = XT_CB_PASSTHROUGH;
4490 goto done;
4492 load_uri(t, url);
4493 break;
4494 case XT_TAB_SHOW:
4495 if (show_tabs == 0) {
4496 show_tabs = 1;
4497 notebook_tab_set_visibility();
4499 break;
4500 case XT_TAB_HIDE:
4501 if (show_tabs == 1) {
4502 show_tabs = 0;
4503 notebook_tab_set_visibility();
4505 break;
4506 case XT_TAB_NEXTSTYLE:
4507 if (tab_style == XT_TABS_NORMAL) {
4508 tab_style = XT_TABS_COMPACT;
4509 recolor_compact_tabs();
4511 else
4512 tab_style = XT_TABS_NORMAL;
4513 notebook_tab_set_visibility();
4514 break;
4515 case XT_TAB_UNDO_CLOSE:
4516 if (undo_count == 0) {
4517 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4518 __func__);
4519 goto done;
4520 } else {
4521 undo_count--;
4522 u = TAILQ_FIRST(&undos);
4523 create_new_tab(u->uri, u, 1, -1);
4525 TAILQ_REMOVE(&undos, u, entry);
4526 g_free(u->uri);
4527 /* u->history is freed in create_new_tab() */
4528 g_free(u);
4530 break;
4531 default:
4532 rv = XT_CB_PASSTHROUGH;
4533 goto done;
4536 done:
4537 if (args->s) {
4538 g_free(args->s);
4539 args->s = NULL;
4542 return (rv);
4546 resizetab(struct tab *t, struct karg *args)
4548 if (t == NULL || args == NULL) {
4549 show_oops(NULL, "resizetab invalid parameters");
4550 return (XT_CB_PASSTHROUGH);
4553 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4554 t->tab_id, args->i);
4556 setzoom_webkit(t, args->i);
4558 return (XT_CB_HANDLED);
4562 movetab(struct tab *t, struct karg *args)
4564 int n, dest;
4566 if (t == NULL || args == NULL) {
4567 show_oops(NULL, "movetab invalid parameters");
4568 return (XT_CB_PASSTHROUGH);
4571 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4572 t->tab_id, args->i);
4574 if (args->i >= XT_TAB_INVALID)
4575 return (XT_CB_PASSTHROUGH);
4577 if (TAILQ_EMPTY(&tabs))
4578 return (XT_CB_PASSTHROUGH);
4580 n = gtk_notebook_get_n_pages(notebook);
4581 dest = gtk_notebook_get_current_page(notebook);
4583 switch (args->i) {
4584 case XT_TAB_NEXT:
4585 if (args->precount < 0)
4586 dest = dest == n - 1 ? 0 : dest + 1;
4587 else
4588 dest = args->precount - 1;
4590 break;
4591 case XT_TAB_PREV:
4592 if (args->precount < 0)
4593 dest -= 1;
4594 else
4595 dest -= args->precount % n;
4597 if (dest < 0)
4598 dest += n;
4600 break;
4601 case XT_TAB_FIRST:
4602 dest = 0;
4603 break;
4604 case XT_TAB_LAST:
4605 dest = n - 1;
4606 break;
4607 default:
4608 return (XT_CB_PASSTHROUGH);
4611 if (dest < 0 || dest >= n)
4612 return (XT_CB_PASSTHROUGH);
4613 if (t->tab_id == dest) {
4614 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4615 return (XT_CB_HANDLED);
4618 set_current_tab(dest);
4620 return (XT_CB_HANDLED);
4623 int cmd_prefix = 0;
4626 command(struct tab *t, struct karg *args)
4628 char *s = NULL, *ss = NULL;
4629 GdkColor color;
4630 const gchar *uri;
4632 if (t == NULL || args == NULL) {
4633 show_oops(NULL, "command invalid parameters");
4634 return (XT_CB_PASSTHROUGH);
4637 switch (args->i) {
4638 case '/':
4639 s = "/";
4640 break;
4641 case '?':
4642 s = "?";
4643 break;
4644 case ':':
4645 if (cmd_prefix == 0)
4646 s = ":";
4647 else {
4648 ss = g_strdup_printf(":%d", cmd_prefix);
4649 s = ss;
4650 cmd_prefix = 0;
4652 break;
4653 case XT_CMD_OPEN:
4654 s = ":open ";
4655 break;
4656 case XT_CMD_TABNEW:
4657 s = ":tabnew ";
4658 break;
4659 case XT_CMD_OPEN_CURRENT:
4660 s = ":open ";
4661 /* FALL THROUGH */
4662 case XT_CMD_TABNEW_CURRENT:
4663 if (!s) /* FALL THROUGH? */
4664 s = ":tabnew ";
4665 if ((uri = get_uri(t)) != NULL) {
4666 ss = g_strdup_printf("%s%s", s, uri);
4667 s = ss;
4669 break;
4670 default:
4671 show_oops(t, "command: invalid opcode %d", args->i);
4672 return (XT_CB_PASSTHROUGH);
4675 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4677 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4678 gdk_color_parse(XT_COLOR_WHITE, &color);
4679 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4680 show_cmd(t);
4681 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4682 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4684 if (ss)
4685 g_free(ss);
4687 return (XT_CB_HANDLED);
4691 * Return a new string with a download row (in html)
4692 * appended. Old string is freed.
4694 char *
4695 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4698 WebKitDownloadStatus stat;
4699 char *status_html = NULL, *cmd_html = NULL, *new_html;
4700 gdouble progress;
4701 char cur_sz[FMT_SCALED_STRSIZE];
4702 char tot_sz[FMT_SCALED_STRSIZE];
4703 char *xtp_prefix;
4705 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4707 /* All actions wil take this form:
4708 * xxxt://class/seskey
4710 xtp_prefix = g_strdup_printf("%s%d/%s/",
4711 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4713 stat = webkit_download_get_status(dl->download);
4715 switch (stat) {
4716 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4717 status_html = g_strdup_printf("Finished");
4718 cmd_html = g_strdup_printf(
4719 "<a href='%s%d/%d'>Remove</a>",
4720 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4721 break;
4722 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4723 /* gather size info */
4724 progress = 100 * webkit_download_get_progress(dl->download);
4726 fmt_scaled(
4727 webkit_download_get_current_size(dl->download), cur_sz);
4728 fmt_scaled(
4729 webkit_download_get_total_size(dl->download), tot_sz);
4731 status_html = g_strdup_printf(
4732 "<div style='width: 100%%' align='center'>"
4733 "<div class='progress-outer'>"
4734 "<div class='progress-inner' style='width: %.2f%%'>"
4735 "</div></div></div>"
4736 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4737 progress, cur_sz, tot_sz, progress);
4739 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4740 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4742 break;
4743 /* LLL */
4744 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4745 status_html = g_strdup_printf("Cancelled");
4746 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4747 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4748 break;
4749 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4750 status_html = g_strdup_printf("Error!");
4751 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4752 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4753 break;
4754 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4755 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4756 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4757 status_html = g_strdup_printf("Starting");
4758 break;
4759 default:
4760 show_oops(t, "%s: unknown download status", __func__);
4763 new_html = g_strdup_printf(
4764 "%s\n<tr><td>%s</td><td>%s</td>"
4765 "<td style='text-align:center'>%s</td></tr>\n",
4766 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4767 status_html, cmd_html);
4768 g_free(html);
4770 if (status_html)
4771 g_free(status_html);
4773 if (cmd_html)
4774 g_free(cmd_html);
4776 g_free(xtp_prefix);
4778 return new_html;
4782 * update all download tabs apart from one. Pass NULL if
4783 * you want to update all.
4785 void
4786 update_download_tabs(struct tab *apart_from)
4788 struct tab *t;
4789 if (!updating_dl_tabs) {
4790 updating_dl_tabs = 1; /* stop infinite recursion */
4791 TAILQ_FOREACH(t, &tabs, entry)
4792 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4793 && (t != apart_from))
4794 xtp_page_dl(t, NULL);
4795 updating_dl_tabs = 0;
4800 * update all cookie tabs apart from one. Pass NULL if
4801 * you want to update all.
4803 void
4804 update_cookie_tabs(struct tab *apart_from)
4806 struct tab *t;
4807 if (!updating_cl_tabs) {
4808 updating_cl_tabs = 1; /* stop infinite recursion */
4809 TAILQ_FOREACH(t, &tabs, entry)
4810 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4811 && (t != apart_from))
4812 xtp_page_cl(t, NULL);
4813 updating_cl_tabs = 0;
4818 * update all history tabs apart from one. Pass NULL if
4819 * you want to update all.
4821 void
4822 update_history_tabs(struct tab *apart_from)
4824 struct tab *t;
4826 if (!updating_hl_tabs) {
4827 updating_hl_tabs = 1; /* stop infinite recursion */
4828 TAILQ_FOREACH(t, &tabs, entry)
4829 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4830 && (t != apart_from))
4831 xtp_page_hl(t, NULL);
4832 updating_hl_tabs = 0;
4836 /* cookie management XTP page */
4838 xtp_page_cl(struct tab *t, struct karg *args)
4840 char *body, *page, *tmp;
4841 int i = 1; /* all ids start 1 */
4842 GSList *sc, *pc, *pc_start;
4843 SoupCookie *c;
4844 char *type, *table_headers, *last_domain;
4846 DNPRINTF(XT_D_CMD, "%s", __func__);
4848 if (t == NULL) {
4849 show_oops(NULL, "%s invalid parameters", __func__);
4850 return (1);
4853 /* Generate a new session key */
4854 if (!updating_cl_tabs)
4855 generate_xtp_session_key(&cl_session_key);
4857 /* table headers */
4858 table_headers = g_strdup_printf("<table><tr>"
4859 "<th>Type</th>"
4860 "<th>Name</th>"
4861 "<th style='width:200px'>Value</th>"
4862 "<th>Path</th>"
4863 "<th>Expires</th>"
4864 "<th>Secure</th>"
4865 "<th>HTTP<br />only</th>"
4866 "<th style='width:40px'>Rm</th></tr>\n");
4868 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4869 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4870 pc_start = pc;
4872 body = NULL;
4873 last_domain = strdup("");
4874 for (; sc; sc = sc->next) {
4875 c = sc->data;
4877 if (strcmp(last_domain, c->domain) != 0) {
4878 /* new domain */
4879 free(last_domain);
4880 last_domain = strdup(c->domain);
4882 if (body != NULL) {
4883 tmp = body;
4884 body = g_strdup_printf("%s</table>"
4885 "<h2>%s</h2>%s\n",
4886 body, c->domain, table_headers);
4887 g_free(tmp);
4888 } else {
4889 /* first domain */
4890 body = g_strdup_printf("<h2>%s</h2>%s\n",
4891 c->domain, table_headers);
4895 type = "Session";
4896 for (pc = pc_start; pc; pc = pc->next)
4897 if (soup_cookie_equal(pc->data, c)) {
4898 type = "Session + Persistent";
4899 break;
4902 tmp = body;
4903 body = g_strdup_printf(
4904 "%s\n<tr>"
4905 "<td>%s</td>"
4906 "<td style='word-wrap:normal'>%s</td>"
4907 "<td>"
4908 " <textarea rows='4'>%s</textarea>"
4909 "</td>"
4910 "<td>%s</td>"
4911 "<td>%s</td>"
4912 "<td>%d</td>"
4913 "<td>%d</td>"
4914 "<td style='text-align:center'>"
4915 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4916 body,
4917 type,
4918 c->name,
4919 c->value,
4920 c->path,
4921 c->expires ?
4922 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4923 c->secure,
4924 c->http_only,
4926 XT_XTP_STR,
4927 XT_XTP_CL,
4928 cl_session_key,
4929 XT_XTP_CL_REMOVE,
4933 g_free(tmp);
4934 i++;
4937 soup_cookies_free(sc);
4938 soup_cookies_free(pc);
4940 /* small message if there are none */
4941 if (i == 1) {
4942 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4943 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4945 tmp = body;
4946 body = g_strdup_printf("%s</table>", body);
4947 g_free(tmp);
4949 page = get_html_page("Cookie Jar", body, "", TRUE);
4950 g_free(body);
4951 g_free(table_headers);
4952 g_free(last_domain);
4954 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4955 update_cookie_tabs(t);
4957 g_free(page);
4959 return (0);
4963 xtp_page_hl(struct tab *t, struct karg *args)
4965 char *body, *page, *tmp;
4966 struct history *h;
4967 int i = 1; /* all ids start 1 */
4969 DNPRINTF(XT_D_CMD, "%s", __func__);
4971 if (t == NULL) {
4972 show_oops(NULL, "%s invalid parameters", __func__);
4973 return (1);
4976 /* Generate a new session key */
4977 if (!updating_hl_tabs)
4978 generate_xtp_session_key(&hl_session_key);
4980 /* body */
4981 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4982 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4984 RB_FOREACH_REVERSE(h, history_list, &hl) {
4985 tmp = body;
4986 body = g_strdup_printf(
4987 "%s\n<tr>"
4988 "<td><a href='%s'>%s</a></td>"
4989 "<td>%s</td>"
4990 "<td style='text-align: center'>"
4991 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4992 body, h->uri, h->uri, h->title,
4993 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4994 XT_XTP_HL_REMOVE, i);
4996 g_free(tmp);
4997 i++;
5000 /* small message if there are none */
5001 if (i == 1) {
5002 tmp = body;
5003 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5004 "colspan='3'>No History</td></tr>\n", body);
5005 g_free(tmp);
5008 tmp = body;
5009 body = g_strdup_printf("%s</table>", body);
5010 g_free(tmp);
5012 page = get_html_page("History", body, "", TRUE);
5013 g_free(body);
5016 * update all history manager tabs as the xtp session
5017 * key has now changed. No need to update the current tab.
5018 * Already did that above.
5020 update_history_tabs(t);
5022 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5023 g_free(page);
5025 return (0);
5029 * Generate a web page detailing the status of any downloads
5032 xtp_page_dl(struct tab *t, struct karg *args)
5034 struct download *dl;
5035 char *body, *page, *tmp;
5036 char *ref;
5037 int n_dl = 1;
5039 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5041 if (t == NULL) {
5042 show_oops(NULL, "%s invalid parameters", __func__);
5043 return (1);
5047 * Generate a new session key for next page instance.
5048 * This only happens for the top level call to xtp_page_dl()
5049 * in which case updating_dl_tabs is 0.
5051 if (!updating_dl_tabs)
5052 generate_xtp_session_key(&dl_session_key);
5054 /* header - with refresh so as to update */
5055 if (refresh_interval >= 1)
5056 ref = g_strdup_printf(
5057 "<meta http-equiv='refresh' content='%u"
5058 ";url=%s%d/%s/%d' />\n",
5059 refresh_interval,
5060 XT_XTP_STR,
5061 XT_XTP_DL,
5062 dl_session_key,
5063 XT_XTP_DL_LIST);
5064 else
5065 ref = g_strdup("");
5067 body = g_strdup_printf("<div align='center'>"
5068 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5069 "</p><table><tr><th style='width: 60%%'>"
5070 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5071 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5073 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5074 body = xtp_page_dl_row(t, body, dl);
5075 n_dl++;
5078 /* message if no downloads in list */
5079 if (n_dl == 1) {
5080 tmp = body;
5081 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5082 " style='text-align: center'>"
5083 "No downloads</td></tr>\n", body);
5084 g_free(tmp);
5087 tmp = body;
5088 body = g_strdup_printf("%s</table></div>", body);
5089 g_free(tmp);
5091 page = get_html_page("Downloads", body, ref, 1);
5092 g_free(ref);
5093 g_free(body);
5096 * update all download manager tabs as the xtp session
5097 * key has now changed. No need to update the current tab.
5098 * Already did that above.
5100 update_download_tabs(t);
5102 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5103 g_free(page);
5105 return (0);
5109 search(struct tab *t, struct karg *args)
5111 gboolean d;
5113 if (t == NULL || args == NULL) {
5114 show_oops(NULL, "search invalid parameters");
5115 return (1);
5118 switch (args->i) {
5119 case XT_SEARCH_NEXT:
5120 d = t->search_forward;
5121 break;
5122 case XT_SEARCH_PREV:
5123 d = !t->search_forward;
5124 break;
5125 default:
5126 return (XT_CB_PASSTHROUGH);
5129 if (t->search_text == NULL) {
5130 if (global_search == NULL)
5131 return (XT_CB_PASSTHROUGH);
5132 else {
5133 d = t->search_forward = TRUE;
5134 t->search_text = g_strdup(global_search);
5135 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5136 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5140 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5141 t->tab_id, args->i, t->search_forward, t->search_text);
5143 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5145 return (XT_CB_HANDLED);
5148 struct settings_args {
5149 char **body;
5150 int i;
5153 void
5154 print_setting(struct settings *s, char *val, void *cb_args)
5156 char *tmp, *color;
5157 struct settings_args *sa = cb_args;
5159 if (sa == NULL)
5160 return;
5162 if (s->flags & XT_SF_RUNTIME)
5163 color = "#22cc22";
5164 else
5165 color = "#cccccc";
5167 tmp = *sa->body;
5168 *sa->body = g_strdup_printf(
5169 "%s\n<tr>"
5170 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5171 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5172 *sa->body,
5173 color,
5174 s->name,
5175 color,
5178 g_free(tmp);
5179 sa->i++;
5183 set_show(struct tab *t, struct karg *args)
5185 char *body, *page, *tmp;
5186 int i = 1;
5187 struct settings_args sa;
5189 bzero(&sa, sizeof sa);
5190 sa.body = &body;
5192 /* body */
5193 body = g_strdup_printf("<div align='center'><table><tr>"
5194 "<th align='left'>Setting</th>"
5195 "<th align='left'>Value</th></tr>\n");
5197 settings_walk(print_setting, &sa);
5198 i = sa.i;
5200 /* small message if there are none */
5201 if (i == 1) {
5202 tmp = body;
5203 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5204 "colspan='2'>No settings</td></tr>\n", body);
5205 g_free(tmp);
5208 tmp = body;
5209 body = g_strdup_printf("%s</table></div>", body);
5210 g_free(tmp);
5212 page = get_html_page("Settings", body, "", 0);
5214 g_free(body);
5216 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5218 g_free(page);
5220 return (XT_CB_PASSTHROUGH);
5224 set(struct tab *t, struct karg *args)
5226 char *p, *val;
5227 int i;
5229 if (args == NULL || args->s == NULL)
5230 return (set_show(t, args));
5232 /* strip spaces */
5233 p = g_strstrip(args->s);
5235 if (strlen(p) == 0)
5236 return (set_show(t, args));
5238 /* we got some sort of string */
5239 val = g_strrstr(p, "=");
5240 if (val) {
5241 *val++ = '\0';
5242 val = g_strchomp(val);
5243 p = g_strchomp(p);
5245 for (i = 0; i < LENGTH(rs); i++) {
5246 if (strcmp(rs[i].name, p))
5247 continue;
5249 if (rs[i].activate) {
5250 if (rs[i].activate(val))
5251 show_oops(t, "%s invalid value %s",
5252 p, val);
5253 else
5254 show_oops(t, ":set %s = %s", p, val);
5255 goto done;
5256 } else {
5257 show_oops(t, "not a runtime option: %s", p);
5258 goto done;
5261 show_oops(t, "unknown option: %s", p);
5262 } else {
5263 p = g_strchomp(p);
5265 for (i = 0; i < LENGTH(rs); i++) {
5266 if (strcmp(rs[i].name, p))
5267 continue;
5269 /* XXX this could use some cleanup */
5270 switch (rs[i].type) {
5271 case XT_S_INT:
5272 if (rs[i].ival)
5273 show_oops(t, "%s = %d",
5274 rs[i].name, *rs[i].ival);
5275 else if (rs[i].s && rs[i].s->get)
5276 show_oops(t, "%s = %s",
5277 rs[i].name,
5278 rs[i].s->get(&rs[i]));
5279 else if (rs[i].s && rs[i].s->get == NULL)
5280 show_oops(t, "%s = ...", rs[i].name);
5281 else
5282 show_oops(t, "%s = ", rs[i].name);
5283 break;
5284 case XT_S_FLOAT:
5285 if (rs[i].fval)
5286 show_oops(t, "%s = %f",
5287 rs[i].name, *rs[i].fval);
5288 else if (rs[i].s && rs[i].s->get)
5289 show_oops(t, "%s = %s",
5290 rs[i].name,
5291 rs[i].s->get(&rs[i]));
5292 else if (rs[i].s && rs[i].s->get == NULL)
5293 show_oops(t, "%s = ...", rs[i].name);
5294 else
5295 show_oops(t, "%s = ", rs[i].name);
5296 break;
5297 case XT_S_STR:
5298 if (rs[i].sval && *rs[i].sval)
5299 show_oops(t, "%s = %s",
5300 rs[i].name, *rs[i].sval);
5301 else if (rs[i].s && rs[i].s->get)
5302 show_oops(t, "%s = %s",
5303 rs[i].name,
5304 rs[i].s->get(&rs[i]));
5305 else if (rs[i].s && rs[i].s->get == NULL)
5306 show_oops(t, "%s = ...", rs[i].name);
5307 else
5308 show_oops(t, "%s = ", rs[i].name);
5309 break;
5310 default:
5311 show_oops(t, "unknown type for %s", rs[i].name);
5312 goto done;
5315 goto done;
5317 show_oops(t, "unknown option: %s", p);
5319 done:
5320 return (XT_CB_PASSTHROUGH);
5324 session_save(struct tab *t, char *filename)
5326 struct karg a;
5327 int rv = 1;
5328 struct session *s;
5330 if (strlen(filename) == 0)
5331 goto done;
5333 if (filename[0] == '.' || filename[0] == '/')
5334 goto done;
5336 a.s = filename;
5337 if (save_tabs(t, &a))
5338 goto done;
5339 strlcpy(named_session, filename, sizeof named_session);
5341 /* add the new session to the list of sessions */
5342 s = g_malloc(sizeof(struct session));
5343 s->name = g_strdup(filename);
5344 TAILQ_INSERT_TAIL(&sessions, s, entry);
5346 rv = 0;
5347 done:
5348 return (rv);
5352 session_open(struct tab *t, char *filename)
5354 struct karg a;
5355 int rv = 1;
5357 if (strlen(filename) == 0)
5358 goto done;
5360 if (filename[0] == '.' || filename[0] == '/')
5361 goto done;
5363 a.s = filename;
5364 a.i = XT_SES_CLOSETABS;
5365 if (open_tabs(t, &a))
5366 goto done;
5368 strlcpy(named_session, filename, sizeof named_session);
5370 rv = 0;
5371 done:
5372 return (rv);
5376 session_delete(struct tab *t, char *filename)
5378 char file[PATH_MAX];
5379 int rv = 1;
5380 struct session *s;
5382 if (strlen(filename) == 0)
5383 goto done;
5385 if (filename[0] == '.' || filename[0] == '/')
5386 goto done;
5388 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5389 if (unlink(file))
5390 goto done;
5392 if (!strcmp(filename, named_session))
5393 strlcpy(named_session, XT_SAVED_TABS_FILE,
5394 sizeof named_session);
5396 /* remove session from sessions list */
5397 TAILQ_FOREACH(s, &sessions, entry) {
5398 if (!strcmp(s->name, filename))
5399 break;
5401 if (s == NULL)
5402 goto done;
5403 TAILQ_REMOVE(&sessions, s, entry);
5404 g_free((gpointer) s->name);
5405 g_free(s);
5407 rv = 0;
5408 done:
5409 return (rv);
5413 session_cmd(struct tab *t, struct karg *args)
5415 char *filename = args->s;
5417 if (t == NULL)
5418 return (1);
5420 if (args->i & XT_SHOW)
5421 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5422 XT_SAVED_TABS_FILE : named_session);
5423 else if (args->i & XT_SAVE) {
5424 if (session_save(t, filename)) {
5425 show_oops(t, "Can't save session: %s",
5426 filename ? filename : "INVALID");
5427 goto done;
5429 } else if (args->i & XT_OPEN) {
5430 if (session_open(t, filename)) {
5431 show_oops(t, "Can't open session: %s",
5432 filename ? filename : "INVALID");
5433 goto done;
5435 } else if (args->i & XT_DELETE) {
5436 if (session_delete(t, filename)) {
5437 show_oops(t, "Can't delete session: %s",
5438 filename ? filename : "INVALID");
5439 goto done;
5442 done:
5443 return (XT_CB_PASSTHROUGH);
5447 * Make a hardcopy of the page
5450 print_page(struct tab *t, struct karg *args)
5452 WebKitWebFrame *frame;
5453 GtkPageSetup *ps;
5454 GtkPrintOperation *op;
5455 GtkPrintOperationAction action;
5456 GtkPrintOperationResult print_res;
5457 GError *g_err = NULL;
5458 int marg_l, marg_r, marg_t, marg_b;
5460 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5462 ps = gtk_page_setup_new();
5463 op = gtk_print_operation_new();
5464 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5465 frame = webkit_web_view_get_main_frame(t->wv);
5467 /* the default margins are too small, so we will bump them */
5468 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5469 XT_PRINT_EXTRA_MARGIN;
5470 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5471 XT_PRINT_EXTRA_MARGIN;
5472 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5473 XT_PRINT_EXTRA_MARGIN;
5474 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5475 XT_PRINT_EXTRA_MARGIN;
5477 /* set margins */
5478 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5479 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5480 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5481 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5483 gtk_print_operation_set_default_page_setup(op, ps);
5485 /* this appears to free 'op' and 'ps' */
5486 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5488 /* check it worked */
5489 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5490 show_oops(NULL, "can't print: %s", g_err->message);
5491 g_error_free (g_err);
5492 return (1);
5495 return (0);
5499 go_home(struct tab *t, struct karg *args)
5501 load_uri(t, home);
5502 return (0);
5506 set_encoding(struct tab *t, struct karg *args)
5508 const gchar *e;
5510 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5511 e = webkit_web_view_get_custom_encoding(t->wv);
5512 if (e == NULL)
5513 e = webkit_web_view_get_encoding(t->wv);
5514 show_oops(t, "encoding: %s", e ? e : "N/A");
5515 } else
5516 webkit_web_view_set_custom_encoding(t->wv, args->s);
5518 return (0);
5522 restart(struct tab *t, struct karg *args)
5524 struct karg a;
5526 a.s = XT_RESTART_TABS_FILE;
5527 save_tabs(t, &a);
5528 execvp(start_argv[0], start_argv);
5529 /* NOTREACHED */
5531 return (0);
5534 #define CTRL GDK_CONTROL_MASK
5535 #define MOD1 GDK_MOD1_MASK
5536 #define SHFT GDK_SHIFT_MASK
5538 /* inherent to GTK not all keys will be caught at all times */
5539 /* XXX sort key bindings */
5540 struct key_binding {
5541 char *cmd;
5542 guint mask;
5543 guint use_in_entry;
5544 guint key;
5545 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5546 } keys[] = {
5547 { "cookiejar", MOD1, 0, GDK_j },
5548 { "downloadmgr", MOD1, 0, GDK_d },
5549 { "history", MOD1, 0, GDK_h },
5550 { "print", CTRL, 0, GDK_p },
5551 { "search", 0, 0, GDK_slash },
5552 { "searchb", 0, 0, GDK_question },
5553 { "statustoggle", CTRL, 0, GDK_n },
5554 { "command", 0, 0, GDK_colon },
5555 { "qa", CTRL, 0, GDK_q },
5556 { "restart", MOD1, 0, GDK_q },
5557 { "js toggle", CTRL, 0, GDK_j },
5558 { "cookie toggle", MOD1, 0, GDK_c },
5559 { "togglesrc", CTRL, 0, GDK_s },
5560 { "yankuri", 0, 0, GDK_y },
5561 { "pasteuricur", 0, 0, GDK_p },
5562 { "pasteurinew", 0, 0, GDK_P },
5563 { "toplevel toggle", 0, 0, GDK_F4 },
5564 { "help", 0, 0, GDK_F1 },
5565 { "run_script", MOD1, 0, GDK_r },
5567 /* search */
5568 { "searchnext", 0, 0, GDK_n },
5569 { "searchprevious", 0, 0, GDK_N },
5571 /* focus */
5572 { "focusaddress", 0, 0, GDK_F6 },
5573 { "focussearch", 0, 0, GDK_F7 },
5575 /* hinting */
5576 { "hinting", 0, 0, GDK_f },
5578 /* custom stylesheet */
5579 { "userstyle", 0, 0, GDK_i },
5581 /* navigation */
5582 { "goback", 0, 0, GDK_BackSpace },
5583 { "goback", MOD1, 0, GDK_Left },
5584 { "goforward", SHFT, 0, GDK_BackSpace },
5585 { "goforward", MOD1, 0, GDK_Right },
5586 { "reload", 0, 0, GDK_F5 },
5587 { "reload", CTRL, 0, GDK_r },
5588 { "reload", CTRL, 0, GDK_l },
5589 { "favorites", MOD1, 1, GDK_f },
5591 /* vertical movement */
5592 { "scrolldown", 0, 0, GDK_j },
5593 { "scrolldown", 0, 0, GDK_Down },
5594 { "scrollup", 0, 0, GDK_Up },
5595 { "scrollup", 0, 0, GDK_k },
5596 { "scrollbottom", 0, 0, GDK_G },
5597 { "scrollbottom", 0, 0, GDK_End },
5598 { "scrolltop", 0, 0, GDK_Home },
5599 { "scrollpagedown", 0, 0, GDK_space },
5600 { "scrollpagedown", CTRL, 0, GDK_f },
5601 { "scrollhalfdown", CTRL, 0, GDK_d },
5602 { "scrollpagedown", 0, 0, GDK_Page_Down },
5603 { "scrollpageup", 0, 0, GDK_Page_Up },
5604 { "scrollpageup", CTRL, 0, GDK_b },
5605 { "scrollhalfup", CTRL, 0, GDK_u },
5606 /* horizontal movement */
5607 { "scrollright", 0, 0, GDK_l },
5608 { "scrollright", 0, 0, GDK_Right },
5609 { "scrollleft", 0, 0, GDK_Left },
5610 { "scrollleft", 0, 0, GDK_h },
5611 { "scrollfarright", 0, 0, GDK_dollar },
5612 { "scrollfarleft", 0, 0, GDK_0 },
5614 /* tabs */
5615 { "tabnew", CTRL, 0, GDK_t },
5616 { "999tabnew", CTRL, 0, GDK_T },
5617 { "tabclose", CTRL, 1, GDK_w },
5618 { "tabundoclose", 0, 0, GDK_U },
5619 { "tabnext 1", CTRL, 0, GDK_1 },
5620 { "tabnext 2", CTRL, 0, GDK_2 },
5621 { "tabnext 3", CTRL, 0, GDK_3 },
5622 { "tabnext 4", CTRL, 0, GDK_4 },
5623 { "tabnext 5", CTRL, 0, GDK_5 },
5624 { "tabnext 6", CTRL, 0, GDK_6 },
5625 { "tabnext 7", CTRL, 0, GDK_7 },
5626 { "tabnext 8", CTRL, 0, GDK_8 },
5627 { "tabnext 9", CTRL, 0, GDK_9 },
5628 { "tabfirst", CTRL, 0, GDK_less },
5629 { "tablast", CTRL, 0, GDK_greater },
5630 { "tabprevious", CTRL, 0, GDK_Left },
5631 { "tabnext", CTRL, 0, GDK_Right },
5632 { "focusout", CTRL, 0, GDK_minus },
5633 { "focusin", CTRL, 0, GDK_plus },
5634 { "focusin", CTRL, 0, GDK_equal },
5635 { "focusreset", CTRL, 0, GDK_0 },
5637 /* command aliases (handy when -S flag is used) */
5638 { "promptopen", 0, 0, GDK_F9 },
5639 { "promptopencurrent", 0, 0, GDK_F10 },
5640 { "prompttabnew", 0, 0, GDK_F11 },
5641 { "prompttabnewcurrent",0, 0, GDK_F12 },
5643 TAILQ_HEAD(keybinding_list, key_binding);
5645 void
5646 walk_kb(struct settings *s,
5647 void (*cb)(struct settings *, char *, void *), void *cb_args)
5649 struct key_binding *k;
5650 char str[1024];
5652 if (s == NULL || cb == NULL) {
5653 show_oops(NULL, "walk_kb invalid parameters");
5654 return;
5657 TAILQ_FOREACH(k, &kbl, entry) {
5658 if (k->cmd == NULL)
5659 continue;
5660 str[0] = '\0';
5662 /* sanity */
5663 if (gdk_keyval_name(k->key) == NULL)
5664 continue;
5666 strlcat(str, k->cmd, sizeof str);
5667 strlcat(str, ",", sizeof str);
5669 if (k->mask & GDK_SHIFT_MASK)
5670 strlcat(str, "S-", sizeof str);
5671 if (k->mask & GDK_CONTROL_MASK)
5672 strlcat(str, "C-", sizeof str);
5673 if (k->mask & GDK_MOD1_MASK)
5674 strlcat(str, "M1-", sizeof str);
5675 if (k->mask & GDK_MOD2_MASK)
5676 strlcat(str, "M2-", sizeof str);
5677 if (k->mask & GDK_MOD3_MASK)
5678 strlcat(str, "M3-", sizeof str);
5679 if (k->mask & GDK_MOD4_MASK)
5680 strlcat(str, "M4-", sizeof str);
5681 if (k->mask & GDK_MOD5_MASK)
5682 strlcat(str, "M5-", sizeof str);
5684 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5685 cb(s, str, cb_args);
5689 void
5690 init_keybindings(void)
5692 int i;
5693 struct key_binding *k;
5695 for (i = 0; i < LENGTH(keys); i++) {
5696 k = g_malloc0(sizeof *k);
5697 k->cmd = keys[i].cmd;
5698 k->mask = keys[i].mask;
5699 k->use_in_entry = keys[i].use_in_entry;
5700 k->key = keys[i].key;
5701 TAILQ_INSERT_HEAD(&kbl, k, entry);
5703 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5704 k->cmd ? k->cmd : "unnamed key");
5708 void
5709 keybinding_clearall(void)
5711 struct key_binding *k, *next;
5713 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5714 next = TAILQ_NEXT(k, entry);
5715 if (k->cmd == NULL)
5716 continue;
5718 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5719 k->cmd ? k->cmd : "unnamed key");
5720 TAILQ_REMOVE(&kbl, k, entry);
5721 g_free(k);
5726 keybinding_add(char *cmd, char *key, int use_in_entry)
5728 struct key_binding *k;
5729 guint keyval, mask = 0;
5730 int i;
5732 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5734 /* Keys which are to be used in entry have been prefixed with an
5735 * exclamation mark. */
5736 if (use_in_entry)
5737 key++;
5739 /* find modifier keys */
5740 if (strstr(key, "S-"))
5741 mask |= GDK_SHIFT_MASK;
5742 if (strstr(key, "C-"))
5743 mask |= GDK_CONTROL_MASK;
5744 if (strstr(key, "M1-"))
5745 mask |= GDK_MOD1_MASK;
5746 if (strstr(key, "M2-"))
5747 mask |= GDK_MOD2_MASK;
5748 if (strstr(key, "M3-"))
5749 mask |= GDK_MOD3_MASK;
5750 if (strstr(key, "M4-"))
5751 mask |= GDK_MOD4_MASK;
5752 if (strstr(key, "M5-"))
5753 mask |= GDK_MOD5_MASK;
5755 /* find keyname */
5756 for (i = strlen(key) - 1; i > 0; i--)
5757 if (key[i] == '-')
5758 key = &key[i + 1];
5760 /* validate keyname */
5761 keyval = gdk_keyval_from_name(key);
5762 if (keyval == GDK_VoidSymbol) {
5763 warnx("invalid keybinding name %s", key);
5764 return (1);
5766 /* must run this test too, gtk+ doesn't handle 10 for example */
5767 if (gdk_keyval_name(keyval) == NULL) {
5768 warnx("invalid keybinding name %s", key);
5769 return (1);
5772 /* Remove eventual dupes. */
5773 TAILQ_FOREACH(k, &kbl, entry)
5774 if (k->key == keyval && k->mask == mask) {
5775 TAILQ_REMOVE(&kbl, k, entry);
5776 g_free(k);
5777 break;
5780 /* add keyname */
5781 k = g_malloc0(sizeof *k);
5782 k->cmd = g_strdup(cmd);
5783 k->mask = mask;
5784 k->use_in_entry = use_in_entry;
5785 k->key = keyval;
5787 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5788 k->cmd,
5789 k->mask,
5790 k->use_in_entry,
5791 k->key);
5792 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5793 k->cmd, gdk_keyval_name(keyval));
5795 TAILQ_INSERT_HEAD(&kbl, k, entry);
5797 return (0);
5801 add_kb(struct settings *s, char *entry)
5803 char *kb, *key;
5805 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5807 /* clearall is special */
5808 if (!strcmp(entry, "clearall")) {
5809 keybinding_clearall();
5810 return (0);
5813 kb = strstr(entry, ",");
5814 if (kb == NULL)
5815 return (1);
5816 *kb = '\0';
5817 key = kb + 1;
5819 return (keybinding_add(entry, key, key[0] == '!'));
5822 struct cmd {
5823 char *cmd;
5824 int level;
5825 int (*func)(struct tab *, struct karg *);
5826 int arg;
5827 int type;
5828 } cmds[] = {
5829 { "command", 0, command, ':', 0 },
5830 { "search", 0, command, '/', 0 },
5831 { "searchb", 0, command, '?', 0 },
5832 { "togglesrc", 0, toggle_src, 0, 0 },
5834 /* yanking and pasting */
5835 { "yankuri", 0, yank_uri, 0, 0 },
5836 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5837 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5838 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5840 /* search */
5841 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5842 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5844 /* focus */
5845 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5846 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5848 /* hinting */
5849 { "hinting", 0, hint, 0, 0 },
5851 /* custom stylesheet */
5852 { "userstyle", 0, userstyle, 0, 0 },
5854 /* navigation */
5855 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5856 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5857 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5859 /* vertical movement */
5860 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5861 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5862 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5863 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5864 { "1", 0, move, XT_MOVE_TOP, 0 },
5865 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5866 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5867 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5868 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5869 /* horizontal movement */
5870 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5871 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5872 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5873 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5875 { "favorites", 0, xtp_page_fl, 0, 0 },
5876 { "fav", 0, xtp_page_fl, 0, 0 },
5877 { "favadd", 0, add_favorite, 0, 0 },
5879 { "qall", 0, quit, 0, 0 },
5880 { "quitall", 0, quit, 0, 0 },
5881 { "w", 0, save_tabs, 0, 0 },
5882 { "wq", 0, save_tabs_and_quit, 0, 0 },
5883 { "help", 0, help, 0, 0 },
5884 { "about", 0, about, 0, 0 },
5885 { "stats", 0, stats, 0, 0 },
5886 { "version", 0, about, 0, 0 },
5888 /* js command */
5889 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5890 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5891 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5892 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5893 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5894 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5895 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5896 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5897 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5898 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5899 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5901 /* cookie command */
5902 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5903 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5904 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5905 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5906 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5907 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5908 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5909 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5910 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5911 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5912 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5914 /* toplevel (domain) command */
5915 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5916 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5918 /* cookie jar */
5919 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5921 /* cert command */
5922 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5923 { "save", 1, cert_cmd, XT_SAVE, 0 },
5924 { "show", 1, cert_cmd, XT_SHOW, 0 },
5926 { "ca", 0, ca_cmd, 0, 0 },
5927 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5928 { "dl", 0, xtp_page_dl, 0, 0 },
5929 { "h", 0, xtp_page_hl, 0, 0 },
5930 { "history", 0, xtp_page_hl, 0, 0 },
5931 { "home", 0, go_home, 0, 0 },
5932 { "restart", 0, restart, 0, 0 },
5933 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5934 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5935 { "statustoggle", 0, statustoggle, 0, 0 },
5936 { "run_script", 0, run_page_script, 0, XT_USERARG },
5938 { "print", 0, print_page, 0, 0 },
5940 /* tabs */
5941 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5942 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5943 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5944 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5945 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5946 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5947 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5948 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5949 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5950 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5951 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5952 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5953 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5954 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5955 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5956 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5957 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5958 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5959 { "buffers", 0, buffers, 0, 0 },
5960 { "ls", 0, buffers, 0, 0 },
5961 { "tabs", 0, buffers, 0, 0 },
5962 { "encoding", 0, set_encoding, 0, XT_USERARG },
5964 /* command aliases (handy when -S flag is used) */
5965 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5966 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5967 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5968 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5970 /* settings */
5971 { "set", 0, set, 0, XT_SETARG },
5973 { "fullscreen", 0, fullscreen, 0, 0 },
5974 { "f", 0, fullscreen, 0, 0 },
5976 /* sessions */
5977 { "session", 0, session_cmd, XT_SHOW, 0 },
5978 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5979 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5980 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5981 { "show", 1, session_cmd, XT_SHOW, 0 },
5984 struct {
5985 int index;
5986 int len;
5987 gchar *list[256];
5988 } cmd_status = {-1, 0};
5990 gboolean
5991 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5994 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5995 btn_down = 0;
5997 return (FALSE);
6000 gboolean
6001 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6003 struct karg a;
6005 hide_oops(t);
6006 hide_buffers(t);
6008 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6009 btn_down = 1;
6010 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6011 /* go backward */
6012 a.i = XT_NAV_BACK;
6013 navaction(t, &a);
6015 return (TRUE);
6016 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6017 /* go forward */
6018 a.i = XT_NAV_FORWARD;
6019 navaction(t, &a);
6021 return (TRUE);
6024 return (FALSE);
6027 gboolean
6028 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6030 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6032 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6033 delete_tab(t);
6035 return (FALSE);
6039 * cancel, remove, etc. downloads
6041 void
6042 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6044 struct download find, *d = NULL;
6046 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6048 /* some commands require a valid download id */
6049 if (cmd != XT_XTP_DL_LIST) {
6050 /* lookup download in question */
6051 find.id = id;
6052 d = RB_FIND(download_list, &downloads, &find);
6054 if (d == NULL) {
6055 show_oops(t, "%s: no such download", __func__);
6056 return;
6060 /* decide what to do */
6061 switch (cmd) {
6062 case XT_XTP_DL_CANCEL:
6063 webkit_download_cancel(d->download);
6064 break;
6065 case XT_XTP_DL_REMOVE:
6066 webkit_download_cancel(d->download); /* just incase */
6067 g_object_unref(d->download);
6068 RB_REMOVE(download_list, &downloads, d);
6069 break;
6070 case XT_XTP_DL_LIST:
6071 /* Nothing */
6072 break;
6073 default:
6074 show_oops(t, "%s: unknown command", __func__);
6075 break;
6077 xtp_page_dl(t, NULL);
6081 * Actions on history, only does one thing for now, but
6082 * we provide the function for future actions
6084 void
6085 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6087 struct history *h, *next;
6088 int i = 1;
6090 switch (cmd) {
6091 case XT_XTP_HL_REMOVE:
6092 /* walk backwards, as listed in reverse */
6093 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6094 next = RB_PREV(history_list, &hl, h);
6095 if (id == i) {
6096 RB_REMOVE(history_list, &hl, h);
6097 g_free((gpointer) h->title);
6098 g_free((gpointer) h->uri);
6099 g_free(h);
6100 break;
6102 i++;
6104 break;
6105 case XT_XTP_HL_LIST:
6106 /* Nothing - just xtp_page_hl() below */
6107 break;
6108 default:
6109 show_oops(t, "%s: unknown command", __func__);
6110 break;
6113 xtp_page_hl(t, NULL);
6116 /* remove a favorite */
6117 void
6118 remove_favorite(struct tab *t, int index)
6120 char file[PATH_MAX], *title, *uri = NULL;
6121 char *new_favs, *tmp;
6122 FILE *f;
6123 int i;
6124 size_t len, lineno;
6126 /* open favorites */
6127 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6129 if ((f = fopen(file, "r")) == NULL) {
6130 show_oops(t, "%s: can't open favorites: %s",
6131 __func__, strerror(errno));
6132 return;
6135 /* build a string which will become the new favroites file */
6136 new_favs = g_strdup("");
6138 for (i = 1;;) {
6139 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6140 if (feof(f) || ferror(f))
6141 break;
6142 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6143 if (len == 0) {
6144 free(title);
6145 title = NULL;
6146 continue;
6149 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6150 if (feof(f) || ferror(f)) {
6151 show_oops(t, "%s: can't parse favorites %s",
6152 __func__, strerror(errno));
6153 goto clean;
6157 /* as long as this isn't the one we are deleting add to file */
6158 if (i != index) {
6159 tmp = new_favs;
6160 new_favs = g_strdup_printf("%s%s\n%s\n",
6161 new_favs, title, uri);
6162 g_free(tmp);
6165 free(uri);
6166 uri = NULL;
6167 free(title);
6168 title = NULL;
6169 i++;
6171 fclose(f);
6173 /* write back new favorites file */
6174 if ((f = fopen(file, "w")) == NULL) {
6175 show_oops(t, "%s: can't open favorites: %s",
6176 __func__, strerror(errno));
6177 goto clean;
6180 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
6181 show_oops(t, "%s: can't fwrite"); /* shut gcc up */
6182 fclose(f);
6184 clean:
6185 if (uri)
6186 free(uri);
6187 if (title)
6188 free(title);
6190 g_free(new_favs);
6193 void
6194 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6196 switch (cmd) {
6197 case XT_XTP_FL_LIST:
6198 /* nothing, just the below call to xtp_page_fl() */
6199 break;
6200 case XT_XTP_FL_REMOVE:
6201 remove_favorite(t, arg);
6202 break;
6203 default:
6204 show_oops(t, "%s: invalid favorites command", __func__);
6205 break;
6208 xtp_page_fl(t, NULL);
6211 void
6212 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6214 switch (cmd) {
6215 case XT_XTP_CL_LIST:
6216 /* nothing, just xtp_page_cl() */
6217 break;
6218 case XT_XTP_CL_REMOVE:
6219 remove_cookie(arg);
6220 break;
6221 default:
6222 show_oops(t, "%s: unknown cookie xtp command", __func__);
6223 break;
6226 xtp_page_cl(t, NULL);
6229 /* link an XTP class to it's session key and handler function */
6230 struct xtp_despatch {
6231 uint8_t xtp_class;
6232 char **session_key;
6233 void (*handle_func)(struct tab *, uint8_t, int);
6236 struct xtp_despatch xtp_despatches[] = {
6237 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6238 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6239 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6240 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6241 { XT_XTP_INVALID, NULL, NULL }
6245 * is the url xtp protocol? (xxxt://)
6246 * if so, parse and despatch correct bahvior
6249 parse_xtp_url(struct tab *t, const char *url)
6251 char *dup = NULL, *p, *last = NULL;
6252 uint8_t n_tokens = 0;
6253 char *tokens[4] = {NULL, NULL, NULL, ""};
6254 struct xtp_despatch *dsp, *dsp_match = NULL;
6255 uint8_t req_class;
6256 int ret = FALSE;
6259 * tokens array meaning:
6260 * tokens[0] = class
6261 * tokens[1] = session key
6262 * tokens[2] = action
6263 * tokens[3] = optional argument
6266 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6268 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6269 goto clean;
6271 dup = g_strdup(url + strlen(XT_XTP_STR));
6273 /* split out the url */
6274 for ((p = strtok_r(dup, "/", &last)); p;
6275 (p = strtok_r(NULL, "/", &last))) {
6276 if (n_tokens < 4)
6277 tokens[n_tokens++] = p;
6280 /* should be atleast three fields 'class/seskey/command/arg' */
6281 if (n_tokens < 3)
6282 goto clean;
6284 dsp = xtp_despatches;
6285 req_class = atoi(tokens[0]);
6286 while (dsp->xtp_class) {
6287 if (dsp->xtp_class == req_class) {
6288 dsp_match = dsp;
6289 break;
6291 dsp++;
6294 /* did we find one atall? */
6295 if (dsp_match == NULL) {
6296 show_oops(t, "%s: no matching xtp despatch found", __func__);
6297 goto clean;
6300 /* check session key and call despatch function */
6301 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6302 ret = TRUE; /* all is well, this was a valid xtp request */
6303 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6306 clean:
6307 if (dup)
6308 g_free(dup);
6310 return (ret);
6315 void
6316 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6318 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6320 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6322 if (t == NULL) {
6323 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6324 return;
6327 if (uri == NULL) {
6328 show_oops(t, "activate_uri_entry_cb no uri");
6329 return;
6332 uri += strspn(uri, "\t ");
6334 /* if xxxt:// treat specially */
6335 if (parse_xtp_url(t, uri))
6336 return;
6338 /* otherwise continue to load page normally */
6339 load_uri(t, (gchar *)uri);
6340 focus_webview(t);
6343 void
6344 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6346 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6347 char *newuri = NULL;
6348 gchar *enc_search;
6350 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6352 if (t == NULL) {
6353 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6354 return;
6357 if (search_string == NULL) {
6358 show_oops(t, "no search_string");
6359 return;
6362 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6364 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6365 newuri = g_strdup_printf(search_string, enc_search);
6366 g_free(enc_search);
6368 marks_clear(t);
6369 webkit_web_view_load_uri(t->wv, newuri);
6370 focus_webview(t);
6372 if (newuri)
6373 g_free(newuri);
6376 void
6377 check_and_set_cookie(const gchar *uri, struct tab *t)
6379 struct domain *d = NULL;
6380 int es = 0;
6382 if (uri == NULL || t == NULL)
6383 return;
6385 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6386 es = 0;
6387 else
6388 es = 1;
6390 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6391 es ? "enable" : "disable", uri);
6393 g_object_set(G_OBJECT(t->settings),
6394 "enable-html5-local-storage", es, (char *)NULL);
6395 webkit_web_view_set_settings(t->wv, t->settings);
6398 void
6399 check_and_set_js(const gchar *uri, struct tab *t)
6401 struct domain *d = NULL;
6402 int es = 0;
6404 if (uri == NULL || t == NULL)
6405 return;
6407 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6408 es = 0;
6409 else
6410 es = 1;
6412 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6413 es ? "enable" : "disable", uri);
6415 g_object_set(G_OBJECT(t->settings),
6416 "enable-scripts", es, (char *)NULL);
6417 g_object_set(G_OBJECT(t->settings),
6418 "javascript-can-open-windows-automatically", es, (char *)NULL);
6419 webkit_web_view_set_settings(t->wv, t->settings);
6421 button_set_stockid(t->js_toggle,
6422 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6425 void
6426 color_address_bar(gpointer p)
6428 GdkColor color;
6429 struct tab *tt, *t = p;
6430 gchar *col_str = XT_COLOR_WHITE;
6431 const gchar *uri, *u = NULL, *error_str = NULL;
6433 #ifdef USE_THREADS
6434 gdk_threads_enter();
6435 #endif
6436 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6438 /* make sure t still exists */
6439 if (t == NULL)
6440 return;
6441 TAILQ_FOREACH(tt, &tabs, entry)
6442 if (t == tt)
6443 break;
6444 if (t != tt)
6445 goto done;
6447 if ((uri = get_uri(t)) == NULL)
6448 goto white;
6449 u = g_strdup(uri);
6451 #ifdef USE_THREADS
6452 gdk_threads_leave();
6453 #endif
6455 col_str = XT_COLOR_YELLOW;
6456 switch (load_compare_cert(u, &error_str)) {
6457 case CERT_LOCAL:
6458 col_str = XT_COLOR_BLUE;
6459 break;
6460 case CERT_TRUSTED:
6461 col_str = XT_COLOR_GREEN;
6462 break;
6463 case CERT_UNTRUSTED:
6464 col_str = XT_COLOR_YELLOW;
6465 break;
6466 case CERT_BAD:
6467 col_str = XT_COLOR_RED;
6468 break;
6471 #ifdef USE_THREADS
6472 gdk_threads_enter();
6473 #endif
6474 /* make sure t isn't deleted */
6475 TAILQ_FOREACH(tt, &tabs, entry)
6476 if (t == tt)
6477 break;
6478 if (t != tt)
6479 goto done;
6481 #ifdef USE_THREADS
6482 /* test to see if the user navigated away and canceled the thread */
6483 if (t->thread != g_thread_self())
6484 goto done;
6485 #endif
6486 white:
6487 gdk_color_parse(col_str, &color);
6488 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6490 if (!strcmp(col_str, XT_COLOR_WHITE))
6491 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6492 else
6493 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6495 if (error_str && error_str[0] != '\0')
6496 show_oops(t, "%s", error_str);
6497 #ifdef USE_THREADS
6498 t->thread = NULL;
6499 #endif
6500 done:
6501 /* t is invalid at this point */
6502 if (u)
6503 g_free((gpointer)u);
6504 #ifdef USE_THREADS
6505 gdk_threads_leave();
6506 #endif
6509 void
6510 show_ca_status(struct tab *t, const char *uri)
6512 GdkColor color;
6513 gchar *col_str = XT_COLOR_WHITE;
6515 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6516 ssl_strict_certs, ssl_ca_file, uri);
6518 if (t == NULL)
6519 return;
6521 if (uri == NULL)
6522 goto done;
6523 if (ssl_ca_file == NULL) {
6524 if (g_str_has_prefix(uri, "http://"))
6525 goto done;
6526 if (g_str_has_prefix(uri, "https://")) {
6527 col_str = XT_COLOR_RED;
6528 goto done;
6530 return;
6532 if (g_str_has_prefix(uri, "http://") ||
6533 !g_str_has_prefix(uri, "https://"))
6534 goto done;
6535 #ifdef USE_THREADS
6537 * It is not necessary to see if the thread is already running.
6538 * If the thread is in progress setting it to something else aborts it
6539 * on the way out.
6542 /* thread the coloring of the address bar */
6543 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6544 #else
6545 color_address_bar(t);
6546 #endif
6547 return;
6549 done:
6550 if (col_str) {
6551 gdk_color_parse(col_str, &color);
6552 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6554 if (!strcmp(col_str, XT_COLOR_WHITE))
6555 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6556 else
6557 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6561 void
6562 free_favicon(struct tab *t)
6564 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6565 __func__, t->icon_download, t->icon_request);
6567 if (t->icon_request)
6568 g_object_unref(t->icon_request);
6569 if (t->icon_dest_uri)
6570 g_free(t->icon_dest_uri);
6572 t->icon_request = NULL;
6573 t->icon_dest_uri = NULL;
6576 void
6577 xt_icon_from_name(struct tab *t, gchar *name)
6579 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6580 GTK_ENTRY_ICON_PRIMARY, "text-html");
6581 if (show_url == 0)
6582 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6583 GTK_ENTRY_ICON_PRIMARY, "text-html");
6584 else
6585 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6586 GTK_ENTRY_ICON_PRIMARY, NULL);
6589 void
6590 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6592 GdkPixbuf *pb_scaled;
6594 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6595 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6596 GDK_INTERP_BILINEAR);
6597 else
6598 pb_scaled = pb;
6600 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6601 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6602 if (show_url == 0)
6603 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6604 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6605 else
6606 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6607 GTK_ENTRY_ICON_PRIMARY, NULL);
6609 if (pb_scaled != pb)
6610 g_object_unref(pb_scaled);
6613 void
6614 xt_icon_from_file(struct tab *t, char *file)
6616 GdkPixbuf *pb;
6618 if (g_str_has_prefix(file, "file://"))
6619 file += strlen("file://");
6621 pb = gdk_pixbuf_new_from_file(file, NULL);
6622 if (pb) {
6623 xt_icon_from_pixbuf(t, pb);
6624 g_object_unref(pb);
6625 } else
6626 xt_icon_from_name(t, "text-html");
6629 gboolean
6630 is_valid_icon(char *file)
6632 gboolean valid = 0;
6633 const char *mime_type;
6634 GFileInfo *fi;
6635 GFile *gf;
6637 gf = g_file_new_for_path(file);
6638 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6639 NULL, NULL);
6640 mime_type = g_file_info_get_content_type(fi);
6641 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6642 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6643 g_strcmp0(mime_type, "image/png") == 0 ||
6644 g_strcmp0(mime_type, "image/gif") == 0 ||
6645 g_strcmp0(mime_type, "application/octet-stream") == 0;
6646 g_object_unref(fi);
6647 g_object_unref(gf);
6649 return (valid);
6652 void
6653 set_favicon_from_file(struct tab *t, char *file)
6655 struct stat sb;
6657 if (t == NULL || file == NULL)
6658 return;
6660 if (g_str_has_prefix(file, "file://"))
6661 file += strlen("file://");
6662 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6664 if (!stat(file, &sb)) {
6665 if (sb.st_size == 0 || !is_valid_icon(file)) {
6666 /* corrupt icon so trash it */
6667 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6668 __func__, file);
6669 unlink(file);
6670 /* no need to set icon to default here */
6671 return;
6674 xt_icon_from_file(t, file);
6677 void
6678 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6679 WebKitWebView *wv)
6681 WebKitDownloadStatus status = webkit_download_get_status(download);
6682 struct tab *tt = NULL, *t = NULL;
6685 * find the webview instead of passing in the tab as it could have been
6686 * deleted from underneath us.
6688 TAILQ_FOREACH(tt, &tabs, entry) {
6689 if (tt->wv == wv) {
6690 t = tt;
6691 break;
6694 if (t == NULL)
6695 return;
6697 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6698 __func__, t->tab_id, status);
6700 switch (status) {
6701 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6702 /* -1 */
6703 t->icon_download = NULL;
6704 free_favicon(t);
6705 break;
6706 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6707 /* 0 */
6708 break;
6709 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6710 /* 1 */
6711 break;
6712 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6713 /* 2 */
6714 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6715 __func__, t->tab_id);
6716 t->icon_download = NULL;
6717 free_favicon(t);
6718 break;
6719 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6720 /* 3 */
6722 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6723 __func__, t->icon_dest_uri);
6724 set_favicon_from_file(t, t->icon_dest_uri);
6725 /* these will be freed post callback */
6726 t->icon_request = NULL;
6727 t->icon_download = NULL;
6728 break;
6729 default:
6730 break;
6734 void
6735 abort_favicon_download(struct tab *t)
6737 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6739 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6740 if (t->icon_download) {
6741 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6742 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6743 webkit_download_cancel(t->icon_download);
6744 t->icon_download = NULL;
6745 } else
6746 free_favicon(t);
6747 #endif
6749 xt_icon_from_name(t, "text-html");
6752 void
6753 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6755 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6757 if (uri == NULL || t == NULL)
6758 return;
6760 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6761 /* take icon from WebKitIconDatabase */
6762 GdkPixbuf *pb;
6764 pb = webkit_web_view_get_icon_pixbuf(wv);
6765 if (pb) {
6766 xt_icon_from_pixbuf(t, pb);
6767 g_object_unref(pb);
6768 } else
6769 xt_icon_from_name(t, "text-html");
6770 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6771 /* download icon to cache dir */
6772 gchar *name_hash, file[PATH_MAX];
6773 struct stat sb;
6775 if (t->icon_request) {
6776 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6777 return;
6780 /* check to see if we got the icon in cache */
6781 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6782 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6783 g_free(name_hash);
6785 if (!stat(file, &sb)) {
6786 if (sb.st_size > 0) {
6787 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6788 __func__, file);
6789 set_favicon_from_file(t, file);
6790 return;
6793 /* corrupt icon so trash it */
6794 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6795 __func__, file);
6796 unlink(file);
6799 /* create download for icon */
6800 t->icon_request = webkit_network_request_new(uri);
6801 if (t->icon_request == NULL) {
6802 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6803 __func__, uri);
6804 return;
6807 t->icon_download = webkit_download_new(t->icon_request);
6808 if (t->icon_download == NULL)
6809 return;
6811 /* we have to free icon_dest_uri later */
6812 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6813 webkit_download_set_destination_uri(t->icon_download,
6814 t->icon_dest_uri);
6816 if (webkit_download_get_status(t->icon_download) ==
6817 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6818 g_object_unref(t->icon_request);
6819 g_free(t->icon_dest_uri);
6820 t->icon_request = NULL;
6821 t->icon_dest_uri = NULL;
6822 return;
6825 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6826 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6828 webkit_download_start(t->icon_download);
6829 #endif
6832 void
6833 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6835 const gchar *uri = NULL, *title = NULL;
6836 struct history *h, find;
6837 struct karg a;
6838 GdkColor color;
6840 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6841 webkit_web_view_get_load_status(wview),
6842 get_uri(t) ? get_uri(t) : "NOTHING");
6844 if (t == NULL) {
6845 show_oops(NULL, "notify_load_status_cb invalid parameters");
6846 return;
6849 switch (webkit_web_view_get_load_status(wview)) {
6850 case WEBKIT_LOAD_PROVISIONAL:
6851 /* 0 */
6852 abort_favicon_download(t);
6853 #if GTK_CHECK_VERSION(2, 20, 0)
6854 gtk_widget_show(t->spinner);
6855 gtk_spinner_start(GTK_SPINNER(t->spinner));
6856 #endif
6857 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6859 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6861 /* assume we are a new address */
6862 gdk_color_parse("white", &color);
6863 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6864 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6866 /* take focus if we are visible */
6867 focus_webview(t);
6868 t->focus_wv = 1;
6869 #ifdef USE_THREAD
6870 /* kill color thread */
6871 t->thread = NULL;
6872 #endif
6873 break;
6875 case WEBKIT_LOAD_COMMITTED:
6876 /* 1 */
6877 uri = get_uri(t);
6878 if (uri == NULL)
6879 return;
6880 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6882 if (t->status) {
6883 g_free(t->status);
6884 t->status = NULL;
6886 set_status(t, (char *)uri, XT_STATUS_LOADING);
6888 /* check if js white listing is enabled */
6889 if (enable_cookie_whitelist)
6890 check_and_set_cookie(uri, t);
6891 if (enable_js_whitelist)
6892 check_and_set_js(uri, t);
6894 if (t->styled)
6895 apply_style(t);
6898 /* we know enough to autosave the session */
6899 if (session_autosave) {
6900 a.s = NULL;
6901 save_tabs(t, &a);
6904 show_ca_status(t, uri);
6905 break;
6907 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6908 /* 3 */
6909 break;
6911 case WEBKIT_LOAD_FINISHED:
6912 /* 2 */
6913 uri = get_uri(t);
6914 if (uri == NULL)
6915 return;
6917 if (!strncmp(uri, "http://", strlen("http://")) ||
6918 !strncmp(uri, "https://", strlen("https://")) ||
6919 !strncmp(uri, "file://", strlen("file://"))) {
6920 find.uri = uri;
6921 h = RB_FIND(history_list, &hl, &find);
6922 if (!h) {
6923 title = get_title(t, FALSE);
6924 h = g_malloc(sizeof *h);
6925 h->uri = g_strdup(uri);
6926 h->title = g_strdup(title);
6927 RB_INSERT(history_list, &hl, h);
6928 completion_add_uri(h->uri);
6929 update_history_tabs(NULL);
6933 set_status(t, (char *)uri, XT_STATUS_URI);
6934 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6935 case WEBKIT_LOAD_FAILED:
6936 /* 4 */
6937 #endif
6938 #if GTK_CHECK_VERSION(2, 20, 0)
6939 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6940 gtk_widget_hide(t->spinner);
6941 #endif
6942 default:
6943 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6944 break;
6947 if (t->item)
6948 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6949 else
6950 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6951 can_go_back_for_real(t));
6953 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6954 can_go_forward_for_real(t));
6957 void
6958 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6960 const gchar *title = NULL, *win_title = NULL;
6962 title = get_title(t, FALSE);
6963 win_title = get_title(t, TRUE);
6964 gtk_label_set_text(GTK_LABEL(t->label), title);
6965 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6966 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6967 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6970 void
6971 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6973 run_script(t, JS_HINTING);
6976 void
6977 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6979 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6980 progress == 100 ? 0 : (double)progress / 100);
6981 if (show_url == 0) {
6982 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6983 progress == 100 ? 0 : (double)progress / 100);
6986 update_statusbar_position(NULL, NULL);
6990 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6991 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6992 WebKitWebPolicyDecision *pd, struct tab *t)
6994 char *uri;
6995 WebKitWebNavigationReason reason;
6996 struct domain *d = NULL;
6998 if (t == NULL) {
6999 show_oops(NULL, "webview_npd_cb invalid parameters");
7000 return (FALSE);
7003 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
7004 t->ctrl_click,
7005 webkit_network_request_get_uri(request));
7007 uri = (char *)webkit_network_request_get_uri(request);
7009 /* if this is an xtp url, we don't load anything else */
7010 if (parse_xtp_url(t, uri))
7011 return (TRUE);
7013 if (t->ctrl_click) {
7014 t->ctrl_click = 0;
7015 create_new_tab(uri, NULL, ctrl_click_focus, -1);
7016 webkit_web_policy_decision_ignore(pd);
7017 return (TRUE); /* we made the decission */
7021 * This is a little hairy but it comes down to this:
7022 * when we run in whitelist mode we have to assist the browser in
7023 * opening the URL that it would have opened in a new tab.
7025 reason = webkit_web_navigation_action_get_reason(na);
7026 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7027 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7028 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7029 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7030 load_uri(t, uri);
7031 webkit_web_policy_decision_use(pd);
7032 return (TRUE); /* we made the decision */
7035 return (FALSE);
7038 WebKitWebView *
7039 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7041 struct tab *tt;
7042 struct domain *d = NULL;
7043 const gchar *uri;
7044 WebKitWebView *webview = NULL;
7046 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7047 webkit_web_view_get_uri(wv));
7049 if (tabless) {
7050 /* open in current tab */
7051 webview = t->wv;
7052 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7053 uri = webkit_web_view_get_uri(wv);
7054 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7055 return (NULL);
7057 tt = create_new_tab(NULL, NULL, 1, -1);
7058 webview = tt->wv;
7059 } else if (enable_scripts == 1) {
7060 tt = create_new_tab(NULL, NULL, 1, -1);
7061 webview = tt->wv;
7064 return (webview);
7067 gboolean
7068 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7070 const gchar *uri;
7071 struct domain *d = NULL;
7073 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7075 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7076 uri = webkit_web_view_get_uri(wv);
7077 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7078 return (FALSE);
7080 delete_tab(t);
7081 } else if (enable_scripts == 1)
7082 delete_tab(t);
7084 return (TRUE);
7088 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7090 /* we can not eat the event without throwing gtk off so defer it */
7092 /* catch middle click */
7093 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7094 t->ctrl_click = 1;
7095 goto done;
7098 /* catch ctrl click */
7099 if (e->type == GDK_BUTTON_RELEASE &&
7100 CLEAN(e->state) == GDK_CONTROL_MASK)
7101 t->ctrl_click = 1;
7102 else
7103 t->ctrl_click = 0;
7104 done:
7105 return (XT_CB_PASSTHROUGH);
7109 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7111 struct mime_type *m;
7113 m = find_mime_type(mime_type);
7114 if (m == NULL)
7115 return (1);
7116 if (m->mt_download)
7117 return (1);
7119 switch (fork()) {
7120 case -1:
7121 show_oops(t, "can't fork mime handler");
7122 return (1);
7123 /* NOTREACHED */
7124 case 0:
7125 break;
7126 default:
7127 return (0);
7130 /* child */
7131 execlp(m->mt_action, m->mt_action,
7132 webkit_network_request_get_uri(request), (void *)NULL);
7134 _exit(0);
7136 /* NOTREACHED */
7137 return (0);
7140 char *
7141 get_mime_type(const char *file)
7143 const gchar *m;
7144 char *mime_type = NULL;
7145 GFileInfo *fi;
7146 GFile *gf;
7148 if (g_str_has_prefix(file, "file://"))
7149 file += strlen("file://");
7151 gf = g_file_new_for_path(file);
7152 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7153 NULL, NULL);
7154 if ((m = g_file_info_get_content_type(fi)) != NULL)
7155 mime_type = g_strdup(m);
7156 g_object_unref(fi);
7157 g_object_unref(gf);
7159 return (mime_type);
7163 run_download_mimehandler(char *mime_type, char *file)
7165 struct mime_type *m;
7167 m = find_mime_type(mime_type);
7168 if (m == NULL)
7169 return (1);
7171 switch (fork()) {
7172 case -1:
7173 show_oops(NULL, "can't fork download mime handler");
7174 return (1);
7175 /* NOTREACHED */
7176 case 0:
7177 break;
7178 default:
7179 return (0);
7182 /* child */
7183 if (g_str_has_prefix(file, "file://"))
7184 file += strlen("file://");
7185 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7187 _exit(0);
7189 /* NOTREACHED */
7190 return (0);
7193 void
7194 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7195 WebKitWebView *wv)
7197 WebKitDownloadStatus status;
7198 const char *file = NULL;
7199 char *mime = NULL;
7201 if (download == NULL)
7202 return;
7203 status = webkit_download_get_status(download);
7204 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7205 return;
7207 file = webkit_download_get_destination_uri(download);
7208 if (file == NULL)
7209 return;
7210 mime = get_mime_type(file);
7211 if (mime == NULL)
7212 return;
7214 run_download_mimehandler((char *)mime, (char *)file);
7215 g_free(mime);
7219 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7220 WebKitNetworkRequest *request, char *mime_type,
7221 WebKitWebPolicyDecision *decision, struct tab *t)
7223 if (t == NULL) {
7224 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7225 return (FALSE);
7228 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7229 t->tab_id, mime_type);
7231 if (run_mimehandler(t, mime_type, request) == 0) {
7232 webkit_web_policy_decision_ignore(decision);
7233 focus_webview(t);
7234 return (TRUE);
7237 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7238 webkit_web_policy_decision_download(decision);
7239 return (TRUE);
7242 return (FALSE);
7246 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7247 struct tab *t)
7249 struct stat sb;
7250 const gchar *suggested_name;
7251 gchar *filename = NULL;
7252 char *uri = NULL;
7253 struct download *download_entry;
7254 int i, ret = TRUE;
7256 if (wk_download == NULL || t == NULL) {
7257 show_oops(NULL, "%s invalid parameters", __func__);
7258 return (FALSE);
7261 suggested_name = webkit_download_get_suggested_filename(wk_download);
7262 if (suggested_name == NULL)
7263 return (FALSE); /* abort download */
7265 i = 0;
7266 do {
7267 if (filename) {
7268 g_free(filename);
7269 filename = NULL;
7271 if (i) {
7272 g_free(uri);
7273 uri = NULL;
7274 filename = g_strdup_printf("%d%s", i, suggested_name);
7276 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7277 filename : suggested_name);
7278 i++;
7279 } while (!stat(uri + strlen("file://"), &sb));
7281 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7282 "local %s\n", __func__, t->tab_id, filename, uri);
7284 webkit_download_set_destination_uri(wk_download, uri);
7286 if (webkit_download_get_status(wk_download) ==
7287 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7288 show_oops(t, "%s: download failed to start", __func__);
7289 ret = FALSE;
7290 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7291 } else {
7292 /* connect "download first" mime handler */
7293 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7294 G_CALLBACK(download_status_changed_cb), NULL);
7296 download_entry = g_malloc(sizeof(struct download));
7297 download_entry->download = wk_download;
7298 download_entry->tab = t;
7299 download_entry->id = next_download_id++;
7300 RB_INSERT(download_list, &downloads, download_entry);
7301 /* get from history */
7302 g_object_ref(wk_download);
7303 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7304 show_oops(t, "Download of '%s' started...",
7305 basename((char *)webkit_download_get_destination_uri(wk_download)));
7308 if (uri)
7309 g_free(uri);
7311 if (filename)
7312 g_free(filename);
7314 /* sync other download manager tabs */
7315 update_download_tabs(NULL);
7318 * NOTE: never redirect/render the current tab before this
7319 * function returns. This will cause the download to never start.
7321 return (ret); /* start download */
7324 void
7325 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7327 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7329 if (t == NULL) {
7330 show_oops(NULL, "webview_hover_cb");
7331 return;
7334 if (uri)
7335 set_status(t, uri, XT_STATUS_LINK);
7336 else {
7337 if (t->status)
7338 set_status(t, t->status, XT_STATUS_NOTHING);
7343 mark(struct tab *t, struct karg *arg)
7345 char mark;
7346 int index;
7348 mark = arg->s[1];
7349 if ((index = marktoindex(mark)) == -1)
7350 return (-1);
7352 if (arg->i == XT_MARK_SET)
7353 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7354 else if (arg->i == XT_MARK_GOTO) {
7355 if (t->mark[index] == XT_INVALID_MARK) {
7356 show_oops(t, "mark '%c' does not exist", mark);
7357 return (-1);
7359 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7360 something changes the document size */
7361 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7364 return (0);
7367 void
7368 marks_clear(struct tab *t)
7370 int i;
7372 for (i = 0; i < LENGTH(t->mark); i++)
7373 t->mark[i] = XT_INVALID_MARK;
7377 qmarks_load(void)
7379 char file[PATH_MAX];
7380 char *line = NULL, *p;
7381 int index, i;
7382 FILE *f;
7383 size_t linelen;
7385 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7386 if ((f = fopen(file, "r+")) == NULL) {
7387 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7388 return (1);
7391 for (i = 1; ; i++) {
7392 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7393 break;
7394 if (strlen(line) == 0 || line[0] == '#') {
7395 free(line);
7396 line = NULL;
7397 continue;
7400 p = strtok(line, " \t");
7402 if (p == NULL || strlen(p) != 1 ||
7403 (index = marktoindex(*p)) == -1) {
7404 warnx("corrupt quickmarks file, line %d", i);
7405 break;
7408 p = strtok(NULL, " \t");
7409 if (qmarks[index] != NULL)
7410 g_free(qmarks[index]);
7411 qmarks[index] = g_strdup(p);
7414 fclose(f);
7416 return (0);
7420 qmarks_save(void)
7422 char file[PATH_MAX];
7423 int i;
7424 FILE *f;
7426 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7427 if ((f = fopen(file, "r+")) == NULL) {
7428 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7429 return (1);
7432 for (i = 0; i < XT_NOMARKS; i++)
7433 if (qmarks[i] != NULL)
7434 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7436 fclose(f);
7438 return (0);
7442 qmark(struct tab *t, struct karg *arg)
7444 char mark;
7445 int index;
7447 mark = arg->s[strlen(arg->s)-1];
7448 index = marktoindex(mark);
7449 if (index == -1)
7450 return (-1);
7452 switch (arg->i) {
7453 case XT_QMARK_SET:
7454 if (qmarks[index] != NULL)
7455 g_free(qmarks[index]);
7457 qmarks_load(); /* sync if multiple instances */
7458 qmarks[index] = g_strdup(get_uri(t));
7459 qmarks_save();
7460 break;
7461 case XT_QMARK_OPEN:
7462 if (qmarks[index] != NULL)
7463 load_uri(t, qmarks[index]);
7464 else {
7465 show_oops(t, "quickmark \"%c\" does not exist",
7466 mark);
7467 return (-1);
7469 break;
7470 case XT_QMARK_TAB:
7471 if (qmarks[index] != NULL)
7472 create_new_tab(qmarks[index], NULL, 1, -1);
7473 else {
7474 show_oops(t, "quickmark \"%c\" does not exist",
7475 mark);
7476 return (-1);
7478 break;
7481 return (0);
7485 go_up(struct tab *t, struct karg *args)
7487 int levels;
7488 char *uri;
7489 char *tmp;
7491 levels = atoi(args->s);
7492 if (levels == 0)
7493 levels = 1;
7495 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7496 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7497 return (1);
7498 tmp += strlen(XT_PROTO_DELIM);
7500 /* if an uri starts with a slash, leave it alone (for file:///) */
7501 if (tmp[0] == '/')
7502 tmp++;
7504 while (levels--) {
7505 char *p;
7507 p = strrchr(tmp, '/');
7508 if (p != NULL)
7509 *p = '\0';
7510 else
7511 break;
7514 load_uri(t, uri);
7515 g_free(uri);
7517 return (0);
7521 gototab(struct tab *t, struct karg *args)
7523 int tab;
7524 struct karg arg = {0, NULL, -1};
7526 tab = atoi(args->s);
7528 arg.i = XT_TAB_NEXT;
7529 arg.precount = tab;
7531 movetab(t, &arg);
7533 return (0);
7537 zoom_amount(struct tab *t, struct karg *arg)
7539 struct karg narg = {0, NULL, -1};
7541 narg.i = atoi(arg->s);
7542 resizetab(t, &narg);
7544 return (0);
7548 flip_colon(struct tab *t, struct karg *arg)
7550 struct karg narg = {0, NULL, -1};
7551 char *p;
7553 if (t == NULL || arg == NULL)
7554 return (1);
7556 p = strstr(arg->s, ":");
7557 if (p == NULL)
7558 return (1);
7559 *p = '\0';
7561 narg.i = ':';
7562 narg.s = arg->s;
7563 command(t, &narg);
7565 return (0);
7568 /* buffer commands receive the regex that triggered them in arg.s */
7569 char bcmd[XT_BUFCMD_SZ];
7570 struct buffercmd {
7571 char *regex;
7572 int precount;
7573 #define XT_PRE_NO (0)
7574 #define XT_PRE_YES (1)
7575 #define XT_PRE_MAYBE (2)
7576 char *cmd;
7577 int (*func)(struct tab *, struct karg *);
7578 int arg;
7579 regex_t cregex;
7580 } buffercmds[] = {
7581 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7582 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7583 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7584 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7585 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7586 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7587 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7588 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7589 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7590 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7591 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7592 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7593 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7594 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7595 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7596 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7597 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7598 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7601 void
7602 buffercmd_init(void)
7604 int i;
7606 for (i = 0; i < LENGTH(buffercmds); i++)
7607 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7608 REG_EXTENDED | REG_NOSUB))
7609 startpage_add("invalid buffercmd regex %s",
7610 buffercmds[i].regex);
7613 void
7614 buffercmd_abort(struct tab *t)
7616 int i;
7618 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7619 for (i = 0; i < LENGTH(bcmd); i++)
7620 bcmd[i] = '\0';
7622 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7623 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7626 void
7627 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7629 struct karg arg = {0, NULL, -1};
7631 arg.i = cmd->arg;
7632 arg.s = g_strdup(bcmd);
7634 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7635 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7636 cmd->func(t, &arg);
7638 if (arg.s)
7639 g_free(arg.s);
7641 buffercmd_abort(t);
7644 gboolean
7645 buffercmd_addkey(struct tab *t, guint keyval)
7647 int i, c, match ;
7648 char s[XT_BUFCMD_SZ];
7650 if (keyval == GDK_Escape) {
7651 buffercmd_abort(t);
7652 return (XT_CB_HANDLED);
7655 /* key with modifier or non-ascii character */
7656 if (!isascii(keyval))
7657 return (XT_CB_PASSTHROUGH);
7659 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7660 "to buffer \"%s\"\n", keyval, bcmd);
7662 for (i = 0; i < LENGTH(bcmd); i++)
7663 if (bcmd[i] == '\0') {
7664 bcmd[i] = keyval;
7665 break;
7668 /* buffer full, ignore input */
7669 if (i >= LENGTH(bcmd) -1) {
7670 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7671 buffercmd_abort(t);
7672 return (XT_CB_HANDLED);
7675 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7677 /* find exact match */
7678 for (i = 0; i < LENGTH(buffercmds); i++)
7679 if (regexec(&buffercmds[i].cregex, bcmd,
7680 (size_t) 0, NULL, 0) == 0) {
7681 buffercmd_execute(t, &buffercmds[i]);
7682 goto done;
7685 /* find non exact matches to see if we need to abort ot not */
7686 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7687 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7688 c = -1;
7689 s[0] = '\0';
7690 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7691 if (isdigit(bcmd[0])) {
7692 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7693 continue;
7694 } else {
7695 c = 0;
7696 if (sscanf(bcmd, "%s", s) == 0)
7697 continue;
7699 } else if (buffercmds[i].precount == XT_PRE_YES) {
7700 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7701 continue;
7702 } else {
7703 if (sscanf(bcmd, "%s", s) == 0)
7704 continue;
7706 if (c == -1 && buffercmds[i].precount)
7707 continue;
7708 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7709 match++;
7711 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7712 i, match, buffercmds[i].cmd, c, s);
7714 if (match == 0) {
7715 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7716 buffercmd_abort(t);
7719 done:
7720 return (XT_CB_HANDLED);
7723 gboolean
7724 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7726 struct key_binding *k;
7728 /* handle keybindings if buffercmd is empty.
7729 if not empty, allow commands like C-n */
7730 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7731 TAILQ_FOREACH(k, &kbl, entry)
7732 if (e->keyval == k->key
7733 && (entry ? k->use_in_entry : 1)) {
7734 if (k->mask == 0) {
7735 if ((e->state & (CTRL | MOD1)) == 0)
7736 return (cmd_execute(t, k->cmd));
7737 } else if ((e->state & k->mask) == k->mask) {
7738 return (cmd_execute(t, k->cmd));
7742 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7743 return buffercmd_addkey(t, e->keyval);
7745 return (XT_CB_PASSTHROUGH);
7749 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7751 char s[2], buf[128];
7752 const char *errstr = NULL;
7754 /* don't use w directly; use t->whatever instead */
7756 if (t == NULL) {
7757 show_oops(NULL, "wv_keypress_after_cb");
7758 return (XT_CB_PASSTHROUGH);
7761 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7762 e->keyval, e->state, t);
7764 if (t->hints_on) {
7765 /* ESC */
7766 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7767 disable_hints(t);
7768 return (XT_CB_HANDLED);
7771 /* RETURN */
7772 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7773 if (errstr) {
7774 /* we have a string */
7775 } else {
7776 /* we have a number */
7777 snprintf(buf, sizeof buf,
7778 "vimprobable_fire(%s)", t->hint_num);
7779 run_script(t, buf);
7781 disable_hints(t);
7784 /* BACKSPACE */
7785 /* XXX unfuck this */
7786 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7787 if (t->hint_mode == XT_HINT_NUMERICAL) {
7788 /* last input was numerical */
7789 int l;
7790 l = strlen(t->hint_num);
7791 if (l > 0) {
7792 l--;
7793 if (l == 0) {
7794 disable_hints(t);
7795 enable_hints(t);
7796 } else {
7797 t->hint_num[l] = '\0';
7798 goto num;
7801 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7802 /* last input was alphanumerical */
7803 int l;
7804 l = strlen(t->hint_buf);
7805 if (l > 0) {
7806 l--;
7807 if (l == 0) {
7808 disable_hints(t);
7809 enable_hints(t);
7810 } else {
7811 t->hint_buf[l] = '\0';
7812 goto anum;
7815 } else {
7816 /* bogus */
7817 disable_hints(t);
7821 /* numerical input */
7822 if (CLEAN(e->state) == 0 &&
7823 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7824 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7825 snprintf(s, sizeof s, "%c", e->keyval);
7826 strlcat(t->hint_num, s, sizeof t->hint_num);
7827 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7828 t->hint_num);
7829 num:
7830 if (errstr) {
7831 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7832 "invalid link number\n");
7833 disable_hints(t);
7834 } else {
7835 snprintf(buf, sizeof buf,
7836 "vimprobable_update_hints(%s)",
7837 t->hint_num);
7838 t->hint_mode = XT_HINT_NUMERICAL;
7839 run_script(t, buf);
7842 /* empty the counter buffer */
7843 bzero(t->hint_buf, sizeof t->hint_buf);
7844 return (XT_CB_HANDLED);
7847 /* alphanumerical input */
7848 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7849 e->keyval <= GDK_z) ||
7850 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7851 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7852 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7853 e->keyval <= GDK_9) ||
7854 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7855 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7856 snprintf(s, sizeof s, "%c", e->keyval);
7857 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7858 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7859 " %s\n", t->hint_buf);
7860 anum:
7861 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7862 run_script(t, buf);
7864 snprintf(buf, sizeof buf,
7865 "vimprobable_show_hints('%s')", t->hint_buf);
7866 t->hint_mode = XT_HINT_ALPHANUM;
7867 run_script(t, buf);
7869 /* empty the counter buffer */
7870 bzero(t->hint_num, sizeof t->hint_num);
7871 return (XT_CB_HANDLED);
7874 return (XT_CB_HANDLED);
7875 } else {
7876 /* prefix input*/
7877 snprintf(s, sizeof s, "%c", e->keyval);
7878 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7879 cmd_prefix = 10 * cmd_prefix + atoi(s);
7882 return (handle_keypress(t, e, 0));
7886 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7888 hide_oops(t);
7890 /* Hide buffers, if they are visible, with escape. */
7891 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7892 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7893 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7894 hide_buffers(t);
7895 return (XT_CB_HANDLED);
7898 return (XT_CB_PASSTHROUGH);
7901 gboolean
7902 search_continue(struct tab *t)
7904 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7905 gboolean rv = FALSE;
7907 if (c[0] == ':')
7908 goto done;
7909 if (strlen(c) == 1) {
7910 webkit_web_view_unmark_text_matches(t->wv);
7911 goto done;
7914 if (c[0] == '/')
7915 t->search_forward = TRUE;
7916 else if (c[0] == '?')
7917 t->search_forward = FALSE;
7918 else
7919 goto done;
7921 rv = TRUE;
7922 done:
7923 return (rv);
7926 gboolean
7927 search_cb(struct tab *t)
7929 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7930 GdkColor color;
7932 if (search_continue(t) == FALSE)
7933 goto done;
7935 /* search */
7936 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7937 TRUE) == FALSE) {
7938 /* not found, mark red */
7939 gdk_color_parse(XT_COLOR_RED, &color);
7940 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7941 /* unmark and remove selection */
7942 webkit_web_view_unmark_text_matches(t->wv);
7943 /* my kingdom for a way to unselect text in webview */
7944 } else {
7945 /* found, highlight all */
7946 webkit_web_view_unmark_text_matches(t->wv);
7947 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7948 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7949 gdk_color_parse(XT_COLOR_WHITE, &color);
7950 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7952 done:
7953 t->search_id = 0;
7954 return (FALSE);
7958 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7960 const gchar *c = gtk_entry_get_text(w);
7962 if (t == NULL) {
7963 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7964 return (XT_CB_PASSTHROUGH);
7967 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7968 e->keyval, e->state, t);
7970 if (search_continue(t) == FALSE)
7971 goto done;
7973 /* if search length is > 4 then no longer play timeout games */
7974 if (strlen(c) > 4) {
7975 if (t->search_id) {
7976 g_source_remove(t->search_id);
7977 t->search_id = 0;
7979 search_cb(t);
7980 goto done;
7983 /* reestablish a new timer if the user types fast */
7984 if (t->search_id)
7985 g_source_remove(t->search_id);
7986 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7988 done:
7989 return (XT_CB_PASSTHROUGH);
7992 gboolean
7993 match_uri(const gchar *uri, const gchar *key) {
7994 gchar *voffset;
7995 size_t len;
7996 gboolean match = FALSE;
7998 len = strlen(key);
8000 if (!strncmp(key, uri, len))
8001 match = TRUE;
8002 else {
8003 voffset = strstr(uri, "/") + 2;
8004 if (!strncmp(key, voffset, len))
8005 match = TRUE;
8006 else if (g_str_has_prefix(voffset, "www.")) {
8007 voffset = voffset + strlen("www.");
8008 if (!strncmp(key, voffset, len))
8009 match = TRUE;
8013 return (match);
8016 gboolean
8017 match_session(const gchar *name, const gchar *key) {
8018 char *sub;
8020 sub = strcasestr(name, key);
8022 return sub == name;
8025 void
8026 cmd_getlist(int id, char *key)
8028 int i, dep, c = 0;
8029 struct history *h;
8030 struct session *s;
8032 if (id >= 0) {
8033 if (cmds[id].type & XT_URLARG) {
8034 RB_FOREACH_REVERSE(h, history_list, &hl)
8035 if (match_uri(h->uri, key)) {
8036 cmd_status.list[c] = (char *)h->uri;
8037 if (++c > 255)
8038 break;
8040 cmd_status.len = c;
8041 return;
8042 } else if (cmds[id].type & XT_SESSARG) {
8043 TAILQ_FOREACH(s, &sessions, entry)
8044 if (match_session(s->name, key)) {
8045 cmd_status.list[c] = (char *)s->name;
8046 if (++c > 255)
8047 break;
8049 cmd_status.len = c;
8050 return;
8051 } else if (cmds[id].type & XT_SETARG) {
8052 for (i = 0; i < LENGTH(rs); i++)
8053 if(!strncmp(key, rs[i].name, strlen(key)))
8054 cmd_status.list[c++] = rs[i].name;
8055 cmd_status.len = c;
8056 return;
8060 dep = (id == -1) ? 0 : cmds[id].level + 1;
8062 for (i = id + 1; i < LENGTH(cmds); i++) {
8063 if (cmds[i].level < dep)
8064 break;
8065 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8066 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8067 cmd_status.list[c++] = cmds[i].cmd;
8071 cmd_status.len = c;
8074 char *
8075 cmd_getnext(int dir)
8077 cmd_status.index += dir;
8079 if (cmd_status.index < 0)
8080 cmd_status.index = cmd_status.len - 1;
8081 else if (cmd_status.index >= cmd_status.len)
8082 cmd_status.index = 0;
8084 return cmd_status.list[cmd_status.index];
8088 cmd_tokenize(char *s, char *tokens[])
8090 int i = 0;
8091 char *tok, *last = NULL;
8092 size_t len = strlen(s);
8093 bool blank;
8095 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8096 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8097 tok = strtok_r(NULL, " ", &last), i++)
8098 tokens[i] = tok;
8100 if (blank && i < 3)
8101 tokens[i++] = "";
8103 return (i);
8106 void
8107 cmd_complete(struct tab *t, char *str, int dir)
8109 GtkEntry *w = GTK_ENTRY(t->cmd);
8110 int i, j, levels, c = 0, dep = 0, parent = -1;
8111 int matchcount = 0;
8112 char *tok, *match, *s = g_strdup(str);
8113 char *tokens[3];
8114 char res[XT_MAX_URL_LENGTH + 32] = ":";
8115 char *sc = s;
8117 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8119 /* copy prefix*/
8120 for (i = 0; isdigit(s[i]); i++)
8121 res[i + 1] = s[i];
8123 for (; isspace(s[i]); i++)
8124 res[i + 1] = s[i];
8126 s += i;
8128 levels = cmd_tokenize(s, tokens);
8130 for (i = 0; i < levels - 1; i++) {
8131 tok = tokens[i];
8132 matchcount = 0;
8133 for (j = c; j < LENGTH(cmds); j++) {
8134 if (cmds[j].level < dep)
8135 break;
8136 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8137 strlen(tok))) {
8138 matchcount++;
8139 c = j + 1;
8140 if (strlen(tok) == strlen(cmds[j].cmd)) {
8141 matchcount = 1;
8142 break;
8147 if (matchcount == 1) {
8148 strlcat(res, tok, sizeof res);
8149 strlcat(res, " ", sizeof res);
8150 dep++;
8151 } else {
8152 g_free(sc);
8153 return;
8156 parent = c - 1;
8159 if (cmd_status.index == -1)
8160 cmd_getlist(parent, tokens[i]);
8162 if (cmd_status.len > 0) {
8163 match = cmd_getnext(dir);
8164 strlcat(res, match, sizeof res);
8165 gtk_entry_set_text(w, res);
8166 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8169 g_free(sc);
8172 gboolean
8173 cmd_execute(struct tab *t, char *str)
8175 struct cmd *cmd = NULL;
8176 char *tok, *last = NULL, *s = g_strdup(str), *sc;
8177 char prefixstr[4];
8178 int j, len, c = 0, dep = 0, matchcount = 0;
8179 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8180 struct karg arg = {0, NULL, -1};
8182 sc = s;
8184 /* copy prefix*/
8185 for (j = 0; j<3 && isdigit(s[j]); j++)
8186 prefixstr[j]=s[j];
8188 prefixstr[j]='\0';
8190 s += j;
8191 while (isspace(s[0]))
8192 s++;
8194 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8195 prefix = atoi(prefixstr);
8196 else
8197 s = sc;
8199 for (tok = strtok_r(s, " ", &last); tok;
8200 tok = strtok_r(NULL, " ", &last)) {
8201 matchcount = 0;
8202 for (j = c; j < LENGTH(cmds); j++) {
8203 if (cmds[j].level < dep)
8204 break;
8205 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8206 strlen(tok);
8207 if (cmds[j].level == dep &&
8208 !strncmp(tok, cmds[j].cmd, len)) {
8209 matchcount++;
8210 c = j + 1;
8211 cmd = &cmds[j];
8212 if (len == strlen(cmds[j].cmd)) {
8213 matchcount = 1;
8214 break;
8218 if (matchcount == 1) {
8219 if (cmd->type > 0)
8220 goto execute_cmd;
8221 dep++;
8222 } else {
8223 show_oops(t, "Invalid command: %s", str);
8224 goto done;
8227 execute_cmd:
8228 if (cmd == NULL) {
8229 show_oops(t, "Empty command");
8230 goto done;
8232 arg.i = cmd->arg;
8234 if (prefix != -1)
8235 arg.precount = prefix;
8236 else if (cmd_prefix > 0)
8237 arg.precount = cmd_prefix;
8239 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8240 show_oops(t, "No prefix allowed: %s", str);
8241 goto done;
8243 if (cmd->type > 1)
8244 arg.s = last ? g_strdup(last) : g_strdup("");
8245 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8246 if (arg.s == NULL) {
8247 show_oops(t, "Invalid command");
8248 goto done;
8250 arg.precount = atoi(arg.s);
8251 if (arg.precount <= 0) {
8252 if (arg.s[0] == '0')
8253 show_oops(t, "Zero count");
8254 else
8255 show_oops(t, "Trailing characters");
8256 goto done;
8260 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8261 __func__, arg.precount, arg.s);
8263 cmd->func(t, &arg);
8265 rv = XT_CB_HANDLED;
8266 done:
8267 if (j > 0)
8268 cmd_prefix = 0;
8269 g_free(sc);
8270 if (arg.s)
8271 g_free(arg.s);
8273 return (rv);
8277 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8279 if (t == NULL) {
8280 show_oops(NULL, "entry_key_cb invalid parameters");
8281 return (XT_CB_PASSTHROUGH);
8284 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8285 e->keyval, e->state, t);
8287 hide_oops(t);
8289 if (e->keyval == GDK_Escape) {
8290 /* don't use focus_webview(t) because we want to type :cmds */
8291 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8294 return (handle_keypress(t, e, 1));
8297 struct command_entry *
8298 history_prev(struct command_list *l, struct command_entry *at)
8300 if (at == NULL)
8301 at = TAILQ_LAST(l, command_list);
8302 else {
8303 at = TAILQ_PREV(at, command_list, entry);
8304 if (at == NULL)
8305 at = TAILQ_LAST(l, command_list);
8308 return (at);
8311 struct command_entry *
8312 history_next(struct command_list *l, struct command_entry *at)
8314 if (at == NULL)
8315 at = TAILQ_FIRST(l);
8316 else {
8317 at = TAILQ_NEXT(at, entry);
8318 if (at == NULL)
8319 at = TAILQ_FIRST(l);
8322 return (at);
8326 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8328 int rv = XT_CB_HANDLED;
8329 const gchar *c = gtk_entry_get_text(w);
8331 if (t == NULL) {
8332 show_oops(NULL, "cmd_keypress_cb parameters");
8333 return (XT_CB_PASSTHROUGH);
8336 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8337 e->keyval, e->state, t);
8339 /* sanity */
8340 if (c == NULL)
8341 e->keyval = GDK_Escape;
8342 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8343 e->keyval = GDK_Escape;
8345 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8346 e->keyval != GDK_ISO_Left_Tab)
8347 cmd_status.index = -1;
8349 switch (e->keyval) {
8350 case GDK_Tab:
8351 if (c[0] == ':')
8352 cmd_complete(t, (char *)&c[1], 1);
8353 goto done;
8354 case GDK_ISO_Left_Tab:
8355 if (c[0] == ':')
8356 cmd_complete(t, (char *)&c[1], -1);
8358 goto done;
8359 case GDK_Down:
8360 if (c[0] != ':') {
8361 if ((search_at = history_next(&shl, search_at))) {
8362 search_at->line[0] = c[0];
8363 gtk_entry_set_text(w, search_at->line);
8364 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8366 } else {
8367 if ((history_at = history_prev(&chl, history_at))) {
8368 history_at->line[0] = c[0];
8369 gtk_entry_set_text(w, history_at->line);
8370 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8374 goto done;
8375 case GDK_Up:
8376 if (c[0] != ':') {
8377 if ((search_at = history_next(&shl, search_at))) {
8378 search_at->line[0] = c[0];
8379 gtk_entry_set_text(w, search_at->line);
8380 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8382 } else {
8383 if ((history_at = history_next(&chl, history_at))) {
8384 history_at->line[0] = c[0];
8385 gtk_entry_set_text(w, history_at->line);
8386 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8390 goto done;
8391 case GDK_BackSpace:
8392 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8393 break;
8394 /* FALLTHROUGH */
8395 case GDK_Escape:
8396 hide_cmd(t);
8397 focus_webview(t);
8399 /* cancel search */
8400 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8401 webkit_web_view_unmark_text_matches(t->wv);
8402 goto done;
8405 rv = XT_CB_PASSTHROUGH;
8406 done:
8407 return (rv);
8410 void
8411 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8413 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8416 void
8417 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8419 /* popup menu enabled */
8420 t->popup = 1;
8424 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8426 if (t == NULL) {
8427 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8428 return (XT_CB_PASSTHROUGH);
8431 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8432 t->tab_id, t->popup);
8434 /* if popup is enabled don't lose focus */
8435 if (t->popup) {
8436 t->popup = 0;
8437 return (XT_CB_PASSTHROUGH);
8440 hide_cmd(t);
8441 hide_oops(t);
8443 if (show_url == 0 || t->focus_wv)
8444 focus_webview(t);
8445 else
8446 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8448 return (XT_CB_PASSTHROUGH);
8451 void
8452 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8454 char *s;
8455 const gchar *c = gtk_entry_get_text(entry);
8457 if (t == NULL) {
8458 show_oops(NULL, "cmd_activate_cb invalid parameters");
8459 return;
8462 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8464 hide_cmd(t);
8466 /* sanity */
8467 if (c == NULL)
8468 goto done;
8469 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8470 goto done;
8471 if (strlen(c) < 2)
8472 goto done;
8473 s = (char *)&c[1];
8475 if (c[0] == '/' || c[0] == '?') {
8476 /* see if there is a timer pending */
8477 if (t->search_id) {
8478 g_source_remove(t->search_id);
8479 t->search_id = 0;
8480 search_cb(t);
8483 if (t->search_text) {
8484 g_free(t->search_text);
8485 t->search_text = NULL;
8488 t->search_text = g_strdup(s);
8489 if (global_search)
8490 g_free(global_search);
8491 global_search = g_strdup(s);
8492 t->search_forward = c[0] == '/';
8494 history_add(&shl, search_file, s, &search_history_count);
8495 goto done;
8498 history_add(&chl, command_file, s, &cmd_history_count);
8499 cmd_execute(t, s);
8500 done:
8501 return;
8504 void
8505 backward_cb(GtkWidget *w, struct tab *t)
8507 struct karg a;
8509 if (t == NULL) {
8510 show_oops(NULL, "backward_cb invalid parameters");
8511 return;
8514 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8516 a.i = XT_NAV_BACK;
8517 navaction(t, &a);
8520 void
8521 forward_cb(GtkWidget *w, struct tab *t)
8523 struct karg a;
8525 if (t == NULL) {
8526 show_oops(NULL, "forward_cb invalid parameters");
8527 return;
8530 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8532 a.i = XT_NAV_FORWARD;
8533 navaction(t, &a);
8536 void
8537 home_cb(GtkWidget *w, struct tab *t)
8539 if (t == NULL) {
8540 show_oops(NULL, "home_cb invalid parameters");
8541 return;
8544 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8546 load_uri(t, home);
8549 void
8550 stop_cb(GtkWidget *w, struct tab *t)
8552 WebKitWebFrame *frame;
8554 if (t == NULL) {
8555 show_oops(NULL, "stop_cb invalid parameters");
8556 return;
8559 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8561 frame = webkit_web_view_get_main_frame(t->wv);
8562 if (frame == NULL) {
8563 show_oops(t, "stop_cb: no frame");
8564 return;
8567 webkit_web_frame_stop_loading(frame);
8568 abort_favicon_download(t);
8571 void
8572 setup_webkit(struct tab *t)
8574 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8575 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8576 FALSE, (char *)NULL);
8577 else
8578 warnx("webkit does not have \"enable-dns-prefetching\" property");
8579 g_object_set(G_OBJECT(t->settings),
8580 "user-agent", t->user_agent, (char *)NULL);
8581 g_object_set(G_OBJECT(t->settings),
8582 "enable-scripts", enable_scripts, (char *)NULL);
8583 g_object_set(G_OBJECT(t->settings),
8584 "enable-plugins", enable_plugins, (char *)NULL);
8585 g_object_set(G_OBJECT(t->settings),
8586 "javascript-can-open-windows-automatically", enable_scripts,
8587 (char *)NULL);
8588 g_object_set(G_OBJECT(t->settings),
8589 "enable-html5-database", FALSE, (char *)NULL);
8590 g_object_set(G_OBJECT(t->settings),
8591 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8592 g_object_set(G_OBJECT(t->settings),
8593 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8594 g_object_set(G_OBJECT(t->settings),
8595 "spell_checking_languages", spell_check_languages, (char *)NULL);
8596 g_object_set(G_OBJECT(t->wv),
8597 "full-content-zoom", TRUE, (char *)NULL);
8599 webkit_web_view_set_settings(t->wv, t->settings);
8602 gboolean
8603 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8605 struct tab *ti, *t = NULL;
8606 gdouble view_size, value, max;
8607 gchar *position;
8609 TAILQ_FOREACH(ti, &tabs, entry)
8610 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8611 t = ti;
8612 break;
8615 if (t == NULL)
8616 return FALSE;
8618 if (adjustment == NULL)
8619 adjustment = gtk_scrolled_window_get_vadjustment(
8620 GTK_SCROLLED_WINDOW(t->browser_win));
8622 view_size = gtk_adjustment_get_page_size(adjustment);
8623 value = gtk_adjustment_get_value(adjustment);
8624 max = gtk_adjustment_get_upper(adjustment) - view_size;
8626 if (max == 0)
8627 position = g_strdup("All");
8628 else if (value == max)
8629 position = g_strdup("Bot");
8630 else if (value == 0)
8631 position = g_strdup("Top");
8632 else
8633 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8635 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8636 g_free(position);
8638 return (TRUE);
8641 GtkWidget *
8642 create_browser(struct tab *t)
8644 GtkWidget *w;
8645 gchar *strval;
8646 GtkAdjustment *adjustment;
8648 if (t == NULL) {
8649 show_oops(NULL, "create_browser invalid parameters");
8650 return (NULL);
8653 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8654 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8655 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8656 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8658 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8659 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8660 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8662 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8663 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8665 /* set defaults */
8666 t->settings = webkit_web_settings_new();
8668 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8670 if (user_agent == NULL) {
8671 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8672 (char *)NULL);
8673 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8674 g_free(strval);
8675 } else
8676 t->user_agent = g_strdup(user_agent);
8678 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8680 adjustment =
8681 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8682 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8683 G_CALLBACK(update_statusbar_position), NULL);
8685 setup_webkit(t);
8687 return (w);
8690 GtkWidget *
8691 create_window(void)
8693 GtkWidget *w;
8695 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8696 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8697 gtk_widget_set_name(w, "xxxterm");
8698 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8699 g_signal_connect(G_OBJECT(w), "delete_event",
8700 G_CALLBACK (gtk_main_quit), NULL);
8702 return (w);
8705 GtkWidget *
8706 create_kiosk_toolbar(struct tab *t)
8708 GtkWidget *toolbar = NULL, *b;
8710 b = gtk_hbox_new(FALSE, 0);
8711 toolbar = b;
8712 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8714 /* backward button */
8715 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8716 gtk_widget_set_sensitive(t->backward, FALSE);
8717 g_signal_connect(G_OBJECT(t->backward), "clicked",
8718 G_CALLBACK(backward_cb), t);
8719 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8721 /* forward button */
8722 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8723 gtk_widget_set_sensitive(t->forward, FALSE);
8724 g_signal_connect(G_OBJECT(t->forward), "clicked",
8725 G_CALLBACK(forward_cb), t);
8726 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8728 /* home button */
8729 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8730 gtk_widget_set_sensitive(t->gohome, true);
8731 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8732 G_CALLBACK(home_cb), t);
8733 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8735 /* create widgets but don't use them */
8736 t->uri_entry = gtk_entry_new();
8737 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8738 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8739 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8741 return (toolbar);
8744 GtkWidget *
8745 create_toolbar(struct tab *t)
8747 GtkWidget *toolbar = NULL, *b, *eb1;
8749 b = gtk_hbox_new(FALSE, 0);
8750 toolbar = b;
8751 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8753 /* backward button */
8754 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8755 gtk_widget_set_sensitive(t->backward, FALSE);
8756 g_signal_connect(G_OBJECT(t->backward), "clicked",
8757 G_CALLBACK(backward_cb), t);
8758 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8760 /* forward button */
8761 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8762 gtk_widget_set_sensitive(t->forward, FALSE);
8763 g_signal_connect(G_OBJECT(t->forward), "clicked",
8764 G_CALLBACK(forward_cb), t);
8765 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8766 FALSE, 0);
8768 /* stop button */
8769 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8770 gtk_widget_set_sensitive(t->stop, FALSE);
8771 g_signal_connect(G_OBJECT(t->stop), "clicked",
8772 G_CALLBACK(stop_cb), t);
8773 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8774 FALSE, 0);
8776 /* JS button */
8777 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8778 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8779 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8780 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8781 G_CALLBACK(js_toggle_cb), t);
8782 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8784 t->uri_entry = gtk_entry_new();
8785 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8786 G_CALLBACK(activate_uri_entry_cb), t);
8787 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8788 G_CALLBACK(entry_key_cb), t);
8789 completion_add(t);
8790 eb1 = gtk_hbox_new(FALSE, 0);
8791 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8792 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8793 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8795 /* search entry */
8796 if (search_string) {
8797 GtkWidget *eb2;
8798 t->search_entry = gtk_entry_new();
8799 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8800 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8801 G_CALLBACK(activate_search_entry_cb), t);
8802 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8803 G_CALLBACK(entry_key_cb), t);
8804 gtk_widget_set_size_request(t->search_entry, -1, -1);
8805 eb2 = gtk_hbox_new(FALSE, 0);
8806 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8807 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8809 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8812 return (toolbar);
8815 GtkWidget *
8816 create_buffers(struct tab *t)
8818 GtkCellRenderer *renderer;
8819 GtkWidget *view;
8821 view = gtk_tree_view_new();
8823 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8825 renderer = gtk_cell_renderer_text_new();
8826 gtk_tree_view_insert_column_with_attributes
8827 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8829 renderer = gtk_cell_renderer_text_new();
8830 gtk_tree_view_insert_column_with_attributes
8831 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8832 (char *)NULL);
8834 gtk_tree_view_set_model
8835 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8837 return view;
8840 void
8841 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8842 GtkTreeViewColumn *col, struct tab *t)
8844 GtkTreeIter iter;
8845 guint id;
8847 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8849 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8850 path)) {
8851 gtk_tree_model_get
8852 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8853 set_current_tab(id - 1);
8856 hide_buffers(t);
8859 /* after tab reordering/creation/removal */
8860 void
8861 recalc_tabs(void)
8863 struct tab *t;
8864 int maxid = 0;
8866 TAILQ_FOREACH(t, &tabs, entry) {
8867 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8868 if (t->tab_id > maxid)
8869 maxid = t->tab_id;
8871 gtk_widget_show(t->tab_elems.sep);
8874 TAILQ_FOREACH(t, &tabs, entry) {
8875 if (t->tab_id == maxid) {
8876 gtk_widget_hide(t->tab_elems.sep);
8877 break;
8882 /* after active tab change */
8883 void
8884 recolor_compact_tabs(void)
8886 struct tab *t;
8887 int curid = 0;
8888 GdkColor color;
8890 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8891 TAILQ_FOREACH(t, &tabs, entry)
8892 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8893 &color);
8895 curid = gtk_notebook_get_current_page(notebook);
8896 TAILQ_FOREACH(t, &tabs, entry)
8897 if (t->tab_id == curid) {
8898 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8899 gtk_widget_modify_fg(t->tab_elems.label,
8900 GTK_STATE_NORMAL, &color);
8901 break;
8905 void
8906 set_current_tab(int page_num)
8908 buffercmd_abort(get_current_tab());
8909 gtk_notebook_set_current_page(notebook, page_num);
8910 recolor_compact_tabs();
8914 undo_close_tab_save(struct tab *t)
8916 int m, n;
8917 const gchar *uri;
8918 struct undo *u1, *u2;
8919 GList *items;
8920 WebKitWebHistoryItem *item;
8922 if ((uri = get_uri(t)) == NULL)
8923 return (1);
8925 u1 = g_malloc0(sizeof(struct undo));
8926 u1->uri = g_strdup(uri);
8928 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8930 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8931 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8932 u1->back = n;
8934 /* forward history */
8935 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8937 while (items) {
8938 item = items->data;
8939 u1->history = g_list_prepend(u1->history,
8940 webkit_web_history_item_copy(item));
8941 items = g_list_next(items);
8944 /* current item */
8945 if (m) {
8946 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8947 u1->history = g_list_prepend(u1->history,
8948 webkit_web_history_item_copy(item));
8951 /* back history */
8952 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8954 while (items) {
8955 item = items->data;
8956 u1->history = g_list_prepend(u1->history,
8957 webkit_web_history_item_copy(item));
8958 items = g_list_next(items);
8961 TAILQ_INSERT_HEAD(&undos, u1, entry);
8963 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8964 u2 = TAILQ_LAST(&undos, undo_tailq);
8965 TAILQ_REMOVE(&undos, u2, entry);
8966 g_free(u2->uri);
8967 g_list_free(u2->history);
8968 g_free(u2);
8969 } else
8970 undo_count++;
8972 return (0);
8975 void
8976 delete_tab(struct tab *t)
8978 struct karg a;
8980 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8982 if (t == NULL)
8983 return;
8986 * no need to join thread here because it won't access t on completion
8989 buffercmd_abort(t);
8990 TAILQ_REMOVE(&tabs, t, entry);
8992 /* Halt all webkit activity. */
8993 abort_favicon_download(t);
8994 webkit_web_view_stop_loading(t->wv);
8996 /* Save the tab, so we can undo the close. */
8997 undo_close_tab_save(t);
8999 if (browser_mode == XT_BM_KIOSK) {
9000 gtk_widget_destroy(t->uri_entry);
9001 gtk_widget_destroy(t->stop);
9002 gtk_widget_destroy(t->js_toggle);
9005 gtk_widget_destroy(t->tab_elems.eventbox);
9006 gtk_widget_destroy(t->vbox);
9008 /* just in case */
9009 if (t->search_id)
9010 g_source_remove(t->search_id);
9012 g_free(t->user_agent);
9013 g_free(t->stylesheet);
9014 g_free(t->tmp_uri);
9015 g_free(t);
9017 if (TAILQ_EMPTY(&tabs)) {
9018 if (browser_mode == XT_BM_KIOSK)
9019 create_new_tab(home, NULL, 1, -1);
9020 else
9021 create_new_tab(NULL, NULL, 1, -1);
9024 /* recreate session */
9025 if (session_autosave) {
9026 a.s = NULL;
9027 save_tabs(t, &a);
9030 recalc_tabs();
9031 recolor_compact_tabs();
9034 void
9035 update_statusbar_zoom(struct tab *t)
9037 gfloat zoom;
9038 char s[16] = { '\0' };
9040 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9041 if ((zoom <= 0.99 || zoom >= 1.01))
9042 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9043 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9046 void
9047 setzoom_webkit(struct tab *t, int adjust)
9049 #define XT_ZOOMPERCENT 0.04
9051 gfloat zoom;
9053 if (t == NULL) {
9054 show_oops(NULL, "setzoom_webkit invalid parameters");
9055 return;
9058 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9059 if (adjust == XT_ZOOM_IN)
9060 zoom += XT_ZOOMPERCENT;
9061 else if (adjust == XT_ZOOM_OUT)
9062 zoom -= XT_ZOOMPERCENT;
9063 else if (adjust > 0)
9064 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9065 else {
9066 show_oops(t, "setzoom_webkit invalid zoom value");
9067 return;
9070 if (zoom < XT_ZOOMPERCENT)
9071 zoom = XT_ZOOMPERCENT;
9072 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9073 update_statusbar_zoom(t);
9076 gboolean
9077 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9079 struct tab *t = (struct tab *) data;
9081 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9083 switch (event->button) {
9084 case 1:
9085 set_current_tab(t->tab_id);
9086 break;
9087 case 2:
9088 delete_tab(t);
9089 break;
9092 return TRUE;
9095 void
9096 append_tab(struct tab *t)
9098 if (t == NULL)
9099 return;
9101 TAILQ_INSERT_TAIL(&tabs, t, entry);
9102 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9105 GtkWidget *
9106 create_sbe(int width)
9108 GtkWidget *sbe;
9110 sbe = gtk_entry_new();
9111 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9112 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9113 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9114 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9115 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9116 gtk_widget_set_size_request(sbe, width, -1);
9118 return sbe;
9121 struct tab *
9122 create_new_tab(char *title, struct undo *u, int focus, int position)
9124 struct tab *t;
9125 int load = 1, id;
9126 GtkWidget *b, *bb;
9127 WebKitWebHistoryItem *item;
9128 GList *items;
9129 GdkColor color;
9130 char *p;
9131 int sbe_p = 0, sbe_b = 0,
9132 sbe_z = 0;
9134 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9136 if (tabless && !TAILQ_EMPTY(&tabs)) {
9137 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9138 return (NULL);
9141 t = g_malloc0(sizeof *t);
9143 if (title == NULL) {
9144 title = "(untitled)";
9145 load = 0;
9148 t->vbox = gtk_vbox_new(FALSE, 0);
9150 /* label + button for tab */
9151 b = gtk_hbox_new(FALSE, 0);
9152 t->tab_content = b;
9154 #if GTK_CHECK_VERSION(2, 20, 0)
9155 t->spinner = gtk_spinner_new();
9156 #endif
9157 t->label = gtk_label_new(title);
9158 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9159 gtk_widget_set_size_request(t->label, 100, 0);
9160 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9161 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9162 gtk_widget_set_size_request(b, 130, 0);
9164 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9165 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9166 #if GTK_CHECK_VERSION(2, 20, 0)
9167 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9168 #endif
9170 /* toolbar */
9171 if (browser_mode == XT_BM_KIOSK) {
9172 t->toolbar = create_kiosk_toolbar(t);
9173 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9175 } else {
9176 t->toolbar = create_toolbar(t);
9177 if (fancy_bar)
9178 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9179 FALSE, 0);
9182 /* marks */
9183 marks_clear(t);
9185 /* browser */
9186 t->browser_win = create_browser(t);
9187 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9189 /* oops message for user feedback */
9190 t->oops = gtk_entry_new();
9191 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9192 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9193 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9194 gdk_color_parse(XT_COLOR_RED, &color);
9195 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9196 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9197 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9199 /* command entry */
9200 t->cmd = gtk_entry_new();
9201 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9202 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9203 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9204 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9206 /* status bar */
9207 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9209 t->sbe.statusbar = gtk_entry_new();
9210 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9211 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9212 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9213 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9215 /* create these widgets only if specified in statusbar_elems */
9217 t->sbe.position = create_sbe(40);
9218 t->sbe.zoom = create_sbe(40);
9219 t->sbe.buffercmd = create_sbe(60);
9221 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9223 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9224 TRUE, FALSE);
9226 /* gtk widgets cannot be added to a box twice. sbe_* variables
9227 make sure of this */
9228 for (p = statusbar_elems; *p != '\0'; p++) {
9229 switch (*p) {
9230 case '|':
9232 GtkWidget *sep = gtk_vseparator_new();
9234 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9235 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9236 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9237 FALSE, FALSE, FALSE);
9238 break;
9240 case 'P':
9241 if (sbe_p) {
9242 warnx("flag \"%c\" specified more than "
9243 "once in statusbar_elems\n", *p);
9244 break;
9246 sbe_p = 1;
9247 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9248 t->sbe.position, FALSE, FALSE, FALSE);
9249 break;
9250 case 'B':
9251 if (sbe_b) {
9252 warnx("flag \"%c\" specified more than "
9253 "once in statusbar_elems\n", *p);
9254 break;
9256 sbe_b = 1;
9257 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9258 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9259 break;
9260 case 'Z':
9261 if (sbe_z) {
9262 warnx("flag \"%c\" specified more than "
9263 "once in statusbar_elems\n", *p);
9264 break;
9266 sbe_z = 1;
9267 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9268 t->sbe.zoom, FALSE, FALSE, FALSE);
9269 break;
9270 default:
9271 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9272 break;
9276 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9278 /* buffer list */
9279 t->buffers = create_buffers(t);
9280 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9282 /* xtp meaning is normal by default */
9283 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9285 /* set empty favicon */
9286 xt_icon_from_name(t, "text-html");
9288 /* and show it all */
9289 gtk_widget_show_all(b);
9290 gtk_widget_show_all(t->vbox);
9292 /* compact tab bar */
9293 t->tab_elems.label = gtk_label_new(title);
9294 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9295 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9296 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9297 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9299 t->tab_elems.eventbox = gtk_event_box_new();
9300 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9301 t->tab_elems.sep = gtk_vseparator_new();
9303 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9304 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9305 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9306 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9307 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9308 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9310 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9311 TRUE, 0);
9312 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9313 FALSE, 0);
9314 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9315 t->tab_elems.box);
9317 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9318 TRUE, 0);
9319 gtk_widget_show_all(t->tab_elems.eventbox);
9321 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9322 append_tab(t);
9323 else {
9324 id = position >= 0 ? position :
9325 gtk_notebook_get_current_page(notebook) + 1;
9326 if (id > gtk_notebook_get_n_pages(notebook))
9327 append_tab(t);
9328 else {
9329 TAILQ_INSERT_TAIL(&tabs, t, entry);
9330 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9331 gtk_box_reorder_child(GTK_BOX(tab_bar),
9332 t->tab_elems.eventbox, id);
9333 recalc_tabs();
9337 #if GTK_CHECK_VERSION(2, 20, 0)
9338 /* turn spinner off if we are a new tab without uri */
9339 if (!load) {
9340 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9341 gtk_widget_hide(t->spinner);
9343 #endif
9344 /* make notebook tabs reorderable */
9345 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9347 /* compact tabs clickable */
9348 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9349 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9351 g_object_connect(G_OBJECT(t->cmd),
9352 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9353 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9354 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9355 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9356 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9357 (char *)NULL);
9359 /* reuse wv_button_cb to hide oops */
9360 g_object_connect(G_OBJECT(t->oops),
9361 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9362 (char *)NULL);
9364 g_signal_connect(t->buffers,
9365 "row-activated", G_CALLBACK(row_activated_cb), t);
9366 g_object_connect(G_OBJECT(t->buffers),
9367 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9369 g_object_connect(G_OBJECT(t->wv),
9370 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9371 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9372 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9373 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9374 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9375 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9376 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9377 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9378 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9379 "signal::event", G_CALLBACK(webview_event_cb), t,
9380 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9381 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9382 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9383 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9384 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9385 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9386 (char *)NULL);
9387 g_signal_connect(t->wv,
9388 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9389 g_signal_connect(t->wv,
9390 "notify::title", G_CALLBACK(notify_title_cb), t);
9392 /* hijack the unused keys as if we were the browser */
9393 g_object_connect(G_OBJECT(t->toolbar),
9394 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9395 (char *)NULL);
9397 g_signal_connect(G_OBJECT(bb), "button_press_event",
9398 G_CALLBACK(tab_close_cb), t);
9400 /* setup history */
9401 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9402 /* restore the tab's history */
9403 if (u && u->history) {
9404 items = u->history;
9405 while (items) {
9406 item = items->data;
9407 webkit_web_back_forward_list_add_item(t->bfl, item);
9408 items = g_list_next(items);
9411 item = g_list_nth_data(u->history, u->back);
9412 if (item)
9413 webkit_web_view_go_to_back_forward_item(t->wv, item);
9415 g_list_free(items);
9416 g_list_free(u->history);
9417 } else
9418 webkit_web_back_forward_list_clear(t->bfl);
9420 /* hide stuff */
9421 hide_cmd(t);
9422 hide_oops(t);
9423 hide_buffers(t);
9424 url_set_visibility();
9425 statusbar_set_visibility();
9427 if (focus) {
9428 set_current_tab(t->tab_id);
9429 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9430 t->tab_id);
9432 if (load) {
9433 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9434 load_uri(t, title);
9435 } else {
9436 if (show_url == 1)
9437 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9438 else
9439 focus_webview(t);
9441 } else if (load)
9442 load_uri(t, title);
9444 recolor_compact_tabs();
9445 setzoom_webkit(t, XT_ZOOM_NORMAL);
9446 return (t);
9449 void
9450 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9451 gpointer *udata)
9453 struct tab *t;
9454 const gchar *uri;
9456 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9458 if (gtk_notebook_get_current_page(notebook) == -1)
9459 recalc_tabs();
9461 TAILQ_FOREACH(t, &tabs, entry) {
9462 if (t->tab_id == pn) {
9463 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9464 "%d\n", pn);
9466 uri = get_title(t, TRUE);
9467 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9469 hide_cmd(t);
9470 hide_oops(t);
9472 if (t->focus_wv) {
9473 /* can't use focus_webview here */
9474 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9480 void
9481 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9482 gpointer *udata)
9484 struct tab *t = NULL, *tt;
9486 recalc_tabs();
9488 TAILQ_FOREACH(tt, &tabs, entry)
9489 if (tt->tab_id == pn) {
9490 t = tt;
9491 break;
9493 if (t == NULL)
9494 return;
9495 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9497 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9498 t->tab_id);
9501 void
9502 menuitem_response(struct tab *t)
9504 gtk_notebook_set_current_page(notebook, t->tab_id);
9507 gboolean
9508 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9510 GtkWidget *menu, *menu_items;
9511 GdkEventButton *bevent;
9512 const gchar *uri;
9513 struct tab *ti;
9515 if (event->type == GDK_BUTTON_PRESS) {
9516 bevent = (GdkEventButton *) event;
9517 menu = gtk_menu_new();
9519 TAILQ_FOREACH(ti, &tabs, entry) {
9520 if ((uri = get_uri(ti)) == NULL)
9521 /* XXX make sure there is something to print */
9522 /* XXX add gui pages in here to look purdy */
9523 uri = "(untitled)";
9524 menu_items = gtk_menu_item_new_with_label(uri);
9525 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9526 gtk_widget_show(menu_items);
9528 g_signal_connect_swapped((menu_items),
9529 "activate", G_CALLBACK(menuitem_response),
9530 (gpointer)ti);
9533 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9534 bevent->button, bevent->time);
9536 /* unref object so it'll free itself when popped down */
9537 #if !GTK_CHECK_VERSION(3, 0, 0)
9538 /* XXX does not need unref with gtk+3? */
9539 g_object_ref_sink(menu);
9540 g_object_unref(menu);
9541 #endif
9543 return (TRUE /* eat event */);
9546 return (FALSE /* propagate */);
9550 icon_size_map(int icon_size)
9552 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9553 icon_size > GTK_ICON_SIZE_DIALOG)
9554 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9556 return (icon_size);
9559 GtkWidget *
9560 create_button(char *name, char *stockid, int size)
9562 GtkWidget *button, *image;
9563 gchar *rcstring;
9564 int gtk_icon_size;
9566 rcstring = g_strdup_printf(
9567 "style \"%s-style\"\n"
9568 "{\n"
9569 " GtkWidget::focus-padding = 0\n"
9570 " GtkWidget::focus-line-width = 0\n"
9571 " xthickness = 0\n"
9572 " ythickness = 0\n"
9573 "}\n"
9574 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9575 gtk_rc_parse_string(rcstring);
9576 g_free(rcstring);
9577 button = gtk_button_new();
9578 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9579 gtk_icon_size = icon_size_map(size ? size : icon_size);
9581 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9582 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9583 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9584 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9585 gtk_widget_set_name(button, name);
9586 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9588 return (button);
9591 void
9592 button_set_stockid(GtkWidget *button, char *stockid)
9594 GtkWidget *image;
9596 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9597 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9598 gtk_button_set_image(GTK_BUTTON(button), image);
9601 void
9602 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9604 gchar *p = NULL;
9605 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9606 gint len;
9608 if (xterm_workaround == 0)
9609 return;
9612 * xterm doesn't play nice with clipboards because it clears the
9613 * primary when clicked. We rely on primary being set to properly
9614 * handle middle mouse button clicks (paste). So when someone clears
9615 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9616 * other application behavior (as in DON'T clear primary).
9619 p = gtk_clipboard_wait_for_text(primary);
9620 if (p == NULL) {
9621 if (gdk_property_get(gdk_get_default_root_window(),
9622 atom,
9623 gdk_atom_intern("STRING", FALSE),
9625 1024 * 1024 /* picked out of my butt */,
9626 FALSE,
9627 NULL,
9628 NULL,
9629 &len,
9630 (guchar **)&p)) {
9631 /* yes sir, we need to NUL the string */
9632 p[len] = '\0';
9633 gtk_clipboard_set_text(primary, p, -1);
9637 if (p)
9638 g_free(p);
9641 void
9642 create_canvas(void)
9644 GtkWidget *vbox;
9645 GList *l = NULL;
9646 GdkPixbuf *pb;
9647 char file[PATH_MAX];
9648 int i;
9650 vbox = gtk_vbox_new(FALSE, 0);
9651 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9652 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9653 #if !GTK_CHECK_VERSION(3, 0, 0)
9654 /* XXX seems to be needed with gtk+2 */
9655 gtk_notebook_set_tab_hborder(notebook, 0);
9656 gtk_notebook_set_tab_vborder(notebook, 0);
9657 #endif
9658 gtk_notebook_set_scrollable(notebook, TRUE);
9659 gtk_notebook_set_show_border(notebook, FALSE);
9660 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9662 abtn = gtk_button_new();
9663 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9664 gtk_widget_set_size_request(arrow, -1, -1);
9665 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9666 gtk_widget_set_size_request(abtn, -1, 20);
9668 #if GTK_CHECK_VERSION(2, 20, 0)
9669 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9670 #endif
9671 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9673 /* compact tab bar */
9674 tab_bar = gtk_hbox_new(TRUE, 0);
9676 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9677 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9678 gtk_widget_set_size_request(vbox, -1, -1);
9680 g_object_connect(G_OBJECT(notebook),
9681 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9682 (char *)NULL);
9683 g_object_connect(G_OBJECT(notebook),
9684 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9685 NULL, (char *)NULL);
9686 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9687 G_CALLBACK(arrow_cb), NULL);
9689 main_window = create_window();
9690 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9692 /* icons */
9693 for (i = 0; i < LENGTH(icons); i++) {
9694 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9695 pb = gdk_pixbuf_new_from_file(file, NULL);
9696 l = g_list_append(l, pb);
9698 gtk_window_set_default_icon_list(l);
9700 /* clipboard work around */
9701 if (xterm_workaround)
9702 g_signal_connect(
9703 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9704 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9706 gtk_widget_show_all(abtn);
9707 gtk_widget_show_all(main_window);
9708 notebook_tab_set_visibility();
9711 void
9712 set_hook(void **hook, char *name)
9714 if (hook == NULL)
9715 errx(1, "set_hook");
9717 if (*hook == NULL) {
9718 *hook = dlsym(RTLD_NEXT, name);
9719 if (*hook == NULL)
9720 errx(1, "can't hook %s", name);
9724 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9725 gboolean
9726 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9728 g_return_val_if_fail(cookie1, FALSE);
9729 g_return_val_if_fail(cookie2, FALSE);
9731 return (!strcmp (cookie1->name, cookie2->name) &&
9732 !strcmp (cookie1->value, cookie2->value) &&
9733 !strcmp (cookie1->path, cookie2->path) &&
9734 !strcmp (cookie1->domain, cookie2->domain));
9737 void
9738 transfer_cookies(void)
9740 GSList *cf;
9741 SoupCookie *sc, *pc;
9743 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9745 for (;cf; cf = cf->next) {
9746 pc = cf->data;
9747 sc = soup_cookie_copy(pc);
9748 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9751 soup_cookies_free(cf);
9754 void
9755 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9757 GSList *cf;
9758 SoupCookie *ci;
9760 print_cookie("soup_cookie_jar_delete_cookie", c);
9762 if (cookies_enabled == 0)
9763 return;
9765 if (jar == NULL || c == NULL)
9766 return;
9768 /* find and remove from persistent jar */
9769 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9771 for (;cf; cf = cf->next) {
9772 ci = cf->data;
9773 if (soup_cookie_equal(ci, c)) {
9774 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9775 break;
9779 soup_cookies_free(cf);
9781 /* delete from session jar */
9782 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9785 void
9786 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9788 struct domain *d = NULL;
9789 SoupCookie *c;
9790 FILE *r_cookie_f;
9792 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9793 jar, p_cookiejar, s_cookiejar);
9795 if (cookies_enabled == 0)
9796 return;
9798 /* see if we are up and running */
9799 if (p_cookiejar == NULL) {
9800 _soup_cookie_jar_add_cookie(jar, cookie);
9801 return;
9803 /* disallow p_cookiejar adds, shouldn't happen */
9804 if (jar == p_cookiejar)
9805 return;
9807 /* sanity */
9808 if (jar == NULL || cookie == NULL)
9809 return;
9811 if (enable_cookie_whitelist &&
9812 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9813 blocked_cookies++;
9814 DNPRINTF(XT_D_COOKIE,
9815 "soup_cookie_jar_add_cookie: reject %s\n",
9816 cookie->domain);
9817 if (save_rejected_cookies) {
9818 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9819 show_oops(NULL, "can't open reject cookie file");
9820 return;
9822 fseek(r_cookie_f, 0, SEEK_END);
9823 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9824 cookie->http_only ? "#HttpOnly_" : "",
9825 cookie->domain,
9826 *cookie->domain == '.' ? "TRUE" : "FALSE",
9827 cookie->path,
9828 cookie->secure ? "TRUE" : "FALSE",
9829 cookie->expires ?
9830 (gulong)soup_date_to_time_t(cookie->expires) :
9832 cookie->name,
9833 cookie->value);
9834 fflush(r_cookie_f);
9835 fclose(r_cookie_f);
9837 if (!allow_volatile_cookies)
9838 return;
9841 if (cookie->expires == NULL && session_timeout) {
9842 soup_cookie_set_expires(cookie,
9843 soup_date_new_from_now(session_timeout));
9844 print_cookie("modified add cookie", cookie);
9847 /* see if we are white listed for persistence */
9848 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9849 /* add to persistent jar */
9850 c = soup_cookie_copy(cookie);
9851 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9852 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9855 /* add to session jar */
9856 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9857 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9860 void
9861 setup_cookies(void)
9863 char file[PATH_MAX];
9865 set_hook((void *)&_soup_cookie_jar_add_cookie,
9866 "soup_cookie_jar_add_cookie");
9867 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9868 "soup_cookie_jar_delete_cookie");
9870 if (cookies_enabled == 0)
9871 return;
9874 * the following code is intricate due to overriding several libsoup
9875 * functions.
9876 * do not alter order of these operations.
9879 /* rejected cookies */
9880 if (save_rejected_cookies)
9881 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9882 XT_REJECT_FILE);
9884 /* persistent cookies */
9885 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9886 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9888 /* session cookies */
9889 s_cookiejar = soup_cookie_jar_new();
9890 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9891 cookie_policy, (void *)NULL);
9892 transfer_cookies();
9894 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9897 void
9898 setup_proxy(char *uri)
9900 if (proxy_uri) {
9901 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9902 soup_uri_free(proxy_uri);
9903 proxy_uri = NULL;
9905 if (http_proxy) {
9906 if (http_proxy != uri) {
9907 g_free(http_proxy);
9908 http_proxy = NULL;
9912 if (uri) {
9913 http_proxy = g_strdup(uri);
9914 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9915 proxy_uri = soup_uri_new(http_proxy);
9916 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9917 g_object_set(session, "proxy-uri", proxy_uri,
9918 (char *)NULL);
9923 set_http_proxy(char *proxy)
9925 SoupURI *uri;
9927 if (proxy == NULL)
9928 return (1);
9930 /* see if we need to clear it instead */
9931 if (strlen(proxy) == 0) {
9932 setup_proxy(NULL);
9933 return (0);
9936 uri = soup_uri_new(proxy);
9937 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9938 return (1);
9940 setup_proxy(proxy);
9942 soup_uri_free(uri);
9944 return (0);
9948 send_cmd_to_socket(char *cmd)
9950 int s, len, rv = 1;
9951 struct sockaddr_un sa;
9953 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9954 warnx("%s: socket", __func__);
9955 return (rv);
9958 sa.sun_family = AF_UNIX;
9959 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9960 work_dir, XT_SOCKET_FILE);
9961 len = SUN_LEN(&sa);
9963 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9964 warnx("%s: connect", __func__);
9965 goto done;
9968 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9969 warnx("%s: send", __func__);
9970 goto done;
9973 rv = 0;
9974 done:
9975 close(s);
9976 return (rv);
9979 gboolean
9980 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9982 int s, n;
9983 char str[XT_MAX_URL_LENGTH];
9984 socklen_t t = sizeof(struct sockaddr_un);
9985 struct sockaddr_un sa;
9986 struct passwd *p;
9987 uid_t uid;
9988 gid_t gid;
9989 struct tab *tt;
9990 gint fd = g_io_channel_unix_get_fd(source);
9992 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9993 warn("accept");
9994 return (FALSE);
9997 if (getpeereid(s, &uid, &gid) == -1) {
9998 warn("getpeereid");
9999 return (FALSE);
10001 if (uid != getuid() || gid != getgid()) {
10002 warnx("unauthorized user");
10003 return (FALSE);
10006 p = getpwuid(uid);
10007 if (p == NULL) {
10008 warnx("not a valid user");
10009 return (FALSE);
10012 n = recv(s, str, sizeof(str), 0);
10013 if (n <= 0)
10014 return (TRUE);
10016 tt = TAILQ_LAST(&tabs, tab_list);
10017 cmd_execute(tt, str);
10018 return (TRUE);
10022 is_running(void)
10024 int s, len, rv = 1;
10025 struct sockaddr_un sa;
10027 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10028 warn("is_running: socket");
10029 return (-1);
10032 sa.sun_family = AF_UNIX;
10033 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10034 work_dir, XT_SOCKET_FILE);
10035 len = SUN_LEN(&sa);
10037 /* connect to see if there is a listener */
10038 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10039 rv = 0; /* not running */
10040 else
10041 rv = 1; /* already running */
10043 close(s);
10045 return (rv);
10049 build_socket(void)
10051 int s, len;
10052 struct sockaddr_un sa;
10054 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10055 warn("build_socket: socket");
10056 return (-1);
10059 sa.sun_family = AF_UNIX;
10060 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10061 work_dir, XT_SOCKET_FILE);
10062 len = SUN_LEN(&sa);
10064 /* connect to see if there is a listener */
10065 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10066 /* no listener so we will */
10067 unlink(sa.sun_path);
10069 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10070 warn("build_socket: bind");
10071 goto done;
10074 if (listen(s, 1) == -1) {
10075 warn("build_socket: listen");
10076 goto done;
10079 return (s);
10082 done:
10083 close(s);
10084 return (-1);
10087 gboolean
10088 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10089 GtkTreeIter *iter, struct tab *t)
10091 gchar *value;
10093 gtk_tree_model_get(model, iter, 0, &value, -1);
10094 load_uri(t, value);
10095 g_free(value);
10097 return (FALSE);
10100 gboolean
10101 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10102 GtkTreeIter *iter, struct tab *t)
10104 gchar *value;
10106 gtk_tree_model_get(model, iter, 0, &value, -1);
10107 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10108 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10109 g_free(value);
10111 return (TRUE);
10114 void
10115 completion_add_uri(const gchar *uri)
10117 GtkTreeIter iter;
10119 /* add uri to list_store */
10120 gtk_list_store_append(completion_model, &iter);
10121 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10124 gboolean
10125 completion_match(GtkEntryCompletion *completion, const gchar *key,
10126 GtkTreeIter *iter, gpointer user_data)
10128 gchar *value;
10129 gboolean match = FALSE;
10131 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10132 -1);
10134 if (value == NULL)
10135 return FALSE;
10137 match = match_uri(value, key);
10139 g_free(value);
10140 return (match);
10143 void
10144 completion_add(struct tab *t)
10146 /* enable completion for tab */
10147 t->completion = gtk_entry_completion_new();
10148 gtk_entry_completion_set_text_column(t->completion, 0);
10149 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10150 gtk_entry_completion_set_model(t->completion,
10151 GTK_TREE_MODEL(completion_model));
10152 gtk_entry_completion_set_match_func(t->completion, completion_match,
10153 NULL, NULL);
10154 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10155 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10156 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10157 G_CALLBACK(completion_select_cb), t);
10158 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10159 G_CALLBACK(completion_hover_cb), t);
10162 void
10163 xxx_dir(char *dir)
10165 struct stat sb;
10167 if (stat(dir, &sb)) {
10168 if (mkdir(dir, S_IRWXU) == -1)
10169 err(1, "mkdir %s", dir);
10170 if (stat(dir, &sb))
10171 err(1, "stat %s", dir);
10173 if (S_ISDIR(sb.st_mode) == 0)
10174 errx(1, "%s not a dir", dir);
10175 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10176 warnx("fixing invalid permissions on %s", dir);
10177 if (chmod(dir, S_IRWXU) == -1)
10178 err(1, "chmod %s", dir);
10182 void
10183 usage(void)
10185 fprintf(stderr,
10186 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10187 exit(0);
10191 main(int argc, char *argv[])
10193 struct stat sb;
10194 int c, s, optn = 0, opte = 0, focus = 1;
10195 char conf[PATH_MAX] = { '\0' };
10196 char file[PATH_MAX];
10197 char *env_proxy = NULL;
10198 char *cmd = NULL;
10199 FILE *f = NULL;
10200 struct karg a;
10201 struct sigaction sact;
10202 GIOChannel *channel;
10203 struct rlimit rlp;
10205 start_argv = argv;
10207 /* prepare gtk */
10208 #ifdef USE_THREADS
10209 g_thread_init(NULL);
10210 #if !defined(__linux__)
10211 /* this call hangs the flash plugin and isn't needed on linux it seems */
10212 gdk_threads_init();
10213 #endif
10214 gdk_threads_enter();
10215 #endif
10216 gtk_init(&argc, &argv);
10218 #ifdef USE_THREADS
10219 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
10220 #endif
10221 gnutls_global_init();
10223 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10225 RB_INIT(&hl);
10226 RB_INIT(&js_wl);
10227 RB_INIT(&downloads);
10229 TAILQ_INIT(&sessions);
10230 TAILQ_INIT(&tabs);
10231 TAILQ_INIT(&mtl);
10232 TAILQ_INIT(&aliases);
10233 TAILQ_INIT(&undos);
10234 TAILQ_INIT(&kbl);
10235 TAILQ_INIT(&spl);
10236 TAILQ_INIT(&chl);
10237 TAILQ_INIT(&shl);
10239 /* fiddle with ulimits */
10240 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10241 warn("getrlimit");
10242 else {
10243 /* just use them all */
10244 rlp.rlim_cur = rlp.rlim_max;
10245 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10246 warn("setrlimit");
10247 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10248 warn("getrlimit");
10249 else if (rlp.rlim_cur <= 256)
10250 startpage_add("%s requires at least 256 file "
10251 "descriptors, currently it has up to %d available",
10252 __progname, rlp.rlim_cur);
10255 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10256 switch (c) {
10257 case 'S':
10258 show_url = 0;
10259 break;
10260 case 'T':
10261 show_tabs = 0;
10262 break;
10263 case 'V':
10264 errx(0 , "Version: %s", version);
10265 break;
10266 case 'f':
10267 strlcpy(conf, optarg, sizeof(conf));
10268 break;
10269 case 's':
10270 strlcpy(named_session, optarg, sizeof(named_session));
10271 break;
10272 case 't':
10273 tabless = 1;
10274 break;
10275 case 'n':
10276 optn = 1;
10277 break;
10278 case 'e':
10279 opte = 1;
10280 break;
10281 default:
10282 usage();
10283 /* NOTREACHED */
10286 argc -= optind;
10287 argv += optind;
10289 init_keybindings();
10291 /* generate session keys for xtp pages */
10292 generate_xtp_session_key(&dl_session_key);
10293 generate_xtp_session_key(&hl_session_key);
10294 generate_xtp_session_key(&cl_session_key);
10295 generate_xtp_session_key(&fl_session_key);
10297 /* signals */
10298 bzero(&sact, sizeof(sact));
10299 sigemptyset(&sact.sa_mask);
10300 sact.sa_handler = sigchild;
10301 sact.sa_flags = SA_NOCLDSTOP;
10302 sigaction(SIGCHLD, &sact, NULL);
10304 /* set download dir */
10305 pwd = getpwuid(getuid());
10306 if (pwd == NULL)
10307 errx(1, "invalid user %d", getuid());
10308 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10310 /* compile buffer command regexes */
10311 buffercmd_init();
10313 /* set default string settings */
10314 home = g_strdup("https://www.cyphertite.com");
10315 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10316 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10317 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10318 cmd_font_name = g_strdup("monospace normal 9");
10319 oops_font_name = g_strdup("monospace normal 9");
10320 statusbar_font_name = g_strdup("monospace normal 9");
10321 tabbar_font_name = g_strdup("monospace normal 9");
10322 statusbar_elems = g_strdup("BP");
10323 encoding = g_strdup("ISO-8859-1");
10325 /* read config file */
10326 if (strlen(conf) == 0)
10327 snprintf(conf, sizeof conf, "%s/.%s",
10328 pwd->pw_dir, XT_CONF_FILE);
10329 config_parse(conf, 0);
10331 /* init fonts */
10332 cmd_font = pango_font_description_from_string(cmd_font_name);
10333 oops_font = pango_font_description_from_string(oops_font_name);
10334 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10335 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10337 /* working directory */
10338 if (strlen(work_dir) == 0)
10339 snprintf(work_dir, sizeof work_dir, "%s/%s",
10340 pwd->pw_dir, XT_DIR);
10341 xxx_dir(work_dir);
10343 /* icon cache dir */
10344 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10345 xxx_dir(cache_dir);
10347 /* certs dir */
10348 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10349 xxx_dir(certs_dir);
10351 /* sessions dir */
10352 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10353 work_dir, XT_SESSIONS_DIR);
10354 xxx_dir(sessions_dir);
10356 /* runtime settings that can override config file */
10357 if (runtime_settings[0] != '\0')
10358 config_parse(runtime_settings, 1);
10360 /* download dir */
10361 if (!strcmp(download_dir, pwd->pw_dir))
10362 strlcat(download_dir, "/downloads", sizeof download_dir);
10363 xxx_dir(download_dir);
10365 /* favorites file */
10366 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10367 if (stat(file, &sb)) {
10368 warnx("favorites file doesn't exist, creating it");
10369 if ((f = fopen(file, "w")) == NULL)
10370 err(1, "favorites");
10371 fclose(f);
10374 /* quickmarks file */
10375 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10376 if (stat(file, &sb)) {
10377 warnx("quickmarks file doesn't exist, creating it");
10378 if ((f = fopen(file, "w")) == NULL)
10379 err(1, "quickmarks");
10380 fclose(f);
10383 /* search history */
10384 if (history_autosave) {
10385 snprintf(search_file, sizeof search_file, "%s/%s",
10386 work_dir, XT_SEARCH_FILE);
10387 if (stat(search_file, &sb)) {
10388 warnx("search history file doesn't exist, creating it");
10389 if ((f = fopen(search_file, "w")) == NULL)
10390 err(1, "search_history");
10391 fclose(f);
10393 history_read(&shl, search_file, &search_history_count);
10396 /* command history */
10397 if (history_autosave) {
10398 snprintf(command_file, sizeof command_file, "%s/%s",
10399 work_dir, XT_COMMAND_FILE);
10400 if (stat(command_file, &sb)) {
10401 warnx("command history file doesn't exist, creating it");
10402 if ((f = fopen(command_file, "w")) == NULL)
10403 err(1, "command_history");
10404 fclose(f);
10406 history_read(&chl, command_file, &cmd_history_count);
10409 /* cookies */
10410 session = webkit_get_default_session();
10411 setup_cookies();
10413 /* certs */
10414 if (ssl_ca_file) {
10415 if (stat(ssl_ca_file, &sb)) {
10416 warnx("no CA file: %s", ssl_ca_file);
10417 g_free(ssl_ca_file);
10418 ssl_ca_file = NULL;
10419 } else
10420 g_object_set(session,
10421 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10422 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10423 (void *)NULL);
10426 /* guess_search regex */
10427 if (url_regex == NULL)
10428 url_regex = g_strdup(XT_URL_REGEX);
10429 if (url_regex)
10430 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10431 startpage_add("invalid url regex %s", url_regex);
10433 /* proxy */
10434 env_proxy = getenv("http_proxy");
10435 if (env_proxy)
10436 setup_proxy(env_proxy);
10437 else
10438 setup_proxy(http_proxy);
10440 if (opte) {
10441 send_cmd_to_socket(argv[0]);
10442 exit(0);
10445 /* set some connection parameters */
10446 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10447 g_object_set(session, "max-conns-per-host", max_host_connections,
10448 (char *)NULL);
10450 /* see if there is already an xxxterm running */
10451 if (single_instance && is_running()) {
10452 optn = 1;
10453 warnx("already running");
10456 if (optn) {
10457 while (argc) {
10458 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10459 send_cmd_to_socket(cmd);
10460 if (cmd)
10461 g_free(cmd);
10463 argc--;
10464 argv++;
10466 exit(0);
10469 /* uri completion */
10470 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10472 /* buffers */
10473 buffers_store = gtk_list_store_new
10474 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10476 qmarks_load();
10478 /* go graphical */
10479 create_canvas();
10480 notebook_tab_set_visibility();
10482 if (save_global_history)
10483 restore_global_history();
10485 /* restore session list */
10486 restore_sessions_list();
10488 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10489 restore_saved_tabs();
10490 else {
10491 a.s = named_session;
10492 a.i = XT_SES_DONOTHING;
10493 open_tabs(NULL, &a);
10496 /* see if we have an exception */
10497 if (!TAILQ_EMPTY(&spl)) {
10498 create_new_tab("about:startpage", NULL, focus, -1);
10499 focus = 0;
10502 while (argc) {
10503 create_new_tab(argv[0], NULL, focus, -1);
10504 focus = 0;
10506 argc--;
10507 argv++;
10510 if (TAILQ_EMPTY(&tabs))
10511 create_new_tab(home, NULL, 1, -1);
10513 if (enable_socket)
10514 if ((s = build_socket()) != -1) {
10515 channel = g_io_channel_unix_new(s);
10516 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10519 gtk_main();
10521 gnutls_global_deinit();
10523 if (url_regex)
10524 regfree(&url_re);
10526 #ifdef USE_THREADS
10527 gdk_threads_leave();
10528 #endif
10529 return (0);